/** @file dal_kernal.c @date 2012-10-18 @version v2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) #include #endif #include "dal_kernel.h" #include "dal_mpool.h" #include MODULE_AUTHOR("Centec Networks Inc."); MODULE_DESCRIPTION("DAL kernel module"); MODULE_LICENSE("GPL"); /* DMA memory pool size */ static char* dma_pool_size; module_param(dma_pool_size, charp, 0); MODULE_PARM_DESC(dma_pool_size, "Specify DMA memory pool size (default 4MB)"); /***************************************************************************** * defines *****************************************************************************/ #define MB_SIZE 0x100000 #define CTC_MAX_INTR_NUM 8 #define MEM_MAP_RESERVE SetPageReserved #define MEM_MAP_UNRESERVE ClearPageReserved #define CTC_VENDOR_VID 0xc001 #define CTC_HUMBER_DEVICE_ID 0x6048 #define CTC_GOLDENGATE_DEVICE_ID 0xc010 #define CTC_PCIE_VENDOR_ID 0xcb10 #define CTC_DUET2_DEVICE_ID 0x7148 #define MEM_MAP_RESERVE SetPageReserved #define MEM_MAP_UNRESERVE ClearPageReserved #define CTC_GREATBELT_DEVICE_ID 0x03e8 /* TBD */ #define DAL_MAX_CHIP_NUM 8 /* [GB] used */ #define VIRT_TO_PAGE(p) virt_to_page((p)) #define DAL_UNTAG_BLOCK 0 #define DAL_DISCARD_BLOCK 1 #define DAL_MATCHED_BLOCK 2 #define DAL_CUR_MATCH_BLOCk 3 /***************************************************************************** * typedef *****************************************************************************/ /* Control Data */ typedef struct dal_isr_s { int irq; void (* isr)(void*); void* isr_data; int trigger; int count; wait_queue_head_t wqh; } dal_isr_t; typedef struct dal_kernel_dev_s { struct list_head list; struct pci_dev* pci_dev; /* PCI I/O mapped base address */ uintptr logic_address; /* Physical address */ unsigned long long phys_address; } dal_kern_dev_t; typedef struct _dma_segment { struct list_head list; unsigned long req_size; /* Requested DMA segment size */ unsigned long blk_size; /* DMA block size */ unsigned long blk_order; /* DMA block size in alternate format */ unsigned long seg_size; /* Current DMA segment size */ unsigned long seg_begin; /* Logical address of segment */ unsigned long seg_end; /* Logical end address of segment */ unsigned long* blk_ptr; /* Array of logical DMA block addresses */ int blk_cnt_max; /* Maximum number of block to allocate */ int blk_cnt; /* Current number of blocks allocated */ } dma_segment_t; typedef irqreturn_t (*p_func) (int irq, void* dev_id); /*************************************************************************** *declared ***************************************************************************/ static unsigned int linux_dal_poll0(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll1(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll2(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll3(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll4(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll5(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll6(struct file* filp, struct poll_table_struct* p); static unsigned int linux_dal_poll7(struct file* filp, struct poll_table_struct* p); /***************************************************************************** * global variables *****************************************************************************/ static dal_kern_dev_t dal_dev[DAL_MAX_CHIP_NUM]; static dal_isr_t dal_isr[CTC_MAX_INTR_NUM]; static int dal_chip_num = 0; static int dal_version = 0; static int dal_intr_num = 0; static int use_high_memory = 0; static unsigned int* dma_virt_base[DAL_MAX_CHIP_NUM]; #ifndef DMA_MEM_MODE_PLATFORM static unsigned int* dma_virt_base_tmp[DAL_MAX_CHIP_NUM]; #endif static unsigned long long dma_phy_base[DAL_MAX_CHIP_NUM]; static unsigned int dma_mem_size = 0xc00000; static unsigned int msi_irq_base[DAL_MAX_CHIP_NUM]; static unsigned int msi_irq_num[DAL_MAX_CHIP_NUM]; static unsigned int msi_used = 0; static struct class *dal_class; static LIST_HEAD(_dma_seg); static int dal_debug = 0; module_param(dal_debug, int, 0); MODULE_PARM_DESC(dal_debug, "Set debug level (default 0)"); static struct pci_device_id dal_id_table[] = { {PCI_DEVICE(CTC_VENDOR_VID, CTC_GREATBELT_DEVICE_ID)}, {PCI_DEVICE(CTC_PCIE_VENDOR_ID, CTC_GOLDENGATE_DEVICE_ID)}, {PCI_DEVICE((CTC_PCIE_VENDOR_ID+1), (CTC_GOLDENGATE_DEVICE_ID+1))}, {PCI_DEVICE(CTC_PCIE_VENDOR_ID, CTC_DUET2_DEVICE_ID)}, {0, }, }; static wait_queue_head_t poll_intr[CTC_MAX_INTR_NUM]; p_func intr_handler_fun[CTC_MAX_INTR_NUM]; static int poll_intr_trigger[CTC_MAX_INTR_NUM]; static struct file_operations dal_intr_fops[CTC_MAX_INTR_NUM] = { { .owner = THIS_MODULE, .poll = linux_dal_poll0, }, { .owner = THIS_MODULE, .poll = linux_dal_poll1, }, { .owner = THIS_MODULE, .poll = linux_dal_poll2, }, { .owner = THIS_MODULE, .poll = linux_dal_poll3, }, { .owner = THIS_MODULE, .poll = linux_dal_poll4, }, { .owner = THIS_MODULE, .poll = linux_dal_poll5, }, { .owner = THIS_MODULE, .poll = linux_dal_poll6, }, { .owner = THIS_MODULE, .poll = linux_dal_poll7, }, }; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)) #include #define virt_to_bus virt_to_phys #define bus_to_virt phys_to_virt #endif /***************************************************************************** * macros *****************************************************************************/ #define VERIFY_CHIP_INDEX(n) (n < dal_chip_num) #define _KERNEL_INTERUPT_PROCESS static irqreturn_t intr0_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[0]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[0] = 1; wake_up(&poll_intr[0]); } } return IRQ_HANDLED; } static irqreturn_t intr1_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[1]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[1] = 1; wake_up(&poll_intr[1]); } } return IRQ_HANDLED; } static irqreturn_t intr2_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[2]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[2] = 1; wake_up(&poll_intr[2]); } } return IRQ_HANDLED; } static irqreturn_t intr3_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[3]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[3] = 1; wake_up(&poll_intr[3]); } } return IRQ_HANDLED; } static irqreturn_t intr4_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[4]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[4] = 1; wake_up(&poll_intr[4]); } } return IRQ_HANDLED; } static irqreturn_t intr5_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[5]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[5] = 1; wake_up(&poll_intr[5]); } } return IRQ_HANDLED; } static irqreturn_t intr6_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[6]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[6] = 1; wake_up(&poll_intr[6]); } } return IRQ_HANDLED; } static irqreturn_t intr7_handler(int irq, void* dev_id) { dal_isr_t* p_dal_isr = (dal_isr_t*)dev_id; if(poll_intr_trigger[7]) { return IRQ_HANDLED; } disable_irq_nosync(irq); if (p_dal_isr) { if (p_dal_isr->isr) { /* kernel mode interrupt handler */ p_dal_isr->isr(p_dal_isr->isr_data); } else if ((NULL == p_dal_isr->isr) && (NULL == p_dal_isr->isr_data)) { /* user mode interrupt handler */ poll_intr_trigger[7] = 1; wake_up(&poll_intr[7]); } } return IRQ_HANDLED; } int dal_interrupt_register(unsigned int irq, int prio, void (* isr)(void*), void* data) { int ret; unsigned char str[16]; unsigned char* int_name = NULL; unsigned int intr_num_tmp = 0; unsigned int intr_num = CTC_MAX_INTR_NUM; unsigned long irq_flags = 0; if (dal_intr_num >= CTC_MAX_INTR_NUM) { printk("Interrupt numbers exceeds max.\n"); return -1; } if (msi_used) { int_name = "dal_msi"; } else { int_name = "dal_intr"; } for (intr_num_tmp=0;intr_num_tmp < CTC_MAX_INTR_NUM; intr_num_tmp++) { if (irq == dal_isr[intr_num_tmp].irq) { if (0 == msi_used) { dal_isr[intr_num_tmp].count++; printk("Interrupt irq %d register count %d.\n", irq, dal_isr[intr_num_tmp].count); } return 0; } if ((0 == dal_isr[intr_num_tmp].irq) && (CTC_MAX_INTR_NUM == intr_num)) { intr_num = intr_num_tmp; dal_isr[intr_num].count = 0; } } dal_isr[intr_num].irq = irq; dal_isr[intr_num].isr = isr; dal_isr[intr_num].isr_data = data; dal_isr[intr_num].count++; init_waitqueue_head(&poll_intr[intr_num]); /* only user mode */ if ((NULL == isr) && (NULL == data)) { snprintf(str, 16, "%s%d", "dal_intr", intr_num); ret = register_chrdev(DAL_DEV_INTR_MAJOR_BASE + intr_num, str, &dal_intr_fops[intr_num]); if (ret < 0) { printk("Register character device for irq %d failed, ret= %d", irq, ret); return ret; } } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) irq_flags = 0; #else irq_flags = IRQF_DISABLED; #endif if ((ret = request_irq(irq, intr_handler_fun[intr_num], irq_flags, int_name, &dal_isr[intr_num])) < 0) { printk("Cannot request irq %d, ret %d.\n", irq, ret); unregister_chrdev(DAL_DEV_INTR_MAJOR_BASE + intr_num, str); } if (0 == ret) { dal_intr_num++; } return ret; } int dal_interrupt_unregister(unsigned int irq) { unsigned char str[16]; int intr_idx = 0; int find_flag = 0; /* get intr device index */ for (intr_idx = 0; intr_idx < CTC_MAX_INTR_NUM; intr_idx++) { if (dal_isr[intr_idx].irq == irq) { find_flag = 1; break; } } if (find_flag == 0) { printk ("irq%d is not registered! unregister failed \n", irq); return -1; } dal_isr[intr_idx].count--; if (0 != dal_isr[intr_idx].count) { printk("Interrupt irq %d unregister count %d.\n", irq, dal_isr[intr_idx].count); return -1; } snprintf(str, 16, "%s%d", "dal_intr", intr_idx); unregister_chrdev(DAL_DEV_INTR_MAJOR_BASE + intr_idx, str); free_irq(irq, &dal_isr[intr_idx]); dal_isr[intr_idx].irq = 0; dal_intr_num--; return 0; } int dal_interrupt_set_en(unsigned int irq, unsigned int enable) { enable ? enable_irq(irq) : disable_irq_nosync(irq); return 0; } static int _dal_set_msi_enabe(unsigned int lchip, unsigned int irq_num) { int ret = 0; if (irq_num == 1) { ret = pci_enable_msi(dal_dev[lchip].pci_dev); if (ret) { printk ("msi enable failed!!! lchip = %d, irq_num = %d\n", lchip, irq_num); pci_disable_msi(dal_dev[lchip].pci_dev); msi_used = 0; } msi_irq_base[lchip] = dal_dev[lchip].pci_dev->irq; msi_irq_num[lchip] = 1; } return ret; } static int _dal_set_msi_disable(unsigned int lchip) { pci_disable_msi(dal_dev[lchip].pci_dev); msi_irq_base[lchip] = 0; msi_irq_num[lchip] = 0; return 0; } int dal_set_msi_cap(unsigned long arg) { int ret = 0; int index = 0; dal_msi_info_t msi_info; if (copy_from_user(&msi_info, (void*)arg, sizeof(dal_msi_info_t))) { return -EFAULT; } printk("####dal_set_msi_cap lchip %d base %d num:%d\n", msi_info.lchip, msi_info.irq_base, msi_info.irq_num); if (msi_info.irq_num > 0) { if (0 == msi_used) { msi_used = 1; ret = _dal_set_msi_enabe(msi_info.lchip, msi_info.irq_num); } else if ((1 == msi_used) && (msi_info.irq_num != msi_irq_num[msi_info.lchip])) { for (index = 0; index < msi_irq_num[msi_info.lchip]; index++) { dal_interrupt_unregister(msi_irq_base[msi_info.lchip]+index); } _dal_set_msi_disable(msi_info.lchip); msi_used = 1; ret = _dal_set_msi_enabe(msi_info.lchip, msi_info.irq_num); } } else { msi_used = 0; ret = _dal_set_msi_disable(msi_info.lchip); } return ret; } int dal_user_interrupt_register(unsigned long arg) { int irq = 0; if (copy_from_user(&irq, (void*)arg, sizeof(int))) { return -EFAULT; } printk("####register interrupt irq:%d\n", irq); return dal_interrupt_register(irq, 0, NULL, NULL); } int dal_user_interrupt_unregister(unsigned long arg) { int irq = 0; if (copy_from_user(&irq, (void*)arg, sizeof(int))) { return -EFAULT; } printk("####unregister interrupt irq:%d\n", irq); return dal_interrupt_unregister(irq); } int dal_user_interrupt_set_en(unsigned long arg) { dal_intr_parm_t dal_intr_parm; if (copy_from_user(&dal_intr_parm, (void*)arg, sizeof(dal_intr_parm_t))) { return -EFAULT; } return dal_interrupt_set_en(dal_intr_parm.irq, dal_intr_parm.enable); } /* * Function: _dal_dma_segment_free */ /* * Function: _find_largest_segment * * Purpose: * Find largest contiguous segment from a pool of DMA blocks. * Parameters: * dseg - DMA segment descriptor * Returns: * 0 on success, < 0 on error. * Notes: * Assembly stops if a segment of the requested segment size * has been obtained. * * Lower address bits of the DMA blocks are used as follows: * 0: Untagged * 1: Discarded block * 2: Part of largest contiguous segment * 3: Part of current contiguous segment */ #ifndef DMA_MEM_MODE_PLATFORM static int _dal_find_largest_segment(dma_segment_t* dseg) { int i, j, blks, found; unsigned long seg_begin; unsigned long seg_end; unsigned long seg_tmp; blks = dseg->blk_cnt; /* Clear all block tags */ for (i = 0; i < blks; i++) { dseg->blk_ptr[i] &= ~3; } for (i = 0; i < blks && dseg->seg_size < dseg->req_size; i++) { /* First block must be an untagged block */ if ((dseg->blk_ptr[i] & 3) == DAL_UNTAG_BLOCK) { /* Initial segment size is the block size */ seg_begin = dseg->blk_ptr[i]; seg_end = seg_begin + dseg->blk_size; dseg->blk_ptr[i] |= DAL_CUR_MATCH_BLOCk; /* Loop looking for adjacent blocks */ do { found = 0; for (j = i + 1; j < blks && (seg_end - seg_begin) < dseg->req_size; j++) { seg_tmp = dseg->blk_ptr[j]; /* Check untagged blocks only */ if ((seg_tmp & 3) == DAL_UNTAG_BLOCK) { if (seg_tmp == (seg_begin - dseg->blk_size)) { /* Found adjacent block below current segment */ dseg->blk_ptr[j] |= DAL_CUR_MATCH_BLOCk; seg_begin = seg_tmp; found = 1; } else if (seg_tmp == seg_end) { /* Found adjacent block above current segment */ dseg->blk_ptr[j] |= DAL_CUR_MATCH_BLOCk; seg_end += dseg->blk_size; found = 1; } } } } while (found); if ((seg_end - seg_begin) > dseg->seg_size) { /* The current block is largest so far */ dseg->seg_begin = seg_begin; dseg->seg_end = seg_end; dseg->seg_size = seg_end - seg_begin; /* Re-tag current and previous largest segment */ for (j = 0; j < blks; j++) { if ((dseg->blk_ptr[j] & 3) == DAL_CUR_MATCH_BLOCk) { /* Tag current segment as the largest */ dseg->blk_ptr[j] &= ~1; } else if ((dseg->blk_ptr[j] & 3) == DAL_MATCHED_BLOCK) { /* Discard previous largest segment */ dseg->blk_ptr[j] ^= 3; } } } else { /* Discard all blocks in current segment */ for (j = 0; j < blks; j++) { if ((dseg->blk_ptr[j] & 3) == DAL_CUR_MATCH_BLOCk) { dseg->blk_ptr[j] &= ~2; } } } } } return 0; } /* * Function: _alloc_dma_blocks */ static int _dal_alloc_dma_blocks(dma_segment_t* dseg, int blks) { int i, start; unsigned long addr; if (dseg->blk_cnt + blks > dseg->blk_cnt_max) { printk("No more DMA blocks\n"); return -1; } start = dseg->blk_cnt; dseg->blk_cnt += blks; for (i = start; i < dseg->blk_cnt; i++) { addr = __get_free_pages(GFP_ATOMIC, dseg->blk_order); if (addr) { dseg->blk_ptr[i] = addr; } else { printk("DMA allocation failed\n"); return -1; } } return 0; } /* * Function: _dal_dma_segment_alloc */ static dma_segment_t* _dal_dma_segment_alloc(unsigned int size, unsigned int blk_size) { dma_segment_t* dseg; int i, blk_ptr_size; unsigned long page_addr; struct sysinfo si; /* Sanity check */ if (size == 0 || blk_size == 0) { return NULL; } /* Allocate an initialize DMA segment descriptor */ if ((dseg = kmalloc(sizeof(dma_segment_t), GFP_ATOMIC)) == NULL) { return NULL; } memset(dseg, 0, sizeof(dma_segment_t)); dseg->req_size = size; dseg->blk_size = PAGE_ALIGN(blk_size); while ((PAGE_SIZE << dseg->blk_order) < dseg->blk_size) { dseg->blk_order++; } si_meminfo(&si); dseg->blk_cnt_max = (si.totalram << PAGE_SHIFT) / dseg->blk_size; blk_ptr_size = dseg->blk_cnt_max * sizeof(unsigned long); /* Allocate an initialize DMA block pool */ dseg->blk_ptr = kmalloc(blk_ptr_size, GFP_KERNEL); if (dseg->blk_ptr == NULL) { kfree(dseg); return NULL; } memset(dseg->blk_ptr, 0, blk_ptr_size); /* Allocate minimum number of blocks */ _dal_alloc_dma_blocks(dseg, dseg->req_size / dseg->blk_size); /* Allocate more blocks until we have a complete segment */ do { _dal_find_largest_segment(dseg); if (dseg->seg_size >= dseg->req_size) { break; } } while (_dal_alloc_dma_blocks(dseg, 8) == 0); /* Reserve all pages in the DMA segment and free unused blocks */ for (i = 0; i < dseg->blk_cnt; i++) { if ((dseg->blk_ptr[i] & 3) == 2) { dseg->blk_ptr[i] &= ~3; for (page_addr = dseg->blk_ptr[i]; page_addr < dseg->blk_ptr[i] + dseg->blk_size; page_addr += PAGE_SIZE) { MEM_MAP_RESERVE(VIRT_TO_PAGE((void*)page_addr)); } } else if (dseg->blk_ptr[i]) { dseg->blk_ptr[i] &= ~3; free_pages(dseg->blk_ptr[i], dseg->blk_order); dseg->blk_ptr[i] = 0; } } return dseg; } /* * Function: _dal_dma_segment_free */ static void _dal_dma_segment_free(dma_segment_t* dseg) { int i; unsigned long page_addr; if (dseg->blk_ptr) { for (i = 0; i < dseg->blk_cnt; i++) { if (dseg->blk_ptr[i]) { for (page_addr = dseg->blk_ptr[i]; page_addr < dseg->blk_ptr[i] + dseg->blk_size; page_addr += PAGE_SIZE) { MEM_MAP_UNRESERVE(VIRT_TO_PAGE(page_addr)); } free_pages(dseg->blk_ptr[i], dseg->blk_order); } } kfree(dseg->blk_ptr); kfree(dseg); } } /* * Function: -dal_pgalloc */ static void* _dal_pgalloc(unsigned int size) { dma_segment_t* dseg; unsigned int blk_size; blk_size = (size < DMA_BLOCK_SIZE) ? size : DMA_BLOCK_SIZE; if ((dseg = _dal_dma_segment_alloc(size, blk_size)) == NULL) { return NULL; } if (dseg->seg_size < size) { /* If we didn't get the full size then forget it */ printk("Notice: Can not get enough memory for requset!!\n"); printk("actual size:0x%lx, request size:0x%x\n", dseg->seg_size, size); //-_dal_dma_segment_free(dseg); //-return NULL; } list_add(&dseg->list, &_dma_seg); return (void*)dseg->seg_begin; } /* * Function: _dal_pgfree */ static int _dal_pgfree(void* ptr) { struct list_head* pos; list_for_each(pos, &_dma_seg) { dma_segment_t* dseg = list_entry(pos, dma_segment_t, list); if (ptr == (void*)dseg->seg_begin) { list_del(&dseg->list); _dal_dma_segment_free(dseg); return 0; } } return -1; } #endif static void dal_alloc_dma_pool(int lchip, int size) { if (use_high_memory) { dma_phy_base[lchip] = virt_to_bus(high_memory); dma_virt_base[lchip] = ioremap_nocache(dma_phy_base[lchip], size); } else { #ifdef DMA_MEM_MODE_PLATFORM dma_virt_base[lchip] = dma_alloc_coherent(&(dal_dev[lchip].pci_dev->dev), dma_mem_size, &dma_phy_base[lchip], GFP_KERNEL); printk(KERN_WARNING "########Using DMA_MEM_MODE_PLATFORM \n"); #endif #ifndef DMA_MEM_MODE_PLATFORM /* Get DMA memory from kernel */ dma_virt_base_tmp[lchip] = _dal_pgalloc(size); dma_phy_base[lchip] = virt_to_bus(dma_virt_base_tmp[lchip]); dma_virt_base [lchip]= ioremap_nocache(dma_phy_base[lchip], size); #endif } } static void dal_free_dma_pool(int lchip) { int ret = 0; ret = ret; if (use_high_memory) { iounmap(dma_virt_base[lchip]); } else { #ifdef DMA_MEM_MODE_PLATFORM dma_free_coherent(&(dal_dev[lchip].pci_dev->dev), dma_mem_size, dma_virt_base[lchip], dma_phy_base[lchip]); #endif #ifndef DMA_MEM_MODE_PLATFORM iounmap(dma_virt_base[lchip]); ret = _dal_pgfree(dma_virt_base_tmp[lchip]); if(ret<0) { printk("Dma free memory fail !!!!!! \n"); } #endif } } #define _KERNEL_DAL_IO static int _dal_pci_read(unsigned char lchip, unsigned int offset, unsigned int* value) { if (!VERIFY_CHIP_INDEX(lchip)) { return -1; } *value = *(volatile unsigned int*)(dal_dev[lchip].logic_address + offset); return 0; } int dal_create_irq_mapping(unsigned long arg) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0)) #ifndef NO_IRQ #define NO_IRQ (-1) #endif dal_irq_mapping_t irq_map; if (copy_from_user(&irq_map, (void*)arg, sizeof(dal_irq_mapping_t))) { return -EFAULT; } irq_map.sw_irq = irq_create_mapping(NULL, irq_map.hw_irq); if (irq_map.sw_irq == NO_IRQ) { printk("IRQ mapping fail !!!!!! \n"); return -1; } if (copy_to_user((dal_irq_mapping_t*)arg, (void*)&irq_map, sizeof(dal_irq_mapping_t))) { return -EFAULT; } #endif return 0; } int dal_pci_read(unsigned long arg) { dal_chip_parm_t cmdpara_chip; if (copy_from_user(&cmdpara_chip, (void*)arg, sizeof(dal_chip_parm_t))) { return -EFAULT; } _dal_pci_read((unsigned char)cmdpara_chip.lchip, (unsigned int)cmdpara_chip.reg_addr, (unsigned int*)(&(cmdpara_chip.value))); if (copy_to_user((dal_chip_parm_t*)arg, (void*)&cmdpara_chip, sizeof(dal_chip_parm_t))) { return -EFAULT; } return 0; } static int _dal_pci_write(unsigned char lchip, unsigned int offset, unsigned int value) { if (!VERIFY_CHIP_INDEX(lchip)) { return -1; } *(volatile unsigned int*)(dal_dev[lchip].logic_address + offset) = value; return 0; } int dal_pci_write(unsigned long arg) { dal_chip_parm_t cmdpara_chip; if (copy_from_user(&cmdpara_chip, (void*)arg, sizeof(dal_chip_parm_t))) { return -EFAULT; } _dal_pci_write((unsigned char)cmdpara_chip.lchip, (unsigned int)cmdpara_chip.reg_addr, (unsigned int)cmdpara_chip.value); return 0; } int dal_pci_conf_read(unsigned char lchip, unsigned int offset, unsigned int* value) { if (!VERIFY_CHIP_INDEX(lchip)) { return -1; } pci_read_config_dword(dal_dev[lchip].pci_dev, offset, value); return 0; } int dal_pci_conf_write(unsigned char lchip, unsigned int offset, unsigned int value) { if (!VERIFY_CHIP_INDEX(lchip)) { return -1; } pci_write_config_dword(dal_dev[lchip].pci_dev, offset, value); return 0; } int dal_user_read_pci_conf(unsigned long arg) { dal_pci_cfg_ioctl_t dal_cfg; if (copy_from_user(&dal_cfg, (void*)arg, sizeof(dal_pci_cfg_ioctl_t))) { return -EFAULT; } if (dal_pci_conf_read(dal_cfg.lchip, dal_cfg.offset, &dal_cfg.value)) { printk("dal_pci_conf_read failed.\n"); return -EFAULT; } if (copy_to_user((dal_pci_cfg_ioctl_t*)arg, (void*)&dal_cfg, sizeof(dal_pci_cfg_ioctl_t))) { return -EFAULT; } return 0; } int dal_user_write_pci_conf(unsigned long arg) { dal_pci_cfg_ioctl_t dal_cfg; if (copy_from_user(&dal_cfg, (void*)arg, sizeof(dal_pci_cfg_ioctl_t))) { return -EFAULT; } return dal_pci_conf_write(dal_cfg.lchip, dal_cfg.offset, dal_cfg.value); } static int linux_get_device(unsigned long arg) { dal_user_dev_t user_dev; int lchip = 0; if (copy_from_user(&user_dev, (void*)arg, sizeof(user_dev))) { return -EFAULT; } user_dev.chip_num = dal_chip_num; lchip = user_dev.lchip; if (lchip < dal_chip_num) { user_dev.phy_base0 = (unsigned int)dal_dev[lchip].phys_address; user_dev.phy_base1 = (unsigned int)(dal_dev[lchip].phys_address >> 32); user_dev.bus_no = dal_dev[lchip].pci_dev->bus->number; user_dev.dev_no = dal_dev[lchip].pci_dev->device; user_dev.fun_no = dal_dev[lchip].pci_dev->devfn; } if (copy_to_user((dal_user_dev_t*)arg, (void*)&user_dev, sizeof(user_dev))) { return -EFAULT; } return 0; } /* set dal version, copy to user */ static int linux_get_dal_version(unsigned long arg) { int dal_ver = VERSION_1DOT2; /* set dal version */ if (copy_to_user((int*)arg, (void*)&dal_ver, sizeof(dal_ver))) { return -EFAULT; } dal_version = dal_ver; /* up sw */ return 0; } static int linux_get_dma_info(unsigned long arg) { dma_info_t dma_para; if (copy_from_user(&dma_para, (void*)arg, sizeof(dma_info_t))) { return -EFAULT; } dma_para.phy_base = (unsigned int)dma_phy_base[dma_para.lchip]; dma_para.phy_base_hi = dma_phy_base[dma_para.lchip] >> 32; dma_para.size = dma_mem_size; if (copy_to_user((dma_info_t*)arg, (void*)&dma_para, sizeof(dma_info_t))) { return -EFAULT; } return 0; } static int dal_get_msi_info(unsigned long arg) { dal_msi_info_t msi_para; unsigned int lchip = 0; /* get lchip form user mode */ if (copy_from_user(&msi_para, (void*)arg, sizeof(dal_msi_info_t))) { return -EFAULT; } lchip = msi_para.lchip; msi_para.irq_base = msi_irq_base[lchip]; msi_para.irq_num = msi_irq_num[lchip]; /* send msi info to user mode */ if (copy_to_user((dal_msi_info_t*)arg, (void*)&msi_para, sizeof(dal_msi_info_t))) { return -EFAULT; } return 0; } static int dal_get_intr_info(unsigned long arg) { dal_intr_info_t intr_para; unsigned int intr_num = 0; /* get lchip form user mode */ if (copy_from_user(&intr_para, (void*)arg, sizeof(dal_intr_info_t))) { return -EFAULT; } intr_para.irq_idx = CTC_MAX_INTR_NUM; for (intr_num=0; intr_num< CTC_MAX_INTR_NUM; intr_num++) { if (intr_para.irq == dal_isr[intr_num].irq) { intr_para.irq_idx = intr_num; break; } } if (CTC_MAX_INTR_NUM == intr_para.irq_idx) { printk("Interrupt %d cann't find.\n", intr_para.irq); } /* send msi info to user mode */ if (copy_to_user((dal_intr_info_t*)arg, (void*)&intr_para, sizeof(dal_intr_info_t))) { return -EFAULT; } return 0; } static int dal_cache_inval(unsigned long arg) { dal_dma_cache_info_t intr_para; if (copy_from_user(&intr_para, (void*)arg, sizeof(dal_dma_cache_info_t))) { return -EFAULT; } #if 0 dma_cache_wback_inv((unsigned long)intr_para.ptr, intr_para.length); #endif #if 0 dma_sync_single_for_cpu(NULL, intr_para.ptr, intr_para.length, DMA_BIDIRECTIONAL); dma_cache_sync(NULL, (void*)intr_para.ptr, intr_para.length, DMA_BIDIRECTIONAL); #endif return 0; } static int dal_cache_flush(unsigned long arg) { dal_dma_cache_info_t intr_para; if (copy_from_user(&intr_para, (void*)arg, sizeof(dal_dma_cache_info_t))) { return -EFAULT; } #if 0 dma_cache_wback_inv(intr_para.ptr, intr_para.length); #endif #if 0 dma_sync_single_for_cpu(NULL, intr_para.ptr, intr_para.length, DMA_BIDIRECTIONAL); dma_cache_sync(NULL, (void*)intr_para.ptr, intr_para.length, DMA_BIDIRECTIONAL); #endif return 0; } int linux_dal_probe(struct pci_dev* pdev, const struct pci_device_id* id) { dal_kern_dev_t* dev = NULL; int bar = 0; int ret = 0; unsigned int temp = 0; unsigned int lchip = 0; printk(KERN_WARNING "********found dal device*****\n"); for (lchip = 0; lchip < DAL_MAX_CHIP_NUM; lchip ++) { if (NULL == dal_dev[lchip].pci_dev) { break; } } if (lchip >= DAL_MAX_CHIP_NUM) { printk("Exceed max local chip num\n"); return -1; } dev = &dal_dev[lchip]; if (NULL == dev) { printk("Cannot obtain PCI resources\n"); } lchip = lchip; dal_chip_num += 1; dev->pci_dev = pdev; if (pci_enable_device(pdev) < 0) { printk("Cannot enable PCI device: vendor id = %x, device id = %x\n", pdev->vendor, pdev->device); } ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); if (ret) { ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); if (ret) { printk("Could not set PCI DMA Mask\n"); return ret; } } if (pci_request_regions(pdev, DAL_NAME) < 0) { printk("Cannot obtain PCI resources\n"); } dev->phys_address = pci_resource_start(pdev, bar); dev->logic_address = (uintptr)ioremap_nocache(dev->phys_address, pci_resource_len(dev->pci_dev, bar)); _dal_pci_read(lchip, 0x48, &temp); if (((temp >> 8) & 0xffff) == 0x3412) { printk("Little endian Cpu detected!!! \n"); _dal_pci_write(lchip, 0x48, 0xFFFFFFFF); } pci_set_master(pdev); /* alloc dma_mem_size for every chip */ if (dma_mem_size) { dal_alloc_dma_pool(lchip, dma_mem_size); /*add check Dma memory pool cannot cross 4G space*/ if ((0==(dma_phy_base[lchip]>>32)) && (0!=((dma_phy_base[lchip]+dma_mem_size)>>32))) { printk("Dma malloc memory cross 4G space!!!!!! \n"); return -1; } } printk(KERN_WARNING "linux_dal_probe end*****\n"); return 0; } void linux_dal_remove(struct pci_dev* pdev) { unsigned int lchip = 0; unsigned int flag = 0; for (lchip = 0; lchip < DAL_MAX_CHIP_NUM; lchip ++) { if (pdev == dal_dev[lchip].pci_dev) { flag = 1; break; } } if (1 == flag) { dal_free_dma_pool(lchip); pci_release_regions(pdev); pci_disable_device(pdev); dal_dev[lchip].pci_dev = NULL; dal_chip_num--; } } #ifdef CONFIG_COMPAT static long linux_dal_ioctl(struct file* file, unsigned int cmd, unsigned long arg) #else #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) static int linux_dal_ioctl(struct file* file, unsigned int cmd, unsigned long arg) #else static int linux_dal_ioctl(struct inode* inode, struct file* file, unsigned int cmd, unsigned long arg) #endif #endif { switch (cmd) { case CMD_READ_CHIP: return dal_pci_read(arg); case CMD_WRITE_CHIP: return dal_pci_write(arg); case CMD_GET_DEVICES: return linux_get_device(arg); case CMD_GET_DAL_VERSION: return linux_get_dal_version(arg); case CMD_GET_DMA_INFO: return linux_get_dma_info(arg); case CMD_PCI_CONFIG_READ: return dal_user_read_pci_conf(arg); case CMD_PCI_CONFIG_WRITE: return dal_user_write_pci_conf(arg); case CMD_REG_INTERRUPTS: return dal_user_interrupt_register(arg); case CMD_UNREG_INTERRUPTS: return dal_user_interrupt_unregister(arg); case CMD_EN_INTERRUPTS: return dal_user_interrupt_set_en(arg); case CMD_SET_MSI_CAP: return dal_set_msi_cap(arg); case CMD_GET_MSI_INFO: return dal_get_msi_info(arg); case CMD_IRQ_MAPPING: return dal_create_irq_mapping(arg); case CMD_GET_INTR_INFO: return dal_get_intr_info(arg); case CMD_CACHE_INVAL: return dal_cache_inval(arg); case CMD_CACHE_FLUSH: return dal_cache_flush(arg); default: break; } return 0; } static unsigned int linux_dal_poll0(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[0], p); local_irq_save(flags); if (poll_intr_trigger[0]) { poll_intr_trigger[0] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll1(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[1], p); local_irq_save(flags); if (poll_intr_trigger[1]) { poll_intr_trigger[1] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll2(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[2], p); local_irq_save(flags); if (poll_intr_trigger[2]) { poll_intr_trigger[2] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll3(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[3], p); local_irq_save(flags); if (poll_intr_trigger[3]) { poll_intr_trigger[3] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll4(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[4], p); local_irq_save(flags); if (poll_intr_trigger[4]) { poll_intr_trigger[4] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll5(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[5], p); local_irq_save(flags); if (poll_intr_trigger[5]) { poll_intr_trigger[5] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll6(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[6], p); local_irq_save(flags); if (poll_intr_trigger[6]) { poll_intr_trigger[6] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static unsigned int linux_dal_poll7(struct file* filp, struct poll_table_struct* p) { unsigned int mask = 0; unsigned long flags; poll_wait(filp, &poll_intr[7], p); local_irq_save(flags); if (poll_intr_trigger[7]) { poll_intr_trigger[7] = 0; mask |= POLLIN | POLLRDNORM; } local_irq_restore(flags); return mask; } static struct pci_driver linux_dal_driver = { .name = DAL_NAME, .id_table = dal_id_table, .probe = linux_dal_probe, .remove = linux_dal_remove, }; static struct file_operations fops = { .owner = THIS_MODULE, #ifdef CONFIG_COMPAT .compat_ioctl = linux_dal_ioctl, .unlocked_ioctl = linux_dal_ioctl, #else #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) .unlocked_ioctl = linux_dal_ioctl, #else .ioctl = linux_dal_ioctl, #endif #endif }; static int __init linux_dal_init(void) { int ret = 0; /* Get DMA memory pool size form dal.ok input param, or use default dma_mem_size */ if (dma_pool_size) { if ((dma_pool_size[strlen(dma_pool_size) - 1] & ~0x20) == 'M') { dma_mem_size = simple_strtoul(dma_pool_size, NULL, 0); printk("dma_mem_size: 0x%x \n", dma_mem_size); dma_mem_size *= MB_SIZE; } else { printk("DMA memory pool size must be specified as e.g. dma_pool_size=8M\n"); } if (dma_mem_size & (dma_mem_size - 1)) { printk("dma_mem_size must be a power of 2 (1M, 2M, 4M, 8M etc.)\n"); dma_mem_size = 0; } } ret = register_chrdev(DAL_DEV_MAJOR, DAL_NAME, &fops); if (ret < 0) { printk(KERN_WARNING "Register linux_dal device, ret %d\n", ret); return ret; } ret = pci_register_driver(&linux_dal_driver); if (ret < 0) { printk(KERN_WARNING "Register ASIC PCI driver failed, ret %d\n", ret); return ret; } /* alloc /dev/linux_dal node */ dal_class = class_create(THIS_MODULE, DAL_NAME); device_create(dal_class, NULL, MKDEV(DAL_DEV_MAJOR, 0), NULL, DAL_NAME); /* init interrupt function */ intr_handler_fun[0] = intr0_handler; intr_handler_fun[1] = intr1_handler; intr_handler_fun[2] = intr2_handler; intr_handler_fun[3] = intr3_handler; intr_handler_fun[4] = intr4_handler; intr_handler_fun[5] = intr5_handler; intr_handler_fun[6] = intr6_handler; intr_handler_fun[7] = intr7_handler; return ret; } static void __exit linux_dal_exit(void) { device_destroy(dal_class, MKDEV(DAL_DEV_MAJOR, 0)); class_destroy(dal_class); unregister_chrdev(DAL_DEV_MAJOR, "linux_dal"); pci_unregister_driver(&linux_dal_driver); } module_init(linux_dal_init); module_exit(linux_dal_exit);