/////////////////////////////////////////////////////////////////////////////////// // File : ioc_driver.c // Date : 23/05/2013 // Author : alain greiner // Maintainer : cesar fuguet // Copyright (c) UPMC-LIP6 /////////////////////////////////////////////////////////////////////////////////// // The ioc_driver.c and ioc_driver.h files are part ot the GIET-VM kernel. // // This abstact driver define a generic API, supporting various physical // block device controlers, including: // - vci_block_device : single channel) => bdv_driver // - vci_ahci : multi channels => hba_driver // - sd_card : single channel => sdc_driver // - ramdisk (single channel meory mapped virtual disk) => rdk_driver // // It can exist only one block-device type in the architecture, that must be // defined by one of the following configuration variables in hard_config.h file: // USE_IOC_BDV, USE_IOC_SDC, USE_IOC_HBA, USE_IOC_RDK. // // Any physical driver xxx must provide the following API: // - _xxx_init() // - _xxx_read() // - _xxx_write() // - _xxx_get_status() // - _xxx_get_block_size() // The "channel" parameter is no transmited to single channel devices. // // The _ioc_read() and _ioc_write() functions are always blocking for // the calling user program. // // These functions compute the physical address of the memory buffer before // calling the proper physical device. They can be called in 3 modes: // // - In BOOT mode, these functions use the buffer virtual address // as a physical address if the MMU is not activated. // They make a V2P translation if the MMU is activated. // This mode is used to load the map.bin file (before memory activation), // or to load the various .elf files (after MMU activation). // // - In KERNEL mode, these functions make a V2P translation to // compute the buffer physical address. // There is no checking of user access right to the memory buffer. // This mode must be used for an "open" system call. // // - In USER mode, these functions make a V2P translation to // compute the buffer physical address. // The user access right to the memory buffer are checked. // This mode must be used for a "read" or "write" system call. // // The IOMMU can be activated or not: // // 1) When the IOMMU is used, a fixed size 2Mbytes vseg is allocated to // the IOC peripheral, in the I/O virtual space, and the user buffer is // dynamically remapped in the IOMMU page table. The corresponding entry // in the IOMMU PT1 is defined by the kernel _ioc_iommu_ix1 variable. // The number of pages to be unmapped is stored in the _ioc_npages variable. // The number of PT2 entries is dynamically computed and stored in the // kernel _ioc_iommu_npages variable. It cannot be larger than 512. // The user buffer is unmapped by the _ioc_completed() function when // the transfer is completed. // // 2/ If the IOMMU is not used, we check that the user buffer is mapped to a // contiguous physical buffer (this is generally true because the user space // page tables are statically constructed to use contiguous physical memory). // // Finally, the memory buffer must fulfill the following conditions: // - The buffer must be word aligned, // - The buffer must be mapped in user space for an user access, // - The buffer must be writable in case of (to_mem) access, // - The total number of physical pages occupied by the user buffer cannot // be larger than 512 pages if the IOMMU is activated, // - All physical pages occupied by the user buffer must be contiguous // if the IOMMU is not activated. // An error code is returned if these conditions are not verified. // // The SEG_IOC_BASE virtual base address must be defined in hard_config.h, // as it is used by the BDV, HBA and SPI drivers. // // If the RAMDISK is used, an extra memory segment with virtual base address // SEG_RDK_BASE, used by RDK driver, must be defined in hard_config.h. /////////////////////////////////////////////////////////////////////////////////// // Implementation note: // In order to share the code, the two _ioc_read() and _ioc_write() functions // call the same _ioc_access() function. /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #if !defined( SEG_IOC_BASE ) # error: You must define SEG_IOC_BASE in the hard_config.h file #endif #if !defined( USE_IOB ) # error: You must define USE_IOB in the hard_config.h file #endif #if !defined(GIET_USE_IOMMU) # error: You must define GIET_USE_IOMMU in the giet_config.h file #endif #if (USE_IOC_BDV + USE_IOC_SPI + USE_IOC_HBA + USE_IOC_RDK) != 1 # error: You must use only one IOC controller type (BDV or SPI or HBA or RDK) #endif #if USE_IOC_BDV # include #endif #if USE_IOC_SPI # include #endif #if USE_IOC_HBA # include #endif #if USE_IOC_RDK # include #endif /////////////////////////////////////////////////////////////////////////////// // IOC global variables /////////////////////////////////////////////////////////////////////////////// #define in_unckdata __attribute__((section (".unckdata"))) in_unckdata volatile unsigned int _ioc_iommu_ix1 = 0; in_unckdata volatile unsigned int _ioc_iommu_npages; /////////////////////////////////////////////////////////////////////////////// // This function transfer data between a memory buffer and the block device. // The buffer lentgth is (count*block_size) bytes. // Arguments are: // - to_mem : from external storage to memory when non 0. // - mode : BOOT_PA / BOOT_VA / KERNEL / USER // - lba : first block index on the external storage. // - buf_vaddr : virtual base address of the memory buffer. // - count : number of blocks to be transfered. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// static unsigned int _ioc_access( unsigned int to_mem, unsigned int channel, unsigned int mode, unsigned int lba, unsigned int buf_vaddr, unsigned int count) { #if GIET_DEBUG_IOC_DRIVER unsigned int procid = _get_procid(); unsigned int cid = procid / NB_PROCS_MAX; unsigned int lpid = procid % NB_PROCS_MAX; unsigned int x = cid >> Y_WIDTH; unsigned int y = cid & ((1< 0) ) { _printf("\n[GIET ERROR] in _ioc_access() : channel must be 0 when HBA not used\n"); _exit(); } unsigned int length = count << 9; // count * 512 bytes // computing memory buffer physical address if ( (mode == IOC_BOOT_MODE) && ((_get_mmu_mode() & 0x4) == 0) ) // identity mapping { buf_paddr = (paddr_t)buf_vaddr; } else // V2P translation required { // get page table virtual address pt_vbase = _get_context_slot(CTX_PTAB_ID); vpn_min = buf_vaddr >> 12; vpn_max = (buf_vaddr + length - 1) >> 12; // loop on all virtual pages covering the user buffer for (vpn = vpn_min, ix2 = 0 ; vpn <= vpn_max ; vpn++, ix2++ ) { // get ppn and flags for each vpn unsigned int ko = _v2p_translate( (page_table_t*)pt_vbase, vpn, &ppn, &flags); // check access rights if ( ko ) { _printf("\n[GIET ERROR] in _ioc_access() : buffer unmapped\n"); return 1; } if ( (mode == IOC_USER_MODE) && ((flags & PTE_U) == 0) ) { _printf("\n[GIET ERROR] in _ioc_access() : buffer not user accessible\n"); return 1; } if ( ((flags & PTE_W) == 0 ) && to_mem ) { _printf("\n[GIET ERROR] in _ioc_access() : buffer not writable\n"); return 1; } // save first ppn value if (ix2 == 0) ppn_first = ppn; #if GIET_USE_IOMMU // check buffer length < 2 Mbytes if (ix2 > 511) // check buffer length < 2 Mbytes { _printf("\n[GIET ERROR] in _ioc_access() : user buffer > 2 Mbytes\n"); return 1; } // map the physical page in IOMMU page table _iommu_add_pte2( _ioc_iommu_ix1, // PT1 index ix2, // PT2 index ppn, // Physical page number flags ); // Protection flags // compute user buffer virtual adress in IO space buf_xaddr = (_ioc_iommu_ix1) << 21 | (buf_vaddr & 0xFFF); #else // check that physical pages are contiguous if ((ppn - ppn_first) != ix2) { _printf("[GIET ERROR] in _ioc_access() : split physical buffer\n"); return 1; } // compute user buffer physical adress buf_paddr = (((paddr_t)ppn_first) << 12) | (buf_vaddr & 0xFFF); #endif } // end for vpn } #if GIET_USE_IOMMU // register the number of pages to be unmapped in IOMMU _ioc_iommu_npages = (vpn_max - vpn_min) + 1; #endif if ( to_mem ) // memory write : invalidate data caches { // L1 cache if ( GIET_NO_HARD_CC ) _dcache_buf_invalidate((void *) buf_vaddr, length); // L2 cache (only if IOB used) if ( USE_IOB ) _mmc_inval( buf_paddr, length ); } else // memory read : update data caches { // L1 cache : nothing to do for L1 write-through // L2 cache (only if IOB used) if ( USE_IOB ) _mmc_sync( buf_paddr, length ); } if ( GIET_USE_IOMMU ) buf_paddr = (paddr_t) buf_xaddr; /////////////////////////////////////////// // select the proper physical device /////////////////////////////////////////// #if ( USE_IOC_BDV ) #if GIET_DEBUG_IOC_DRIVER _printf("\n[IOC DEBUG] Calling BDV driver\n"); #endif if (to_mem) error = _bdv_read ( mode, lba, buf_paddr, count); else error = _bdv_write( mode, lba, buf_paddr, count); #elif ( USE_IOC_SPI ) #if GIET_DEBUG_IOC_DRIVER _printf("\n[IOC DEBUG] Calling SPI driver\n"); #endif if (to_mem) error = _sdc_read (mode, lba, buf_paddr, count); else error = _sdc_write(mode, lba, buf_paddr, count); #elif ( USE_IOC_HBA ) #if GIET_DEBUG_IOC_DRIVER _printf("\n[IOC DEBUG] Calling HBA driver\n"); #endif if (to_mem) error = _hba_read (channel, mode, lba, buf_paddr, count); else error = _hba_write(channel, mode, lba, buf_paddr, count); #elif ( USE_IOC_RDK ) #if GIET_DEBUG_IOC_DRIVER _printf("\n[IOC DEBUG] Calling RDK driver\n"); #endif if (to_mem) error = _rdk_read (lba, buf_vaddr, count); else error = _rdk_write(lba, buf_vaddr, count); #endif return error; } // end _ioc_access() /////////////////////////////////////////////////////////////////////////////// // This function cheks block size, and desactivates the IOC interrupts. // Return 0 for success. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_init( unsigned int channel ) { #if ( USE_IOC_BDV ) return _bdv_init(); #elif ( USE_IOC_SPI ) return _sdc_init(); #elif ( USE_IOC_HBA ) return _hba_init( channel ); #elif ( USE_IOC_RDK ) return _rdk_init(); #endif } /////////////////////////////////////////////////////////////////////////////// // Transfer data from the block device to a memory buffer. // - mode : BOOT_PA / BOOT_VA / KERNEL / USER // - lba : first block index on the block device // - buffer : base address of the memory buffer (must be word aligned) // - count : number of blocks to be transfered. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_read( unsigned int channel, unsigned int mode, unsigned int lba, void* buffer, unsigned int count) { return _ioc_access( 1, // read access channel, mode, lba, (unsigned int) buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // Transfer data from a memory buffer to the block device. // - mode : BOOT_PA / BOOT_VA / KERNEL / USER // - lba : first block index on the block device // - buffer : base address of the memory buffer (must be word aligned) // - count : number of blocks to be transfered. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_write( unsigned int channel, unsigned int mode, unsigned int lba, const void* buffer, unsigned int count ) { return _ioc_access( 0, // write access channel, mode, lba, (unsigned int) buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // This function returns in the status variable, the transfert status, and // acknowledge the IRQ if the IOC controler is not busy. // Returns 0 if success, > 0 if error /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_get_status( unsigned int channel ) { #if ( USE_IOC_BDV ) return _bdv_get_status( ); #elif ( USE_IOC_SPI ) return _sdc_get_status( ); #elif ( USE_IOC_HBA ) return _hba_get_status( channel ); #elif ( USE_IOC_RDK ) _printf("[GIET ERROR] _ioc_get_status() should not be called"); _printf(" when RAMDISK is used...\n"); _exit(); return 0; #endif } /////////////////////////////////////////////////////////////////////////////// // This function returns the block_size with which the IOC has been configured. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_get_block_size() { #if ( USE_IOC_BDV ) return _bdv_get_block_size(); #elif ( USE_IOC_SPI ) return _sdc_get_block_size(); #elif ( USE_IOC_HBA ) return _hba_get_block_size(); #elif ( USE_IOC_RDK ) return 512; #endif } // Local Variables: // tab-width: 4 // c-basic-offset: 4 // c-file-offsets:((innamespace . 0)(inline-open . 0)) // indent-tabs-mode: nil // End: // vim: filetype=c:expandtab:shiftwidth=4:tabstop=4:softtabstop=4