/////////////////////////////////////////////////////////////////////////////////// // 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_gcd // - vci_frame_buffer // - vci_block_device // // The following global parameters must be defined in the giet_config.h file: // - NB_CLUSTERS // - NB_PROCS_MAX // - NB_TIMERS_MAX // - NB_DMAS_MAX // - NB_TTYS // // The following base addresses must be defined in the sys.ld file: // - seg_icu_base // - seg_timer_base // - seg_tty_base // - seg_gcd_base // - seg_dma_base // - seg_fb_base // - seg_ioc_base /////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #if !defined(NB_CLUSTERS) # error: You must define NB_CLUSTERS in 'giet_config.h' file #endif #if !defined(NB_PROCS_MAX) # error: You must define NB_PROCS_MAX in 'giet_config.h' file #endif #if (NB_PROCS_MAX > 8) # error: NB_PROCS_MAX cannot be larger than 8! #endif #if !defined(CLUSTER_SPAN) # error: You must define CLUSTER_SPAN in 'giet_config.h' file #endif #if !defined(NB_TTYS) # error: You must define NB_TTYS in 'giet_config.h' file #endif #if (NB_TTYS < 1) # error: NB_TTYS cannot be smaller than 1! #endif #if !defined(NB_DMAS_MAX) # error: You must define NB_DMAS_MAX in 'giet_config.h' file #endif #if (NB_DMAS_MAX < 1) # error: NB_DMAS_MAX cannot be 0! #endif #if !defined(NB_TIMERS_MAX) # error: You must define NB_TIMERS_MAX in 'giet_config.h' file #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 'giet_config.h' file #endif #if ( NB_IOCS > 1 ) # error: NB_IOCS cannot be larger than 1 #endif #define in_unckdata __attribute__((section (".unckdata"))) ////////////////////////////////////////////////////////////////////////////// // VciMultiTimer driver ////////////////////////////////////////////////////////////////////////////// // There is one multi_timer (or xicu) component per cluster. // The global index is cluster_id*(NB_PROCS_MAX+NB_TIMERS_MAX) + local_id // 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 tty_id is stored in the context of the task // and must be explicitely defined in the boot code. // These timers can be implemented in a vci_multi_timer component // or in a vci_xicu component (depending on the GIET_USE_XICU parameter). ////////////////////////////////////////////////////////////////////////////// // 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_access() // This function is the only way to access a timer device. // It can be a multi-timer component or an xicu component. // It can be used by 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_access( unsigned int read, unsigned int cluster_id, unsigned int local_id, unsigned int register_id, unsigned int* buffer ) { // parameters checking if ( register_id >= TIMER_SPAN) return 1; if ( cluster_id >= NB_CLUSTERS) return 1; if ( local_id >= NB_TIMERS_MAX + NB_PROCS_MAX ) return 1; #if GIET_USE_XICU unsigned int* timer_address = //TODO #else unsigned int* timer_address = (unsigned int*)&seg_timer_base + (cluster_id * CLUSTER_SPAN) + (local_id * TIMER_SPAN); #endif if (read) *buffer = timer_address[register_id]; // read word else timer_address[register_id] = *buffer; // write word return 0; } ////////////////////////////////////////////////////////////////////////////// // _timer_write() // This function implements a write access to a "user" timer register. // It gets the cluster_id and local_id from the global index stored in // the task context and use the timer_access() function to make the write. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////// unsigned int _timer_write( unsigned int register_id, unsigned int value ) { unsigned int buffer = value; unsigned int task_id = _get_current_task_id(); unsigned int timer_id = _get_context_slot(task_id, CTX_TIMER_ID); unsigned int cluster_id = timer_id / (NB_PROCS_MAX + NB_TIMERS_MAX); unsigned int local_id = timer_id % (NB_PROCS_MAX + NB_TIMERS_MAX); // checking user timer if ( local_id < NB_PROCS_MAX ) { return 2; } else { return _timer_access ( 0, // write access cluster_id, local_id, register_id, &buffer ); } } ////////////////////////////////////////////////////////////////////////////// // _timer_read() // This function implements a read access to a "user" timer register. // It gets the cluster_id and local_id from the global index stored in // the task context and use the timer_access() function to make the read. // Returns 0 if success, > 0 if error. ////////////////////////////////////////////////////////////////////////////// unsigned int _timer_read( unsigned int register_id, unsigned int* buffer ) { unsigned int task_id = _get_current_task_id(); unsigned int timer_id = _get_context_slot(task_id, CTX_TIMER_ID); unsigned int cluster_id = timer_id / (NB_PROCS_MAX + NB_TIMERS_MAX); unsigned int local_id = timer_id % (NB_PROCS_MAX + NB_TIMERS_MAX); // checking user timer if ( local_id < NB_PROCS_MAX ) { return 2; } else { return _timer_access ( 1, // read access cluster_id, local_id, register_id, buffer ); } } ///////////////////////////////////////////////////////////////////////////////// // _timer_check() ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // 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 "); _putw( task_id ); _puts(" on processor "); _putw( 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 + tty_id*TTY_SPAN; for (nwritten = 0; nwritten < length; nwritten++) { // check tty's status if ((tty_address[TTY_STATUS] & 0x2) == 0x2) break; else // write character tty_address[TTY_WRITE] = (unsigned int)buffer[nwritten]; } return nwritten; } ////////////////////////////////////////////////////////////////////////////// // _tty_read_irq() // 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 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. // Returns 0 if the kernel buffer is empty, 1 if the buffer is full. ////////////////////////////////////////////////////////////////////////////// unsigned int _tty_read_irq( 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_read() // This non-blocking function fetches one character directly from the TTY_READ // register of the TTY controler, and writes this character to the user buffer. // It doesn't use the TTY_GET_IRQ interrupt and the associated kernel buffer. // Returns 0 if the register is empty, 1 if the register 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; } unsigned int* tty_address = (unsigned int*)&seg_tty_base + tty_id*TTY_SPAN; if ((tty_address[TTY_STATUS] & 0x1) != 0x1) { return 0; } else { *buffer = (char)tty_address[TTY_READ]; return 1; } } //////////////////////////////////////////////////////////////////////////////// // VciMultiIcu and VciXicu drivers //////////////////////////////////////////////////////////////////////////////// // There is in principle 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_write() // Write a 32-bit word in a memory mapped register of the MULTI_ICU device, // identified by the cluster index, and a processor local index. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _icu_write( unsigned int cluster_index, unsigned int proc_index, unsigned int register_index, unsigned int value ) { #if GIET_USE_XICU #else // parameters checking if ( register_index >= ICU_SPAN) return 1; if ( cluster_index >= NB_CLUSTERS) return 1; if ( proc_index >= NB_PROCS_MAX ) return 1; unsigned int *icu_address = (unsigned int*)&seg_icu_base + (cluster_index * CLUSTER_SPAN) + (proc_index * ICU_SPAN); icu_address[register_index] = value; // write word return 0; #endif } //////////////////////////////////////////////////////////////////////////////// // _icu_read() // Read a 32-bit word in a memory mapped register of the MULTI_ICU device, // identified by the cluster index and a processor local index. // Returns 0 if success, > 0 if error. //////////////////////////////////////////////////////////////////////////////// unsigned int _icu_read( unsigned int cluster_index, unsigned int proc_index, unsigned int register_index, unsigned int* buffer ) { #if GIET_USE_XICU #else // parameters checking if ( register_index >= ICU_SPAN) return 1; if ( cluster_index >= NB_CLUSTERS) return 1; if ( proc_index >= NB_PROCS_MAX ) return 1; unsigned int *icu_address = (unsigned int*)&seg_icu_base + (cluster_index * CLUSTER_SPAN) + (proc_index * ICU_SPAN); *buffer = icu_address[register_index]; // read word return 0; #endif } //////////////////////////////////////////////////////////////////////////////// // 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) { volatile unsigned int *gcd_address; // parameters checking if (register_index >= GCD_END) return 1; 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) { volatile unsigned int *gcd_address; // parameters checking if (register_index >= GCD_END) return 1; 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 ( GIET_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 ( GIET_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 ( GIET_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 ); } ////////////////////////////////////////////////////////////////////////////////// // 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. ////////////////////////////////////////////////////////////////////////////////// #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 ////////////////////////////////////////////////////////////////////////////////// // 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_fb_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_fb_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 memory 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 4Kbytes // 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 ) { 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*)&seg_dma_base + (cluster_id * CLUSTER_SPAN) + (loc_id * DMA_SPAN); // check user buffer address and length alignment if ( (user_vaddr & 0x3) || (length & 0x3) ) { _puts("[GIET ERROR] in _fbdma_access() : user buffer not word aligned\n"); 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_fb_base + offset; ko = _v2p_translate( (page_table_t*)user_ptab, (fb_vaddr >> 12), &ppn, &flags ); fb_pbase = (ppn << 12) | (fb_vaddr & 0x00000FFF); if ( ko ) { _puts("[GIET ERROR] in _fbdma_access() : frame buffer unmapped\n"); 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 ) { _puts("[GIET ERROR] in _fbdma_access() : user buffer unmapped\n"); return 3; } if ( (flags & PTE_U) == 0 ) { _puts("[GIET ERROR] in _fbdma_access() : user buffer not in user space\n"); return 4; } if ( ( (flags & PTE_W) == 0 ) && to_user ) { _puts("[GIET ERROR] in _fbdma_access() : user buffer not writable\n"); 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 ( GIET_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[DMA_SRC] = (unsigned int)fb_pbase; dma_base[DMA_DST] = (unsigned int)user_pbase; } else { dma_base[DMA_SRC] = (unsigned int)user_pbase; dma_base[DMA_DST] = (unsigned int)fb_pbase; } dma_base[DMA_LEN] = (unsigned int)length; return 0; } ////////////////////////////////////////////////////////////////////////////////// // _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() { 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 ( GIET_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]; }