sonic-buildimage/platform/centec/sonic-platform-modules-e582/48x2q4z/modules/dal_kernel.c

1834 lines
43 KiB
C
Raw Normal View History

/**
@file dal_kernal.c
@date 2012-10-18
@version v2.0
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/types.h>
#include <asm/io.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <asm/irq.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include <linux/dma-mapping.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 0, 0))
#include <linux/irqdomain.h>
#endif
#include "dal_kernel.h"
#include "dal_mpool.h"
#include <linux/slab.h>
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 <linux/slab.h>
#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;
}
else
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 79))
ret = pci_enable_msi_exact(dal_dev[lchip].pci_dev, irq_num);
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 26, 32))
ret = pci_enable_msi_block(dal_dev[lchip].pci_dev, irq_num);
#else
ret = -1;
#endif
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] = irq_num;
}
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);