/////////////////////////////////////////////////////////////////////////////////// // File : hba_driver.c // Date : 23/11/2013 // Author : alain greiner and zhang // Copyright (c) UPMC-LIP6 /////////////////////////////////////////////////////////////////////////////////// // The hba_driver.c and hba_driver.h files are part ot the GIET-VM kernel. // This driver supports the SocLib VciMultiAhci component, that is a multi-channels, // block oriented, external storage contrĂ´ler, respecting the AHCI standard. // // It can exist only one ahci-device controler in the architecture. // // The _ioc_read() and _ioc_write() functions use the _ioc_access() function, // that is always blocking, but can be called in 4 modes: // // - In BOOT_PA mode, the _ioc_access() function use the buffer virtual address // as a physical address (as the page tables are not build) and use a polling // policy on the IOC_STATUS register to detect transfer completion, as // hardware interrupts are not activated. This mode is used by the // boot code to load the map.bin file into memory. // // - In BOOT_VA mode, the _ioc_access() function makes a V2P translation to // compute the buffer physical address, and use a polling policy on IOC_STATUS // register to detect transfer completion. This mode is used by the boot code // to load the various .elf files into memory. // // - In KERNEL mode, the _ioc_access() function makes a V2P translation to // compute the buffer physical address, and use a descheduling strategy: // The ISR executed when transfer completes should restart the calling task. // There is no checking of user access right to the memory buffer. // This mode must be used to access IOC, for an "open" system call. // // - In USER mode, the _ioc_access() function makes a V2P translation to // compute the buffer physical address, and use a descheduling strategy: // The ISR executed when transfer completes should restart the calling task, // The user access right to the memory buffer must be checked. // This mode must be used to access IOC, for a "read/write" system call. // // As the IOC component can be used by several programs running in parallel, // the _ioc_lock variable guaranties exclusive access to the device. The // _ioc_read() and _ioc_write() functions use atomic LL/SC to get the lock. // // 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 addresses must be defined in giet_vsegs.ld file. /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #if !defined( NB_HBA_CHANNELS ) # error: You must define NB_HBA_CHANNELS in the hard_config.h file #endif #if ( NB_HBA_CHANNELS > 8 ) # error: NB_HBA_CHANNELS cannot be larger than 8 #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 #define in_unckdata __attribute__((section (".unckdata"))) ////////////////////////////////////////////////////////////////// // Global variables ////////////////////////////////////////////////////////////////// // command list array (one per channel) hba_cmd_list_t hba_cmd_list[NB_HBA_CHANNELS] __attribute__((aligned(0x1000))); // command tables array (32 command tables per channel) hba_cmd_table_t hba_cmd_table[NB_HBA_CHANNELS][32] __attribute__((aligned(0x1000))); // command list physical addresses array (one per channel) paddr_t hba_cmd_list_paddr[NB_HBA_CHANNELS]; // command tables physical addresses array (32 command tables per channel) paddr_t hba_cmd_table_paddr[NB_HBA_CHANNELS][32]; // command list pointer array (one per channel) unsigned int hba_cmd_slot[NB_HBA_CHANNELS]; ////////////////////////////////////////////////////////////////// // This function returns the status of a given channel. // return 0 if success, >0 if error ////////////////////////////////////////////////////////////////// unsigned int _hba_get_status( unsigned int channel, unsigned int* status ) { volatile unsigned int* hba_address; hba_address = (unsigned int*)(&seg_ioc_base) + (HBA_SPAN*channel); if( channel >= NB_HBA_CHANNELS ) { _tty_get_lock( 0 ); _puts("\n[GIET ERROR] in _hba_get_status() : illegal channel\n"); _tty_release_lock( 0 ); return 1; } else { *status = hba_address[HBA_PXIS]; return 0; } } ////////////////////////////////////////////////////////////////// // This function reset the status resgister for a given channel. // return 0 if success, >0 if error ////////////////////////////////////////////////////////////////// unsigned int _hba_reset_status( unsigned int channel ) { volatile unsigned int* hba_address; hba_address = (unsigned int*)(&seg_ioc_base) + (HBA_SPAN*channel); if( channel >= NB_HBA_CHANNELS ) { _tty_get_lock( 0 ); _puts("\n[GIET ERROR] in _hba_reset_status() : illegal channel\n"); _tty_release_lock( 0 ); return 1; } else { hba_address[HBA_PXIS] = 0; return 0; } } /////////////////////////////////////////////////////////////////////////////// // This function register a command in both the command list // and the command table, and updates the HBA_PXCI register. // It uses the AHCI Scatter/Gather mechanisme to split the user // buffer in several physical buffers, with the constraint that each physical // buffer must be an integer number of blocks entirely contained in a single // page frame. // return 0 if success, > 0 if error /////////////////////////////////////////////////////////////////////////////// unsigned int _hba_cmd_set( unsigned int is_read, // to memory unsigned int lba, // logic block address unsigned int buf_vaddr, // buffer virtual address unsigned int count ) // number of blocks { volatile unsigned int *hba_address; unsigned int block_size; // defined by the block device (bytes) unsigned int channel_id; // channel index unsigned int pxci; // command list status unsigned int cmd_id; // command index in command list unsigned int buf_id; // for physical buffers covering user buffer unsigned int user_pt_vbase; // user page table virtual base address unsigned int vpn; // for all pages covering the userbuffer unsigned int vpn_min; // first virtual page index for user buffer unsigned int vpn_max; // last virtual page index for user buffer unsigned int offset; // unaligned bytes in page frame: buf_vaddr & 0xFFF unsigned int offset_last; // unaligned bytes in last frame hba_cmd_desc_t* cmd_desc; // command descriptor pointer hba_cmd_table_t* cmd_table; // command table pointer block_size = _hba_get_block_size(); // check buffer alignment if( buf_vaddr & (block_size-1) ) { _tty_get_lock( 0 ); _puts("\n[GIET ERROR] in _hba_set_cmd() : user buffer not block aligned\n"); _tty_release_lock( 0 ); return 1; } // get channel index channel_id = _get_context_slot(CTX_HBA_ID); if ( channel_id == 0xFFFFFFFF ) { _tty_get_lock( 0 ); _puts("\n[GIET ERROR] in _hba_set_cmd() : no HBA channel allocated\n"); _tty_release_lock( 0 ); return 1; } // get hba device address hba_address = (unsigned int*)(&seg_ioc_base) + (HBA_SPAN * channel_id); // get command list status pxci = hba_address[HBA_PXCI]; // get command index and return error if command list full cmd_id = hba_cmd_slot[channel_id]; if( pxci & (1<> 12; vpn_max = (buf_vaddr + (block_size*count) - 1) >> 12; offset = buf_vaddr & 0xFFF; offset_last = (buf_vaddr + (block_size*count) - 1) & 0xFFF; // initialize all buffer descriptors in command table // (loop on all virtual pages covering the user buffer) for( vpn = vpn_min, buf_id = 0 ; vpn <= vpn_max ; vpn++ ) { paddr_t paddr; unsigned int count; unsigned int ppn; unsigned int flags; unsigned int ko; unsigned int buf_id = 0; // get ppn and flags ko = _v2p_translate( (page_table_t*)user_pt_vbase, vpn, &ppn, &flags ); // check access rights if ( ko ) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_set_cmd() : user buffer unmapped\n"); _tty_release_lock( 0 ); return 1; } if ((flags & PTE_U) == 0) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_set_cmd() : user buffer not in user space\n"); _tty_release_lock( 0 ); return 1; } if (((flags & PTE_W) == 0 ) && (is_read == 0) ) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_set_cmd() : user buffer not writable\n"); _tty_release_lock( 0 ); return 1; } // check buffer index overflow if( buf_id > 245 ) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_set_cmd() : max number of buffers is 248\n"); _tty_release_lock( 0 ); return 1; } // buffer allocation if( vpn == vpn_min ) // first page: one single buffer { paddr = (((paddr_t)ppn) << 12) + offset; count = 0x1000 - offset; cmd_table->entry[buf_id].dba = (unsigned int)(paddr); cmd_table->entry[buf_id].dbau = (unsigned int)(paddr >> 32); cmd_table->entry[buf_id].dbc = count; #if GIET_DEBUG_HBA_DRIVER _puts("\n- buf_index = "); _putd( buf_id ); _puts(" / paddr = "); _putl( paddr ); _puts(" / count = "); _putd( count ); _puts("\n"); #endif buf_id++; } else if( vpn == vpn_max ) // last page: one single buffer { paddr = (((paddr_t)ppn) << 12); count = offset_last; cmd_table->entry[buf_id].dba = (unsigned int)(paddr); cmd_table->entry[buf_id].dbau = (unsigned int)(paddr >> 32); cmd_table->entry[buf_id].dbc = count; #if GIET_DEBUG_HBA_DRIVER _puts("\n- buf_index = "); _putd( buf_id ); _puts(" / paddr = "); _putl( paddr ); _puts(" / count = "); _putd( count ); _puts("\n"); #endif buf_id++; } else if( offset ) // midle page and offset != 0: two buffers { paddr = (((paddr_t)ppn) << 12); count = offset; cmd_table->entry[buf_id].dba = (unsigned int)(paddr); cmd_table->entry[buf_id].dbau = (unsigned int)(paddr >> 32); cmd_table->entry[buf_id].dbc = count; #if GIET_DEBUG_HBA_DRIVER _puts("\n- buf_index = "); _putd( buf_id ); _puts(" / paddr = "); _putl( paddr ); _puts(" / count = "); _putd( count ); _puts("\n"); #endif buf_id++; paddr = (((paddr_t)ppn) << 12) + offset; count = 0x1000 - offset; cmd_table->entry[buf_id].dba = (unsigned int)(paddr); cmd_table->entry[buf_id].dbau = (unsigned int)(paddr >> 32); cmd_table->entry[buf_id].dbc = count; #if GIET_DEBUG_HBA_DRIVER _puts("\n- buf_index = "); _putd( buf_id ); _puts(" / paddr = "); _putl( paddr ); _puts(" / count = "); _putd( count ); _puts("\n"); #endif buf_id++; } else // middle page and offset == 0: one buffer { paddr = (((paddr_t)ppn) << 12); count = 0x1000; cmd_table->entry[buf_id].dba = (unsigned int)(paddr); cmd_table->entry[buf_id].dbau = (unsigned int)(paddr >> 32); cmd_table->entry[buf_id].dbc = count; #if GIET_DEBUG_HBA_DRIVER _puts("\n- buf_index = "); _putd( buf_id ); _puts(" / paddr = "); _putl( paddr ); _puts(" / count = "); _putd( count ); _puts("\n"); #endif buf_id++; } } // initialize command table header cmd_table->header.lba0 = (char)lba; cmd_table->header.lba1 = (char)(lba>>8); cmd_table->header.lba2 = (char)(lba>>16); cmd_table->header.lba3 = (char)(lba>>24); // initialise command descriptor cmd_desc->prdtl[0] = (unsigned char)(buf_id); cmd_desc->prdtl[1] = (unsigned char)(buf_id>>8); cmd_desc->ctba = (unsigned int)(hba_cmd_table_paddr[channel_id][cmd_id]); cmd_desc->ctbau = (unsigned int)(hba_cmd_table_paddr[channel_id][cmd_id]>>32); if( is_read ) cmd_desc->flag[0] = 0x00; else cmd_desc->flag[0] = 0x40; // update PXCI register hba_address[HBA_PXCI] = (1< 0 if error. /////////////////////////////////////////////////////////////////// unsigned int _hba_write( unsigned int mode, unsigned int lba, void* buffer, unsigned int count ) { return _hba_cmd_set( 0, lba, (unsigned int)buffer, count ); } /////////////////////////////////////////////////////////////////// // Register a read command in Command List and Command Table // for a single buffer. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////// unsigned int _hba_read( unsigned int mode, unsigned int lba, void* buffer, unsigned int count ) { return _hba_cmd_set( 1, lba, (unsigned int)buffer, count ); } ////////////////////////////////////////////////////////////////// // This function initializes for a given channel // - the HBA hardware registers, // - the command list pointer, // - the command lists physical addresse, // - the command tables physical addresses array, ////////////////////////////////////////////////////////////////// unsigned int _hba_init( unsigned int channel ) { unsigned int ppn; unsigned int flags; unsigned int fail; unsigned int vbase; unsigned int c; // c == command index // get page_table pointer unsigned int pt = _get_context_slot(CTX_PTAB_ID); // HBA registers unsigned int* hba_address; hba_address = (unsigned int*)&seg_ioc_base + HBA_SPAN * channel; hba_address[HBA_PXCLB] = (unsigned int)(&hba_cmd_list[channel]); hba_address[HBA_PXCLBU] = 0; hba_address[HBA_PXIE] = 0x40000001; hba_address[HBA_PXIS] = 0; hba_address[HBA_PXCI] = 0; hba_address[HBA_PXCMD] = 1; // command list pointer hba_cmd_slot[channel] = 0; // Command list physical addresse vbase = (unsigned int)(&hba_cmd_list[channel]); fail = _v2p_translate( (page_table_t*)pt, vbase>>12, &ppn, &flags ); if ( fail ) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_init() : command list unmapped\n"); _tty_release_lock( 0 ); return 1; } hba_cmd_list_paddr[channel] = ((paddr_t)ppn) | (vbase & 0xFFF); // Command tables physical addresses for( c=0 ; c<32 ; c++ ) { vbase = (unsigned int)(&hba_cmd_table[channel][c]); fail = _v2p_translate( (page_table_t*)pt, vbase>>12, &ppn, &flags ); if ( fail ) { _tty_get_lock( 0 ); _puts("[GIET ERROR] in _hba_init() : command table unmapped\n"); _tty_release_lock( 0 ); return 1; } hba_cmd_table_paddr[channel][c] = ((paddr_t)ppn) | (vbase & 0xFFF); } return 0; } /////////////////////////////////////////////////////////////////////////////// // _hba_get_block_size() // This function returns the block_size of HBA controller /////////////////////////////////////////////////////////////////////////////// unsigned int _hba_get_block_size() { // TODO The block size must be obtained from the hardware... return 512; } // 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