/////////////////////////////////////////////////////////////////////////////////// // File : bdv_driver.c // Date : 23/05/2013 // Author : alain greiner // Maintainer: cesar fuguet // Copyright (c) UPMC-LIP6 /////////////////////////////////////////////////////////////////////////////////// // The bdv_driver.c and bdv_driver.h files are part ot the GIET-VM kernel. // This driver supports the SocLib vci_block_device component, that is // a single channel, block oriented, external storage contrĂ´ler. // // The _bdv_read() and _bdv_write() functions are always blocking. // They can be called in 3 modes: // // - In BOOT mode, these functions use a polling policy on the BDV STATUS // register to detect transfer completion, as interrupts are not activated. // This mode is used by the boot code to load the map.bin file into memory // (before MMU activation), or to load the .elf files (after MMU activation). // // - In KERNEL mode, these functions 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, for an "open" system call. // // - In USER mode, these functions 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 for a "read/write" system call. // // As the BDV component can be used by several programs running in parallel, // the _bdv_lock variable guaranties exclusive access to the device. The // _bdv_read() and _bdv_write() functions use atomic LL/SC to get the lock. // // 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" must be defined in giet_vsegs.ld file. /////////////////////////////////////////////////////////////////////////////////// // Implementation notes: // // 1. In order to share code, the two _bdv_read() and _bdv_write() functions // call the same _bdv_access() function. // // 2. All accesses to BDV registers are done by the two // _bdv_set_register() and _bdv_get_register() low-level functions, // that are handling virtual / physical extended addressing. /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // BDV global variables /////////////////////////////////////////////////////////////////////////////// #define in_unckdata __attribute__((section (".unckdata"))) in_unckdata unsigned int _bdv_lock = 0; in_unckdata volatile unsigned int _bdv_status = 0; in_unckdata volatile unsigned int _bdv_gtid; /////////////////////////////////////////////////////////////////////////////// // This low_level function returns the value contained in register (index). /////////////////////////////////////////////////////////////////////////////// unsigned int _bdv_get_register( unsigned int index ) { unsigned int* vaddr = (unsigned int*)&seg_ioc_base + index; return _io_extended_read( vaddr ); } /////////////////////////////////////////////////////////////////////////////// // This low-level function set a new value in register (index). /////////////////////////////////////////////////////////////////////////////// void _bdv_set_register( unsigned int index, unsigned int value ) { unsigned int* vaddr = (unsigned int*)&seg_ioc_base + index; _io_extended_write( vaddr, value ); } /////////////////////////////////////////////////////////////////////////////// // 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 / KERNEL / USER // - lba : first block index on the external storage. // - buf_paddr : physical base address of the memory buffer. // - count : number of blocks to be transfered. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// static unsigned int _bdv_access( unsigned int to_mem, unsigned int mode, unsigned int lba, unsigned long long buf_paddr, unsigned int count) { #if GIET_DEBUG_BDV_DRIVER unsigned int procid = _get_procid(); unsigned int cxy = procid / NB_PROCS_MAX; unsigned int lpid = procid % NB_PROCS_MAX; unsigned int x = cxy >> Y_WIDTH; unsigned int y = cxy & ((1<>32) ); _bdv_set_register( BLOCK_DEVICE_COUNT , count ); _bdv_set_register( BLOCK_DEVICE_LBA , lba ); // In BOOT mode, we launch transfer, and poll the BDV_STATUS // register because IRQs are masked. if ( mode == IOC_BOOT_MODE ) { // Launch transfert if (to_mem == 0) _bdv_set_register( BLOCK_DEVICE_OP, BLOCK_DEVICE_WRITE ); else _bdv_set_register( BLOCK_DEVICE_OP, BLOCK_DEVICE_READ ); unsigned int status; do { status = _bdv_get_register( BLOCK_DEVICE_STATUS ); #if GIET_DEBUG_BDV_DRIVER _printf("\n[BDV DEBUG] _bdv_access() : ... waiting on BDV_STATUS register ...\n"); #endif } while( (status != BLOCK_DEVICE_READ_SUCCESS) && (status != BLOCK_DEVICE_READ_ERROR) && (status != BLOCK_DEVICE_WRITE_SUCCESS) && (status != BLOCK_DEVICE_WRITE_ERROR) ); // busy waiting // analyse status error = ( (status == BLOCK_DEVICE_READ_ERROR) || (status == BLOCK_DEVICE_WRITE_ERROR) ); // release lock _release_lock(&_bdv_lock); } // in USER or KERNEL mode, we deschedule the task. // When the task is rescheduled, we check the _bdv_status variable, // and release the lock. // We need a critical section, because we must reset the RUN bit // before to launch the transfer, and we don't want to be descheduled // between these two operations. else { unsigned int save_sr; unsigned int ltid = _get_current_task_id(); unsigned int gpid = _get_procid(); // activates BDV interrupts _bdv_set_register( BLOCK_DEVICE_IRQ_ENABLE, 1 ); // set the _bdv_status variable _bdv_status = BLOCK_DEVICE_BUSY; // enters critical section _it_disable( &save_sr ); // set _bdv_gtid and reset runnable _bdv_gtid = (gpid<<16) + ltid; _set_task_slot( gpid, ltid, CTX_RUN_ID, 0 ); // launch transfer if (to_mem == 0) _bdv_set_register( BLOCK_DEVICE_OP, BLOCK_DEVICE_WRITE ); else _bdv_set_register( BLOCK_DEVICE_OP, BLOCK_DEVICE_READ ); // deschedule task _ctx_switch(); // restore SR _it_restore( &save_sr ); // analyse status error = ( (_bdv_status == BLOCK_DEVICE_READ_ERROR) || (_bdv_status == BLOCK_DEVICE_WRITE_ERROR) ); // reset _bdv_status and release lock _bdv_status = BLOCK_DEVICE_IDLE; _release_lock(&_bdv_lock); } #if GIET_DEBUG_BDV_DRIVER _printf("\n[BDV DEBUG] Processor[%d,%d,%d] exit _bdv_access() at cycle %d\n", x, y, lpid, _get_proctime() ); #endif return error; } // end _bdv_access() /////////////////////////////////////////////////////////////////////////////// // This function cheks block size, and desactivates the interrupts. // Return 0 for success, > 0 if error /////////////////////////////////////////////////////////////////////////////// unsigned int _bdv_init() { if ( _bdv_get_register( BLOCK_DEVICE_BLOCK_SIZE ) != 512 ) { _printf("\n[GIET ERROR] in _bdv_init() : block size must be 512 bytes\n"); return 1; } _bdv_set_register( BLOCK_DEVICE_IRQ_ENABLE, 0 ); return 0; } /////////////////////////////////////////////////////////////////////////////// // Transfer data from the block device to a memory buffer. // - mode : BOOT / 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 _bdv_read( unsigned int mode, unsigned int lba, unsigned long long buffer, unsigned int count) { return _bdv_access( 1, // read access mode, lba, buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // Transfer data from a memory buffer to the block device. // - mode : BOOT / 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 _bdv_write( unsigned int mode, unsigned int lba, unsigned long long buffer, unsigned int count ) { return _bdv_access( 0, // write access mode, lba, buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // Returns device status. /////////////////////////////////////////////////////////////////////////////// unsigned int _bdv_get_status() { return _bdv_get_register( BLOCK_DEVICE_STATUS ); } /////////////////////////////////////////////////////////////////////////////// // Returns block size. /////////////////////////////////////////////////////////////////////////////// unsigned int _bdv_get_block_size() { return _bdv_get_register( BLOCK_DEVICE_BLOCK_SIZE ); } /////////////////////////////////////////////////////////////////////////////////// // This ISR save the status, acknowledge the IRQ, // and activates the task waiting on IO transfer. // It can be an HWI or a SWI. // // TODO the _set_task_slot access should be replaced by an atomic LL/SC // when the CTX_RUN bool will be replaced by a bit_vector. /////////////////////////////////////////////////////////////////////////////////// void _bdv_isr( unsigned int irq_type, // HWI / WTI unsigned int irq_id, // index returned by ICU unsigned int channel ) // unused { unsigned int procid = _get_procid(); unsigned int cluster_xy = procid / NB_PROCS_MAX; unsigned int lpid = procid % NB_PROCS_MAX; // get BDV status (and reset IRQ) unsigned int status = _bdv_get_register( BLOCK_DEVICE_STATUS ); // check status: does nothing if IDLE or BUSY if ( (status == BLOCK_DEVICE_IDLE) || (status == BLOCK_DEVICE_BUSY) ) return; // reset WTI in XCU if WTI type if ( irq_type == IRQ_TYPE_WTI ) { unsigned int value; _xcu_get_wti_value( cluster_xy, irq_id, &value ); } // save status in kernel buffer _bdv_status _bdv_status = status; // identify task waiting on BDV unsigned int rprocid = _bdv_gtid>>16; unsigned int ltid = _bdv_gtid & 0xFFFF; unsigned int remote_xy = rprocid / NB_PROCS_MAX; // re-activates sleeping task _set_task_slot( rprocid, // global processor index ltid, // local task index on processor CTX_RUN_ID, // CTX_RUN slot 1 ); // running // requires a context switch for remote processor running the waiting task _xcu_send_wti( remote_xy, // cluster index lpid, // local processor index 0 ); // don't force context switch if not idle #if GIET_DEBUG_IRQS // we don't take the TTY lock to avoid deadlock unsigned int x = cluster_xy >> Y_WIDTH; unsigned int y = cluster_xy & ((1<> Y_WIDTH; unsigned int ry = remote_xy & ((1<