Skip to content

Instantly share code, notes, and snippets.

@proywm
Last active June 23, 2018 23:40
Show Gist options
  • Save proywm/15b38a293770b0aaa99eaf1f03a2c9f7 to your computer and use it in GitHub Desktop.
Save proywm/15b38a293770b0aaa99eaf1f03a2c9f7 to your computer and use it in GitHub Desktop.
DMAdriver
#define BAR 0
#define DMA_BITS 28
#define DMA_START 0x40000
#define DMA_SIZE 4096
#define DMA_IRQ 4
#define DMA_CMD 1
#define CDEV_NAME "lkmc_pci"
#define EDU_DEVICE_ID 0x11e8
#define IO_IRQ_ACK 0x64
#define IO_IRQ_STATUS 0x24
#define IO_DMA_SRC 0x80
#define IO_DMA_DST 0x88
#define IO_DMA_XCNT 0x90
#define IO_DMA_CMD 0x98
#define QEMU_VENDOR_ID 0x1234
MODULE_LICENSE("GPL");
static struct pci_device_id pci_ids[] = {
{ PCI_DEVICE(QEMU_VENDOR_ID, EDU_DEVICE_ID), },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, pci_ids);
static int pci_irq;
static int major;
static struct pci_dev *pdev;
static void __iomem *mmio;
static ContextFrame *rootframe = NULL;
dma_addr_t dma_handle_to_device;
void *vaddr_to_device;
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
ssize_t ret;
u32 kbuf;
if (*off % 4 || len == 0) {
ret = 0;
} else {
kbuf = ioread32(mmio + *off);
if (copy_to_user(buf, (void *)&kbuf, 4)) {
ret = -EFAULT;
} else {
ret = 4;
(*off)++;
}
}
return ret;
}
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
ssize_t ret;
u32 kbuf;
ret = len;
if (!(*off % 4)) {
if (copy_from_user((void *)&kbuf, buf, 4) || len != 4) {
ret = -EFAULT;
} else {
iowrite32(kbuf, mmio + *off);
}
}
return ret;
}
static loff_t llseek(struct file *filp, loff_t off, int whence)
{
filp->f_pos = off;
return off;
}
/* These fops are a bit daft since read and write interfaces don't map well to IO registers.
*
* One ioctl per register would likely be the saner option. But we are lazy.
*
* We use the fact that every IO is aligned to 4 bytes. Misaligned reads means EOF. */
static struct file_operations fops = {
.owner = THIS_MODULE,
.llseek = llseek,
.read = read,
.write = write,
};
//#define current_sp() ({ void *sp; __asm__("movq %%rsp, %0" : "=r" (sp) : ); sp; })
/*-----------------------------------------------------------------------------------------------
* XXX Some Information relating to DMA Transfer (Refer to DMA-API-HOWTO.txt)
*
If the device supports DMA, the driver sets up a buffer using kmalloc() or
a similar interface, which returns a virtual address (X). The virtual
memory system maps X to a physical address (Y) in system RAM. The driver
can use virtual address X to access the buffer, but the device itself
cannot because DMA doesn't go through the CPU virtual memory system.
In some simple systems, the device can do DMA directly to physical address
Y. But in many others, there is IOMMU hardware that translates DMA
addresses to physical addresses, e.g., it translates Z to Y. This is part
of the reason for the DMA API: the driver can give a virtual address X to
an interface like dma_map_single(), which sets up any required IOMMU
mapping and returns the DMA address Z. The driver then tells the device to
do DMA to Z, and the IOMMU maps it to the buffer at address Y in system
RAM.
*
*-------------------------------------------------------------------------------------------------
*/
/*-------------------------------------------------------------------------------------------------
* Streaming DMA Mapping (dma_map_single). HyPerfDriver only requires to send data to HyPerf device
* Mapping single memory region.
* HyPerf device has One 4096 bytes long buffer at offset 0x40000 . Pushing data there using DMA
*--------------------------------------------------------------------------------------------------
*/
static int write_to_HyPerf(void *dev, uint32_t size)
{
int ret = 0;
if(size >= DMA_SIZE)
return -1;
/* ----------------------------------------------------------------------------
* Set up the device
* Example of transferring a 100 byte block to and from the buffer using a given
* PCI address 'addr':
* addr -> DMA source address
* 0x40000 -> DMA destination address
* 100 -> DMA transfer count
* 1 -> DMA command register
* while (DMA command register & 1)
*--------------------------------------------------------------------------------
*/
#if 1
iowrite32((u32 *)dma_handle_to_device, mmio + IO_DMA_SRC);
iowrite32(DMA_START, mmio + IO_DMA_DST);
iowrite32((u32 *)size, mmio + IO_DMA_XCNT);
u32 cmd_status = 0;
// do{
// iowrite32(1, mmio + IO_DMA_CMD);
// cmd_status = ioread32(mmio + IO_DMA_CMD);
// }while(cmd_status & 1); /* Keep try sending */
iowrite32(DMA_CMD | DMA_IRQ, mmio + IO_DMA_CMD);
#endif
return ret;
err_dma:
return ret;
}
static irqreturn_t irq_handler(int irq, void *dev)
{
int devi;
irqreturn_t ret = 0;
u32 irq_status;
devi = *(int *)dev;
if (devi == major) {
irq_status = ioread32(mmio + IO_IRQ_STATUS);
// pr_info("interrupt irq = %d dev = %d irq_status = %llx\n",
// irq, devi, (unsigned long long)irq_status);
/* Must do this ACK, or else the interrupts just keeps firing. */
iowrite32(irq_status, mmio + IO_IRQ_ACK);
ret = IRQ_HANDLED;
} else {
ret = IRQ_NONE;
}
if(irq_status == EVENT_IRQ)
{
/* Perform hyperf task */
int size = 0;
if(vaddr_to_device != NULL)
{
getInteruptedStack(vaddr_to_device, &size);
if(size == 0)
return ret;
pr_info("size %d and context size %d \n", size, sizeof(ContextFrame));
/* sending requested information to HyPerf device */
ret = write_to_HyPerf(dev, size);
if(ret)
pr_info("Failed to send data to device %d \n", ret);
ret = 0;
}
}
return ret;
}
/**
* Called just after insmod if the hardware device is connected,
* not called otherwise.
*
* 0: all good
* 1: failed
*/
static int pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
u8 val;
/* At first, find kernel APIs */
if(find_kernel_api())
{
pr_info("Could not find kernel APIs. Exiting....\n");
goto error;
}
/* allocating buffer for request handling */
rootframe = kzalloc(sizeof(ContextFrame) * MAXCONTEXTFRAMES, GFP_KERNEL);
if (!rootframe)
{
pr_info("Could not allocate buffer for HyPerf Context\n");
goto error;
}
pr_info("pci_probe\n");
major = register_chrdev(0, CDEV_NAME, &fops);
pdev = dev;
/* enable device */
if (pci_enable_device(dev) < 0) {
dev_err(&(pdev->dev), "pci_enable_device\n");
goto error;
}
/* enable bus master capability on device for DMA*/
pci_set_master(dev);
/* Reserves PCI I/O and memory resource. HyPerf device has only one BAR (Base Address Register) IO */
if (pci_request_region(dev, BAR, DRV_NAME)) {
dev_err(&(pdev->dev), "pci_request_region\n");
goto error;
}
/* HyPerf device expects 28 bit DMA. Enforce it. */
if (pci_set_dma_mask(pdev, DMA_BIT_MASK(DMA_BITS))) {
dev_err(&(pdev->dev), "pci_set_dma_mask\n");
goto error;
}
/* map the device memory or IO region into kernel virtual address space */
mmio = pci_iomap(pdev, BAR, pci_resource_len(pdev, BAR));
/* IRQ setup. */
pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &val); /* identify the irq line */
pci_irq = val;
if (request_irq(pci_irq, irq_handler, IRQF_SHARED, DRV_NAME, &major) < 0) {
dev_err(&(dev->dev), "request_irq\n");
goto error;
}
/* Optional sanity checks. The PCI is ready now, all of this could also be called from fops. */
{
unsigned i;
/* Check that we are using MEM instead of IO.
*
* In QEMU, the type is defiened by either:
*
* - PCI_BASE_ADDRESS_SPACE_IO
* - PCI_BASE_ADDRESS_SPACE_MEMORY
*/
if ((pci_resource_flags(dev, BAR) & IORESOURCE_MEM) != IORESOURCE_MEM) {
dev_err(&(dev->dev), "pci_resource_flags\n");
goto error;
}
/* 1Mb, as defined by the "1 << 20" in QEMU's memory_region_init_io. Same as pci_resource_len. */
resource_size_t start = pci_resource_start(pdev, BAR);
resource_size_t end = pci_resource_end(pdev, BAR);
pr_info("length %llx\n", (unsigned long long)(end + 1 - start));
/* The PCI standardized 64 bytes of the configuration space, see LDD3. */
for (i = 0; i < 64u; ++i) {
pci_read_config_byte(pdev, i, &val);
pr_info("config %x %x\n", i, val);
}
pr_info("irq %x\n", pci_irq);
/* Initial value of the IO memory. */
for (i = 0; i < 0x28; i += 4) {
pr_info("io %x %x\n", i, ioread32((void*)(mmio + i)));
}
}
/* Setting up DMA Buffer */
vaddr_to_device = dma_alloc_coherent(&(dev->dev), DMA_SIZE, &dma_handle_to_device, GFP_ATOMIC);
return 0;
error:
return 1;
}
static void pci_remove(struct pci_dev *dev)
{
pr_info("pci_remove\n");
/* release the irq */
free_irq(pci_irq, &major);
/* freeing DMA Buffer */
dma_free_coherent(&(dev->dev), DMA_SIZE, vaddr_to_device, dma_handle_to_device);
/* unmapping memory mapped region */
pci_iounmap(dev, mmio);
/* release the I/O ports & memory */
pci_release_region(dev, BAR);
/* Disable bus master capability */
pci_clear_master(dev);
/* disable the PCI entry */
pci_disable_device(dev);
/* Unregistering char dev */
unregister_chrdev(major, CDEV_NAME);
/* Freeing up buffer */
kfree(rootframe);
}
static struct pci_driver pci_driver = {
.name = CDEV_NAME,//"lkmc_pci",
.id_table = pci_ids,
.probe = pci_probe,
.remove = pci_remove,
};
static int __init myinit(void)
{
if (pci_register_driver(&pci_driver) < 0) {
return 1;
}
return 0;
}
static void __exit myexit(void)
{
pci_unregister_driver(&pci_driver);
}
module_init(myinit);
module_exit(myexit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment