/////////////////////////////////////////////////////////////////////////////////// // File : drivers.c // Date : 01/04/2012 // Author : alain greiner // Copyright (c) UPMC-LIP6 /////////////////////////////////////////////////////////////////////////////////// // The drivers.c and drivers.h files are part ot the GIET-VM nano kernel. // They contains the drivers for the peripherals available in the SoCLib library: // - vci_multi_tty // - vci_multi_timer // - vci_multi_dma // - vci_multi_icu // - vci_xicu & vci_multi_icu // - vci_gcd // - vci_frame_buffer // - vci_block_device // // The following global parameters must be defined in the giet_config.h file: // - CLUSTER_SIZE // - NB_CLUSTERS // - NB_PROCS_MAX // - NB_TIMERS_MAX // - NB_DMAS_MAX // - NB_TTYS // // The following virtual base addresses must be defined in the giet.ld file: // - seg_icu_base // - seg_tim_base // - seg_tty_base // - seg_gcd_base // - seg_dma_base // - seg_fbf_base // - seg_ioc_base // As some peripherals can be replicated in the clusters (ICU, TIMER, DMA) // These addresses must be completed by an offset depending on the cluster index // full_base_address = seg_***_base + cluster_id * CLUSTER_SIZE /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #if !defined(NB_CLUSTERS) # error: You must define NB_CLUSTERS in the configs file #endif #if !defined(NB_PROCS_MAX) # error: You must define NB_PROCS_MAX in the configs file #endif #if (NB_PROCS_MAX > 8) # error: NB_PROCS_MAX cannot be larger than 8! #endif #if !defined(CLUSTER_SIZE) # error: You must define CLUSTER_SIZE in the configs file #endif #if !defined(NB_TTYS) # error: You must define NB_TTYS in the configs file #endif #if (NB_TTYS < 1) # error: NB_TTYS cannot be smaller than 1! #endif #if !defined(NB_DMAS_MAX) #define NB_DMAS_MAX 0 #endif #if !defined(NB_TIMERS_MAX) #define NB_TIMERS_MAX 0 #endif #if ( (NB_TIMERS_MAX + NB_PROCS_MAX) > 32 ) # error: NB_TIMERS_MAX + NB_PROCS_MAX cannot be larger than 32 #endif #if !defined(NB_IOCS) # error: You must define NB_IOCS in the configs file #endif #if ( NB_IOCS > 1 ) # error: NB_IOCS cannot be larger than 1 #endif #if !defined( USE_XICU ) # error: You must define USE_XICU in the configs file #endif #if !defined( IOMMU_ACTIVE ) # error: You must define IOMMU_ACTIVE in the configs file #endif #define in_unckdata __attribute__((section (".unckdata"))) ////////////////////////////////////////////////////////////////////////////// // Timers driver ////////////////////////////////////////////////////////////////////////////// // The timers can be implemented in a vci_timer component or in a vci_xicu // component (depending on the USE_XICU parameter). // There is one timer (or xicu) component per cluster. // There is two types of timers: // - "system" timers : one per processor, used for context switch. // local_id in [0, NB_PROCS_MAX-1], // - "user" timers : requested by the task in the mapping_info data structure. // local_id in [NB_PROC_MAX, NB_PROCS_MAX + NB_TIMERS_MAX - 1] // For each user timer, the timer_id is stored in the context of the task. // The global index is cluster_id * (NB_PROCS_MAX+NB_TIMERS_MAX) + local_id ////////////////////////////////////////////////////////////////////////////// // User Timer signaling variables #if (NB_TIMERS_MAX > 0) in_unckdata volatile unsigned char _user_timer_event[NB_CLUSTERS*NB_TIMERS_MAX] = { [0 ... ((NB_CLUSTERS*NB_TIMERS_MAX)-1)] = 0 }; #endif ////////////////////////////////////////////////////////////////////////////// // _timer_start() // This function activates a timer in the vci_timer (or vci_xicu) component // by writing in the proper register the period value. // It can be used by both the kernel to initialise a "system" timer, // or by a task (through a system call) to configure an "user" timer. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////// unsigned int _timer_start( unsigned int cluster_id, unsigned int local_id, unsigned int period ) { // parameters checking if ( cluster_id >= NB_CLUSTERS) return 1; if ( local_id >= NB_TIMERS_MAX + NB_PROCS_MAX ) return 1; #if USE_XICU unsigned int* timer_address = (unsigned int*)((char*)&seg_icu_base + (cluster_id * CLUSTER_SIZE) ); timer_address[XICU_REG(XICU_PTI_PER, local_id)] = period; #else unsigned int* timer_address = (unsigned int*)((char*)&seg_tim_base + (cluster_id * CLUSTER_SIZE) ); timer_address[local_id * TIMER_SPAN + TIMER_PERIOD] = period; timer_address[local_id * TIMER_SPAN + TIMER_MODE] = 0x3; #endif return 0; } ////////////////////////////////////////////////////////////////////////////// // _timer_stop() // This function desactivates a timer in the vci_timer (or vci_xicu) component // by writing in the proper register. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////// unsigned int _timer_stop( unsigned int cluster_id, unsigned int local_id ) { // parameters checking if ( cluster_id >= NB_CLUSTERS) return 1; if ( local_id >= NB_TIMERS_MAX + NB_PROCS_MAX ) return 1; #if USE_XICU unsigned int* timer_address = (unsigned int*)((char*)&seg_icu_base + (cluster_id * CLUSTER_SIZE) ); timer_address[XICU_REG(XICU_PTI_PER, local_id)] = 0; #else unsigned int* timer_address = (unsigned int*)((char*)&seg_tim_base + (cluster_id * CLUSTER_SIZE) ); timer_address[local_id * TIMER_SPAN + TIMER_MODE] = 0; #endif return 0; } ////////////////////////////////////////////////////////////////////////////// // _timer_reset_irq() // This function acknowlegge a timer interrupt in the vci_timer (or vci_xicu) // component by reading/writing in the proper register. // It can be used by both the isr_switch() for a "system" timer, // or by the _isr_timer() for an "user" timer. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////// unsigned int _timer_reset_irq( unsigned int cluster_id, unsigned int local_id ) { // parameters checking if ( cluster_id >= NB_CLUSTERS) return 1; if ( local_id >= NB_TIMERS_MAX + NB_PROCS_MAX ) return 1; #if USE_XICU unsigned int* timer_address = (unsigned int*)((char*)&seg_icu_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); unsigned int bloup = timer_address[XICU_REG(XICU_PTI_ACK, local_id)]; bloup++; // to avoid a warning #else unsigned int* timer_address = (unsigned int*)((char*)&seg_tim_base + (cluster_id * CLUSTER_SIZE) ); timer_address[local_id * TIMER_SPAN + TIMER_RESETIRQ] = 0; #endif return 0; } ///////////////////////////////////////////////////////////////////////////////// // VciMultiTty driver ///////////////////////////////////////////////////////////////////////////////// // There is only one multi_tty controler in the architecture. // The total number of TTYs is defined by the configuration parameter NB_TTYS. // The "system" terminal is TTY[0]. // The "user" TTYs are allocated to applications by the GIET in the boot phase, // as defined in the mapping_info data structure. The corresponding tty_id must // be stored in the context of the task by the boot code. // The TTY address is : seg_tty_base + tty_id*TTY_SPAN ///////////////////////////////////////////////////////////////////////////////// // TTY variables in_unckdata volatile unsigned char _tty_get_buf[NB_TTYS]; in_unckdata volatile unsigned char _tty_get_full[NB_TTYS] = { [0 ... NB_TTYS-1] = 0 }; in_unckdata unsigned int _tty_put_lock = 0; // protect kernel TTY[0] //////////////////////////////////////////////////////////////////////////////// // _tty_error() //////////////////////////////////////////////////////////////////////////////// void _tty_error( unsigned int task_id ) { unsigned int proc_id = _procid(); _get_lock(&_tty_put_lock); _puts("\n[GIET ERROR] TTY index too large for task "); _putd( task_id ); _puts(" on processor "); _putd( proc_id ); _puts("\n"); _release_lock(&_tty_put_lock); } ///////////////////////////////////////////////////////////////////////////////// // _tty_write() // Write one or several characters directly from a fixed-length user buffer to // the TTY_WRITE register of the TTY controler. // It doesn't use the TTY_PUT_IRQ interrupt and the associated kernel buffer. // This is a non blocking call: it tests the TTY_STATUS register, and stops // the transfer as soon as the TTY_STATUS[WRITE] bit is set. // The function returns the number of characters that have been written. ///////////////////////////////////////////////////////////////////////////////// unsigned int _tty_write( const char *buffer, unsigned int length) { unsigned int nwritten; unsigned int task_id = _get_current_task_id(); unsigned int tty_id = _get_context_slot(task_id, CTX_TTY_ID); if ( tty_id >= NB_TTYS ) { _tty_error( task_id ); return 0; } unsigned int* tty_address = (unsigned int*) &seg_tty_base; for (nwritten = 0; nwritten < length; nwritten++) { // check tty's status if ((tty_address[tty_id*TTY_SPAN + TTY_STATUS] & 0x2) == 0x2) break; else // write character tty_address[tty_id*TTY_SPAN + TTY_WRITE] = (unsigned int)buffer[nwritten]; } return nwritten; } ////////////////////////////////////////////////////////////////////////////// // _tty_read() // This non-blocking function uses the TTY_GET_IRQ[tty_id] interrupt and // the associated kernel buffer, that has been written by the ISR. // It get the TTY terminal index from the context of the current task. // It fetches one single character from the _tty_get_buf[tty_id] kernel // buffer, writes this character to the user buffer, and resets the // _tty_get_full[tty_id] buffer. // The length argument is not used. // Returns 0 if the kernel buffer is empty, 1 if the buffer is full. ////////////////////////////////////////////////////////////////////////////// unsigned int _tty_read( char *buffer, unsigned int length) { unsigned int task_id = _get_current_task_id(); unsigned int tty_id = _get_context_slot(task_id, CTX_TTY_ID); if ( tty_id >= NB_TTYS ) { _tty_error( task_id ); return 0; } if (_tty_get_full[tty_id] == 0) { return 0; } else { *buffer = _tty_get_buf[tty_id]; _tty_get_full[tty_id] = 0; return 1; } } //////////////////////////////////////////////////////////////////////////////// // _tty_get_char() // This function is used by the _isr_tty to read a character in the TTY // terminal defined by the tty_id argument. The character is stored // in requested buffer, and the IRQ is acknowledged. // Returns 0 if success, 1 if tty_id too large. //////////////////////////////////////////////////////////////////////////////// unsigned int _tty_get_char( unsigned int tty_id, unsigned char* buffer ) { // checking argument if ( tty_id >= NB_TTYS ) return 1; // compute terminal base address unsigned int *tty_address = (unsigned int*) &seg_tty_base; *buffer = (unsigned char)tty_address[tty_id*TTY_SPAN + TTY_READ]; return 0; } //////////////////////////////////////////////////////////////////////////////// // VciMultiIcu and VciXicu drivers //////////////////////////////////////////////////////////////////////////////// // There is one vci_multi_icu (or vci_xicu) component per cluster, // and the number of independant ICUs is equal to NB_PROCS_MAX, // because there is one private interrupr controler per processor. //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // _icu_set_mask() // This function can be used with both the vci_xicu & vci_multi_icu components. // It set the mask register for the ICU channel identified by the cluster index // and the processor index: all '1' bits are set / all '0' bits are not modified. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _icu_set_mask( unsigned int cluster_id, unsigned int proc_id, unsigned int value, unsigned int is_timer ) { // parameters checking if ( cluster_id >= NB_CLUSTERS) return 1; if ( proc_id >= NB_PROCS_MAX ) return 1; unsigned int* icu_address = (unsigned int*)( (char*)&seg_icu_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); #if USE_XICU if ( is_timer ) icu_address[XICU_REG(XICU_MSK_PTI_ENABLE, proc_id)] = value; else icu_address[XICU_REG(XICU_MSK_HWI_ENABLE, proc_id)] = value; #else icu_address[proc_id * ICU_SPAN + ICU_MASK_SET] = value; #endif return 0; } //////////////////////////////////////////////////////////////////////////////// // _icu_get_index() // This function can be used with both the vci_xicu & vci_multi_icu components. // It returns the index of the highest priority (smaller index) active HWI. // The ICU channel is identified by the cluster index and the processor index. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _icu_get_index( unsigned int cluster_id, unsigned int proc_id, unsigned int* buffer ) { // parameters checking if ( cluster_id >= NB_CLUSTERS) return 1; if ( proc_id >= NB_PROCS_MAX ) return 1; unsigned int* icu_address = (unsigned int*)( (char*)&seg_icu_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); #if USE_XICU unsigned int prio = icu_address[XICU_REG(XICU_PRIO, proc_id)]; unsigned int pti_ok = (prio & 0x00000001); unsigned int hwi_ok = (prio & 0x00000002); unsigned int swi_ok = (prio & 0x00000004); unsigned int pti_id = (prio & 0x00001F00) >> 8; unsigned int hwi_id = (prio & 0x001F0000) >> 16; unsigned int swi_id = (prio & 0x1F000000) >> 24; if (pti_ok) *buffer = pti_id; else if (hwi_ok) *buffer = hwi_id; else if (swi_ok) *buffer = swi_id; else *buffer = 32; #else *buffer = icu_address[proc_id * ICU_SPAN + ICU_IT_VECTOR]; #endif return 0; } //////////////////////////////////////////////////////////////////////////////// // VciGcd driver //////////////////////////////////////////////////////////////////////////////// // The Greater Dommon Divider is a -very- simple hardware coprocessor // performing the computation of the GCD of two 32 bits integers. // It has no DMA capability. //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // _gcd_write() // Write a 32-bit word in a memory mapped register of the GCD coprocessor. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _gcd_write( unsigned int register_index, unsigned int value) { // parameters checking if (register_index >= GCD_END) return 1; unsigned int* gcd_address = (unsigned int*) &seg_gcd_base; gcd_address[register_index] = value; // write word return 0; } //////////////////////////////////////////////////////////////////////////////// // _gcd_read() // Read a 32-bit word in a memory mapped register of the GCD coprocessor. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _gcd_read( unsigned int register_index, unsigned int *buffer) { // parameters checking if (register_index >= GCD_END) return 1; unsigned int* gcd_address = (unsigned int*) &seg_gcd_base; *buffer = gcd_address[register_index]; // read word return 0; } //////////////////////////////////////////////////////////////////////////////// // VciBlockDevice driver //////////////////////////////////////////////////////////////////////////////// // The VciBlockDevice is a single channel external storage contrĂ´ler. // // 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 user buffer must be word aligned, // - The user buffer must be mapped in user address space, // - The user 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. // // 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. // and set _ioc_lock to a non zero value. The _ioc_write() and _ioc_read() // functions are blocking, polling the _ioc_lock variable until the device is // available. // When the tranfer is completed, the ISR routine activated by the IOC IRQ // set the _ioc_done variable to a non-zero value. Possible address errors // detected by the IOC peripheral are reported by the ISR in the _ioc_status // variable. // The _ioc_completed() function is polling the _ioc_done variable, waiting for // transfer completion. When the completion is signaled, the _ioc_completed() // function reset the _ioc_done variable to zero, and releases the _ioc_lock // variable. // // In a multi-processing environment, this polling policy should be replaced by // a descheduling policy for the requesting process. /////////////////////////////////////////////////////////////////////////////// // IOC global variables in_unckdata volatile unsigned int _ioc_status = 0; in_unckdata volatile unsigned int _ioc_done = 0; in_unckdata unsigned int _ioc_lock = 0; in_unckdata unsigned int _ioc_iommu_ix1 = 0; in_unckdata unsigned int _ioc_iommu_npages; /////////////////////////////////////////////////////////////////////////////// // _ioc_access() // 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 // - lba : first block index on the external storage. // - user_vaddr : virtual base address of the memory buffer. // - count : number of blocks to be transfered. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_access( unsigned int to_mem, unsigned int lba, unsigned int user_vaddr, unsigned int count ) { unsigned int user_vpn_min; // first virtuel page index in user space unsigned int user_vpn_max; // last virtual page index in user space unsigned int vpn; // current virtual page index in user space unsigned int ppn; // physical page number unsigned int flags; // page protection flags unsigned int ix2; // page index in IOMMU PT1 page table unsigned int addr; // buffer address for IOC peripheral unsigned int ppn_first; // first physical page number for user buffer // check buffer alignment if ( (unsigned int)user_vaddr & 0x3 ) return 1; unsigned int* ioc_address = (unsigned int*) &seg_ioc_base ; unsigned int block_size = ioc_address[BLOCK_DEVICE_BLOCK_SIZE]; unsigned int length = count*block_size; // get user space page table virtual address unsigned int task_id = _get_current_task_id(); unsigned int user_pt_vbase = _get_context_slot( task_id, CTX_PTAB_ID ); user_vpn_min = user_vaddr >> 12; user_vpn_max = (user_vaddr + length - 1) >> 12; ix2 = 0; // loop on all virtual pages covering the user buffer for ( vpn = user_vpn_min ; vpn <= user_vpn_max ; vpn++ ) { // get ppn and flags for each vpn unsigned int ko = _v2p_translate( (page_table_t*)user_pt_vbase, vpn, &ppn, &flags ); // check access rights if ( ko ) return 2; // unmapped if ( (flags & PTE_U) == 0 ) return 3; // not in user space if ( ( (flags & PTE_W) == 0 ) && to_mem ) return 4; // not writable // save first ppn value if ( ix2 == 0 ) ppn_first = ppn; if ( IOMMU_ACTIVE ) // the user buffer must be remapped in the I/0 space { // check buffer length < 2 Mbytes if ( ix2 > 511 ) return 2; // 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 } else // no IOMMU : check that physical pages are contiguous { if ( (ppn - ppn_first) != ix2 ) return 5; // split physical buffer } // increment page index ix2++; } // end for vpn // register the number of pages to be unmapped _ioc_iommu_npages = (user_vpn_max - user_vpn_min) + 1; // invalidate data cache in case of memory write if ( to_mem ) _dcache_buf_invalidate( (void*)user_vaddr, length ); // compute buffer base address for IOC depending on IOMMU activation if ( IOMMU_ACTIVE ) addr = (_ioc_iommu_ix1) << 21 | (user_vaddr & 0xFFF); else addr = (ppn_first << 12) | (user_vaddr & 0xFFF); // get the lock on ioc device _get_lock( &_ioc_lock ); // peripheral configuration ioc_address[BLOCK_DEVICE_BUFFER] = addr; ioc_address[BLOCK_DEVICE_COUNT] = count; ioc_address[BLOCK_DEVICE_LBA] = lba; if ( to_mem == 0 ) ioc_address[BLOCK_DEVICE_OP] = BLOCK_DEVICE_WRITE; else ioc_address[BLOCK_DEVICE_OP] = BLOCK_DEVICE_READ; return 0; } ///////////////////////////////////////////////////////////////////////////////// // _ioc_completed() // // This function checks completion of an I/O transfer and reports errors. // As it is a blocking call, the processor is stalled. // If the virtual memory is activated, the pages mapped in the I/O virtual // space are unmapped, and the IOB TLB is cleared. // Returns 0 if success, > 0 if error. ///////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_completed() { unsigned int ret; unsigned int ix2; // busy waiting while (_ioc_done == 0) asm volatile("nop"); // unmap the buffer from IOMMU page table if IOMMU is activated if ( IOMMU_ACTIVE ) { unsigned int* iob_address = (unsigned int*) &seg_iob_base; for ( ix2 = 0 ; ix2 < _ioc_iommu_npages ; ix2++ ) { // unmap the page in IOMMU page table _iommu_inval_pte2( _ioc_iommu_ix1, // PT1 index ix2 ); // PT2 index // clear IOMMU TLB iob_address[IOB_INVAL_PTE] = (_ioc_iommu_ix1 << 21) | (ix2 << 12); } } // test IOC status if ((_ioc_status != BLOCK_DEVICE_READ_SUCCESS) && (_ioc_status != BLOCK_DEVICE_WRITE_SUCCESS)) ret = 1; // error else ret = 0; // success // reset synchronization variables _ioc_lock =0; _ioc_done =0; return ret; } /////////////////////////////////////////////////////////////////////////////// // _ioc_read() // Transfer data from the block device to a memory buffer in user space. // - 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 lba, void* buffer, unsigned int count ) { return _ioc_access( 1, // read access lba, (unsigned int)buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // _ioc_write() // Transfer data from a memory buffer in user space to the block device. // - 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 lba, const void* buffer, unsigned int count ) { return _ioc_access( 0, // write access lba, (unsigned int)buffer, count ); } /////////////////////////////////////////////////////////////////////////////// // _ioc_get_status() // This function returns the transfert status, and acknowledge the IRQ. // Returns 0 if success, > 0 if error. /////////////////////////////////////////////////////////////////////////////// unsigned int _ioc_get_status(unsigned int* status) { // get IOC base address unsigned int* ioc_address = (unsigned int*) &seg_ioc_base; *status = ioc_address[BLOCK_DEVICE_STATUS]; // read status & reset IRQ return 0; } ////////////////////////////////////////////////////////////////////////////////// // VciMultiDma driver ////////////////////////////////////////////////////////////////////////////////// // The DMA controllers are physically distributed in the clusters. // There is (NB_CLUSTERS * NB_DMAS_MAX) channels, indexed by a global index: // dma_id = cluster_id * NB_DMA_MAX + loc_id // // As a DMA channel can be used by several tasks, each DMA channel is protected // by a specific lock: _dma_lock[dma_id] // The signalisation between the OS and the DMA uses the _dma_done[dma_id] // synchronisation variables (set by the ISR, and reset by the OS). // The transfer status is copied by the ISR in the _dma_status[dma_id] variables. // // These DMA channels can be used by the FB driver, or by the NIC driver. ////////////////////////////////////////////////////////////////////////////////// //+1: for the case where the NB_DMAS_MAX == 0 #if NB_DMAS_MAX > 0 in_unckdata unsigned int _dma_lock[NB_DMAS_MAX * NB_CLUSTERS] = { [0 ... ((NB_DMAS_MAX) * NB_CLUSTERS)-1] = 0 }; in_unckdata volatile unsigned int _dma_done[NB_DMAS_MAX * NB_CLUSTERS] = { [0 ... (NB_DMAS_MAX * NB_CLUSTERS)-1] = 0 }; in_unckdata volatile unsigned int _dma_status[NB_DMAS_MAX * NB_CLUSTERS]; in_unckdata unsigned int _dma_iommu_ix1 = 1; in_unckdata unsigned int _dma_iommu_npages[NB_DMAS_MAX * NB_CLUSTERS]; #endif ////////////////////////////////////////////////////////////////////////////////// // _dma_reset_irq() ////////////////////////////////////////////////////////////////////////////////// unsigned int _dma_reset_irq( unsigned int cluster_id, unsigned int channel_id ) { #if NB_DMAS_MAX > 0 // parameters checking if ( cluster_id >= NB_CLUSTERS ) return 1; if ( channel_id >= NB_DMAS_MAX ) return 1; // compute DMA base address unsigned int* dma_address = (unsigned int*)( (char*)&seg_dma_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); dma_address[channel_id*DMA_SPAN + DMA_RESET] = 0; return 0; #else return -1; #endif } ////////////////////////////////////////////////////////////////////////////////// // _dma_get_status() ////////////////////////////////////////////////////////////////////////////////// unsigned int _dma_get_status( unsigned int cluster_id, unsigned int channel_id, unsigned int* status ) { #if NB_DMAS_MAX > 0 // parameters checking if ( cluster_id >= NB_CLUSTERS ) return 1; if ( channel_id >= NB_DMAS_MAX ) return 1; // compute DMA base address unsigned int* dma_address = (unsigned int*)( (char*)&seg_dma_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); *status = dma_address[channel_id*DMA_SPAN + DMA_LEN]; return 0; #else return -1; #endif } ////////////////////////////////////////////////////////////////////////////////// // VciFrameBuffer driver ////////////////////////////////////////////////////////////////////////////////// // The vci_frame_buffer device can be accessed directly by software with memcpy(), // or it can be accessed through a multi-channels DMA component: // // The '_fb_sync_write' and '_fb_sync_read' functions use a memcpy strategy to // implement the transfer between a data buffer (user space) and the frame // buffer (kernel space). They are blocking until completion of the transfer. // // The '_fb_write()', '_fb_read()' and '_fb_completed()' functions use the DMA // controlers (distributed in the clusters) to transfer data // between the user buffer and the frame buffer. A DMA channel is // allocated to each task requesting it in the mapping_info data structure. ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// // _fb_sync_write() // Transfer data from an memory buffer to the frame_buffer device using // a memcpy. The source memory buffer must be in user address space. // - offset : offset (in bytes) in the frame buffer. // - buffer : base address of the memory buffer. // - length : number of bytes to be transfered. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_sync_write( unsigned int offset, const void* buffer, unsigned int length ) { // buffer must be mapped in user space if ( ((unsigned int)buffer + length ) >= 0x80000000 ) { return 1; } else { unsigned char *fb_address = (unsigned char*)&seg_fbf_base + offset; memcpy((void*)fb_address, (void*)buffer, length); return 0; } } ////////////////////////////////////////////////////////////////////////////////// // _fb_sync_read() // Transfer data from the frame_buffer device to a memory buffer using // a memcpy. The destination memory buffer must be in user address space. // - offset : offset (in bytes) in the frame buffer. // - buffer : base address of the memory buffer. // - length : number of bytes to be transfered. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_sync_read( unsigned int offset, const void* buffer, unsigned int length ) { // buffer must be mapped in user space if ( ((unsigned int)buffer + length ) >= 0x80000000 ) { return 1; } else { unsigned char *fb_address = (unsigned char*)&seg_fbf_base + offset; memcpy((void*)buffer, (void*)fb_address, length); return 0; } } ////////////////////////////////////////////////////////////////////////////////// // _fb_dma_access() // Transfer data between a user buffer and the frame_buffer using DMA. // - to_user : from frame buffer to user buffer when true. // - offset : offset (in bytes) in the frame buffer. // - user_vaddr : virtual base address of the memory buffer. // - length : number of bytes to be transfered. // The user buffer must be mapped in user address space and word-aligned. // The user buffer length must be multiple of 4 bytes. // Me must compute the physical base addresses for both the frame buffer // and the user buffer before programming the DMA transfer. // The GIET being fully static, we don't need to split the transfer in 4 Kbytes // pages, because the user buffer is contiguous in physical space. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_dma_access( unsigned int to_user, unsigned int offset, unsigned int user_vaddr, unsigned int length ) { #if NB_DMAS_MAX > 0 unsigned int ko; // unsuccessfull V2P translation unsigned int flags; // protection flags unsigned int ppn; // physical page number unsigned int user_pbase; // user buffer pbase address unsigned int fb_pbase; // frame buffer pbase address // get DMA channel and compute DMA vbase address unsigned int task_id = _get_current_task_id(); unsigned int dma_id = _get_context_slot( task_id, CTX_FBDMA_ID ); unsigned int cluster_id = dma_id / NB_DMAS_MAX; unsigned int loc_id = dma_id % NB_DMAS_MAX; unsigned int* dma_base = (unsigned int*)( (char*)&seg_dma_base + (cluster_id * (unsigned)CLUSTER_SIZE) ); // check user buffer address and length alignment if ( (user_vaddr & 0x3) || (length & 0x3) ) { _get_lock(&_tty_put_lock); _puts("\n[GIET ERROR] in _fbdma_access() : user buffer not word aligned\n"); _release_lock(&_tty_put_lock); return 1; } // get user space page table virtual address unsigned int user_ptab = _get_context_slot( task_id, CTX_PTAB_ID ); // compute frame buffer pbase address unsigned int fb_vaddr = (unsigned int)&seg_fbf_base + offset; ko = _v2p_translate( (page_table_t*)user_ptab, (fb_vaddr >> 12), &ppn, &flags ); fb_pbase = (ppn << 12) | (fb_vaddr & 0x00000FFF); if ( ko ) { _get_lock(&_tty_put_lock); _puts("\n[GIET ERROR] in _fbdma_access() : frame buffer unmapped\n"); _release_lock(&_tty_put_lock); return 2; } // Compute user buffer pbase address ko = _v2p_translate( (page_table_t*)user_ptab, (user_vaddr >> 12), &ppn, &flags ); user_pbase = (ppn << 12) | (user_vaddr & 0x00000FFF); if ( ko ) { _get_lock(&_tty_put_lock); _puts("\n[GIET ERROR] in _fbdma_access() : user buffer unmapped\n"); _release_lock(&_tty_put_lock); return 3; } if ( (flags & PTE_U) == 0 ) { _get_lock(&_tty_put_lock); _puts("[GIET ERROR] in _fbdma_access() : user buffer not in user space\n"); _release_lock(&_tty_put_lock); return 4; } if ( ( (flags & PTE_W) == 0 ) && to_user ) { _get_lock(&_tty_put_lock); _puts("\n[GIET ERROR] in _fbdma_access() : user buffer not writable\n"); _release_lock(&_tty_put_lock); return 5; } /* // loop on all virtual pages covering the user buffer unsigned int user_vpn_min = user_vaddr >> 12; unsigned int user_vpn_max = (user_vaddr + length - 1) >> 12; unsigned int ix2 = 0; unsigned int ix1 = _dma_iommu_ix1 + dma_id; for ( vpn = user_vpn_min ; vpn <= user_vpn_max ; vpn++ ) { // get ppn and flags for each vpn unsigned int ko = _v2p_translate( (page_table_t*)user_pt_vbase, vpn, &ppn, &flags ); // check access rights if ( ko ) return 3; // unmapped if ( (flags & PTE_U) == 0 ) return 4; // not in user space if ( ( (flags & PTE_W) == 0 ) && to_user ) return 5; // not writable // save first ppn value if ( ix2 == 0 ) ppn_first = ppn; if ( IOMMU_ACTIVE ) // the user buffer must be remapped in the I/0 space { // check buffer length < 2 Mbytes if ( ix2 > 511 ) return 2; // map the physical page in IOMMU page table _iommu_add_pte2( ix1, // PT1 index ix2, // PT2 index ppn, // physical page number flags ); // protection flags } else // no IOMMU : check that physical pages are contiguous { if ( (ppn - ppn_first) != ix2 ) return 6; // split physical buffer } // increment page index ix2++; } // end for vpn // register the number of pages to be unmapped if iommu activated _dma_iommu_npages[dma_id] = (user_vpn_max - user_vpn_min) + 1; */ // invalidate data cache in case of memory write if ( to_user ) _dcache_buf_invalidate( (void*)user_vaddr, length ); // get the lock _get_lock( &_dma_lock[dma_id] ); // DMA configuration if ( to_user ) { dma_base[loc_id*DMA_SPAN + DMA_SRC] = (unsigned int)fb_pbase; dma_base[loc_id*DMA_SPAN + DMA_DST] = (unsigned int)user_pbase; } else { dma_base[loc_id*DMA_SPAN + DMA_SRC] = (unsigned int)user_pbase; dma_base[loc_id*DMA_SPAN + DMA_DST] = (unsigned int)fb_pbase; } dma_base[loc_id*DMA_SPAN + DMA_LEN] = (unsigned int)length; return 0; #else //NB_DMAS_MAX == 0 return -1; #endif } ////////////////////////////////////////////////////////////////////////////////// // _fb_write() // Transfer data from a memory buffer to the frame_buffer device using DMA. // - offset : offset (in bytes) in the frame buffer. // - buffer : base address of the memory buffer. // - length : number of bytes to be transfered. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_write( unsigned int offset, const void* buffer, unsigned int length ) { return _fb_dma_access( 0, // write to frame buffer offset, (unsigned int)buffer, length ); } ////////////////////////////////////////////////////////////////////////////////// // _fb_read() // Transfer data from the frame_buffer device to a memory buffer using DMA. // - offset : offset (in bytes) in the frame buffer. // - buffer : base address of the memory buffer. // - length : number of bytes to be transfered. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_read( unsigned int offset, const void* buffer, unsigned int length ) { return _fb_dma_access( 1, // read from frame buffer offset, (unsigned int)buffer, length ); } ////////////////////////////////////////////////////////////////////////////////// // _fb_completed() // This function checks completion of a DMA transfer to or fom the frame buffer. // As it is a blocking call, the processor is busy waiting. // Returns 0 if success, > 0 if error // (1 == read error / 2 == DMA idle error / 3 == write error) ////////////////////////////////////////////////////////////////////////////////// unsigned int _fb_completed() { #if NB_DMAS_MAX > 0 unsigned int task_id = _get_current_task_id(); unsigned int dma_id = _get_context_slot( task_id, CTX_FBDMA_ID ); // busy waiting with a pseudo random delay between bus access while (_dma_done[dma_id] == 0) { unsigned int i; unsigned int delay = ( _proctime() ^ _procid()<<4 ) & 0xFF; for (i = 0; i < delay; i++) asm volatile("nop"); } // unmap the buffer from IOMMU page table if IOMMU is activated if ( IOMMU_ACTIVE ) { unsigned int* iob_address = (unsigned int*) &seg_iob_base; unsigned int ix1 = _dma_iommu_ix1 + dma_id; unsigned int ix2; for ( ix2 = 0 ; ix2 < _dma_iommu_npages[dma_id] ; ix2++ ) { // unmap the page in IOMMU page table _iommu_inval_pte2( ix1, // PT1 index ix2 ); // PT2 index // clear IOMMU TLB iob_address[IOB_INVAL_PTE] = (ix1 << 21) | (ix2 << 12); } } // reset synchronization variables _dma_lock[dma_id] = 0; _dma_done[dma_id] = 0; return _dma_status[dma_id]; #else //NB_DMAS_MAX == 0 return -1; #endif }