/////////////////////////////////////////////////////////////////////////////////// // File : hba_driver.c // Date : 23/11/2013 // Author : alain greiner // Copyright (c) UPMC-LIP6 /////////////////////////////////////////////////////////////////////////////////// // Implementation notes: // 1. In order to share code, the two _hba_read() and _hba_write() functions // call the same _hba_set_cmd() function. // 2. All accesses to HBA registers are done by the two // _hba_set_register() and _hba_get_register() low-level functions, // that are handling virtual / physical extended addressing. /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #if !defined( NB_IOC_CHANNELS ) # error: You must define NB_IOC_CHANNELS in the hard_config.h file #endif #if ( NB_IOC_CHANNELS > 8 ) # error: NB_IOC_CHANNELS cannot be larger than 8 #endif #define in_unckdata __attribute__((section (".unckdata"))) ////////////////////////////////////////////////////////////////// // Global variables ////////////////////////////////////////////////////////////////// // command list array (one per channel) hba_cmd_list_t hba_cmd_list[NB_IOC_CHANNELS] __attribute__((aligned(0x1000))); // command tables array (32 command tables per channel) hba_cmd_table_t hba_cmd_table[NB_IOC_CHANNELS][32] __attribute__((aligned(0x1000))); // command list physical addresses array (one per channel) paddr_t hba_cmd_list_paddr[NB_IOC_CHANNELS]; // command tables physical addresses array (32 command tables per channel) paddr_t hba_cmd_table_paddr[NB_IOC_CHANNELS][32]; // command list pointer array (one per channel) unsigned int hba_cmd_slot[NB_IOC_CHANNELS]; ////////////////////////////////////////////////////////////////////////////// // This low level function returns the value of register (channel / index) ////////////////////////////////////////////////////////////////////////////// unsigned int _hba_get_register( unsigned int channel, unsigned int index ) { unsigned int* vaddr = (unsigned int*)SEG_IOC_BASE + channel*HBA_SPAN + index; return _io_extended_read( vaddr ); } ////////////////////////////////////////////////////////////////////////////// // This low level function set a new value in register (channel / index) ////////////////////////////////////////////////////////////////////////////// void _hba_set_register( unsigned int channel, unsigned int index, unsigned int value ) { unsigned int* vaddr = (unsigned int*)SEG_IOC_BASE + channel*HBA_SPAN + index; _io_extended_write( vaddr, value ); } /////////////////////////////////////////////////////////////////////////////// // 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, -1 if error /////////////////////////////////////////////////////////////////////////////// unsigned int _hba_cmd_set( unsigned int channel, // channel index unsigned int is_read, // to memory unsigned int lba, // logic block address paddr_t buffer, // buffer physical address unsigned int count ) // number of blocks { unsigned int block_size; // defined by the block device (bytes) unsigned int pxci; // command list status unsigned int cmd_id; // command index in command list 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( buffer & (block_size-1) ) { _puts("\n[GIET ERROR] in _hba_set_cmd() : user buffer not block aligned\n"); return -1; } // get command list status from PXCI register pxci = _hba_get_register( channel, HBA_PXCI ); // get command index and return error if command list full cmd_id = hba_cmd_slot[channel]; if( pxci & (1<entry[0].dba = (unsigned int)(buffer); cmd_table->entry[0].dbau = (unsigned int)(buffer >> 32); cmd_table->entry[0].dbc = count * block_size; // 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); cmd_table->header.lba4 = 0; cmd_table->header.lba5 = 0; // initialise command descriptor cmd_desc->prdtl[0] = 1; cmd_desc->prdtl[1] = 0; cmd_desc->ctba = (unsigned int)(hba_cmd_table_paddr[channel][cmd_id]); cmd_desc->ctbau = (unsigned int)(hba_cmd_table_paddr[channel][cmd_id]>>32); if( is_read ) cmd_desc->flag[0] = 0x00; else cmd_desc->flag[0] = 0x40; // update PXCI register _hba_set_register( channel, HBA_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 _v2p_translate( (page_table_t*)user_pt_vbase, vpn, &ppn, &flags ); // check access rights if ((flags & PTE_U) == 0) { _puts("[GIET ERROR] in _hba_set_cmd() : user buffer not in user space\n"); return -1; } if (((flags & PTE_W) == 0 ) && (is_read == 0) ) { _puts("[GIET ERROR] in _hba_set_cmd() : user buffer not writable\n"); return -1; } // check buffer index overflow if( buf_id > 245 ) { _puts("[GIET ERROR] in _hba_set_cmd() : max number of buffers is 248\n"); 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; 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; 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; 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; 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; buf_id++; } } */ ////////////////////////////////////////////// unsigned int _hba_init( unsigned int channel ) { unsigned int ppn; unsigned int flags; unsigned int vbase; unsigned int c; // c == command index // get page_table pointer unsigned int pt = _get_context_slot(CTX_PTAB_ID); // HBA registers TODO: ne faut_il pas un V2P pour PXCLB/PXCLBU ? (AG) _hba_set_register( channel, HBA_PXCLB , (unsigned int)&hba_cmd_list[channel] ); _hba_set_register( channel, HBA_PXCLBU, 0 ); _hba_set_register( channel, HBA_PXIE , 0x40000001 ); _hba_set_register( channel, HBA_PXIS , 0 ); _hba_set_register( channel, HBA_PXCI , 0 ); _hba_set_register( channel, HBA_PXCMD , 1 ); // command list pointer hba_cmd_slot[channel] = 0; // Command list physical addresse vbase = (unsigned int)(&hba_cmd_list[channel]); _v2p_translate( (page_table_t*)pt, vbase>>12, &ppn, &flags ); 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]); _v2p_translate( (page_table_t*)pt, vbase>>12, &ppn, &flags ); hba_cmd_table_paddr[channel][c] = ((paddr_t)ppn) | (vbase & 0xFFF); } return 0; } /////////////////////////////////////////////// unsigned int _hba_write( unsigned int channel, unsigned int mode, unsigned int lba, paddr_t buffer, unsigned int count ) { return _hba_cmd_set( channel, 0, // write lba, buffer, count ); } ////////////////////////////////////////////// unsigned int _hba_read( unsigned int channel, unsigned int mode, unsigned int lba, paddr_t buffer, unsigned int count ) { return _hba_cmd_set( channel, 1, // read lba, buffer, count ); } ////////////////////////////////// unsigned int _hba_get_block_size() { // TODO The block size must be obtained from the hardware... return 512; } //////////////////////////////////////////////////// unsigned int _hba_get_status( unsigned int channel ) { if( channel >= NB_IOC_CHANNELS ) { _puts("\n[GIET ERROR] in _hba_get_status() : illegal channel\n"); _exit(); } // get HBA_PXIS value unsigned int status = _hba_get_register( channel, HBA_PXIS ); // reset HBA_PXIS _hba_set_register( channel, HBA_PXIS, 0 ); return status; } // 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