2018年11月1日木曜日

[Linux] PCIe Endpoint Driver with Character Device Driver


好一陣子沒寫東西了
來紀錄一下最近做的東西

最近從 Windows driver 轉做 Linux driver
不知道是不是找資料的方式不對
還是 Linux 更新太快了
一直都找不到比較新的資料

Linux kernel 都已經更新到 4.19 了
結果大部分的資料都還在講 2.6.x
雖然絕大部分都可以向下相容
但總是有些東西沒辦法用

所以這篇用來紀錄一下
前陣子用 kernel 4.x 做 PCIe EP device driver 開發的東西
說是紀錄
但也就只是放個 pcie 的 device driver 的樣板上來而已
本來偷懶想直接全部複製貼上
不過因為有很多東西不能公開
所以只好貼通用的部份上來了

是說完整的內容太長了
所以還是分段貼好了...OTL

理論上下面這串程式直接做成一個.c檔
建成一個 kernel module 應該就可以用了
但不保證我手殘有很大的機會敲錯就是了...w



#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/init.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_pci.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/phy/phy.h>

#include <linux/irqreturn.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/dma-mapping.h>
#include <linux/wait.h>
#include <linux/completion.h>


#ifndef PCI_EP_ENGINE_DRIVER_VERSION_NUMBER
#define PCI_EP_ENGINE_DRIVER_VERSION_NUMBER "1.0"
#endif

#ifndef PCI_EP_ENGINE_NAME
#define PCI_EP_ENGINE_NAME "pcie_ep_device"
#endif

#ifndef PCI_EP_ENGINE_VENDOR_ID
/* 0x11ab for Marvell Technology Group Ltd.*/
/* ref. http://pci-ids.ucw.cz/read/PC */
#define PCI_EP_ENGINE_VENDOR_ID 0x11ab 
#endif

#ifndef PCI_EP_ENGINE_DEVICE_ID
/* 0x0023 for Marvell 88pa6220 */
#define PCI_EP_ENGINE_DEVICE_ID 0x0023
#endif

#ifndef PCI_EP_ENGINE_CLASS_ID
/* 0x08: Generic system peripheral */
/* 0xXX80: System peripheral */
/* 0xXXXX00: Program interfaces */
#define PCI_EP_ENGINE_CLASS_ID 0x088000
#endifux

#ifndef PCI_EP_ENGINE_CLASS_MASK
/* i am not sure what this is ... */
#define PCI_EP_ENGINE_CLASS_MASK 0xffffff00
#endif

// 0: for static; 1: for dynamic
#define PCI_EP_ENGINE_DYNAMIC_ALLOC_CHR_NUM 1

#ifndef PCI_EP_ENGINE_ALLOC_CHR_COUNT
#define PCI_EP_ENGINE_ALLOC_CHR_COUNT 1
#endif

#ifndef PCI_EP_ENGINE_CHAR_MAJOR
#define PCI_EP_ENGINE_CHAR_MAJOR 0
#endif

#ifndef PCI_EP_ENGINE_CHAR_MINOR
#define PCI_EP_ENGINE_CHAR_MINOR 0
#endif

#define PCI_EP_ENGINE_FUNCTION_IN_PRINT \
    printk(KERN_ALERT "%s(%d): in\n", __func__, __LINE__)

#define PCI_EP_ENGINE_FUNCTION_OUT_PRINT \
    printk(KERN_ALERT "%s(%d): out\n", __func__, __LINE__)

#define PCI_EP_ENGINE_PRINT_FUNCTION_CALLER \
    printk(KERN_ALERT "Caller is: %pS\n", __builtin_return_address(0))

static struct class * g_p_pci_ep_engine_class;
static struct device * g_p_pci_ep_engine_device;

static struct pci_device_id pci_ep_engine_ids[] = {
//{PCI_EP_ENGINE_VENDOR_ID, PCI_EP_ENGINE_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, /* how to determine the RC or EP with same VID & DID ? */
{ PCI_DEVICE_CLASS(PCI_EP_ENGINE_CLASS_ID, PCI_EP_ENGINE_CLASS_MASK) }, /* determine the EP which is a System peripheral */
{0,}
};
MODULE_DEVICE_TABLE(pci, pci_ep_engine_ids);

struct pci_ep_engine
{
    struct pci_device * pci_dev;
    struct cdev chr_dev;
    dev_t devno;
    unsigned long bar0_physical;
    unsigned long bar0_virtual;
    unsigned long bar0_length; 
} ep_engine_device;

static int pci_ep_engine_probe(struct pci_dev * pdev, const struct pci_device_id * id);
static void pci_ep_engine_remove(struct pci_dev * pdev);
static irqreturn_t pci_ep_engine_interrupt(int irq, void * pdev);

static int pci_ep_engine_probe(struct pci_dev * pdev, const struct pci_device_id * id)
{
    int i;
    int result;
    PCI_EP_ENGINE_FUNCTION_IN_PRINT; 
    pci_set_drvdata(pdev, ep_engine_device.pci_dev);

    if (pci_enable_device(pdev)) {
        PCI_EP_ENGINE_DEBUG_ERR("failed' pci_enable_device\n");
        result = -EIO;
        goto end;
    }

    pci_set_master(pdev);
    ep_engine_device.pci_dev = pdev;

    if (unlikely(pci_request_regions(pdev, PCI_EP_ENGINE_NAME))) {
        printk(KERN_ALERT "%s(%d): failed pci_request_regions\n", __func__, __LINE__);
        result = -EIO;
        goto enable_device_err;
    }

    // get bar 0 physical & virtual address
    ep_engine_device.bar0_physical = pci_resource_start(pdev, 0);
    if (ep_engine_device.bar0_physical < 0) {
        printk(KERN_ALERT "%s(%d): failed: pci_resource_start\n", __func__, __LINE__);
        result = -EIO;
        goto request_regions_err;
    } else {
        printk(KERN_ALERT "%s(%d): got pci resource: bar 0 physical address(0x%08x)\n", __func__, __LINE__, ep_engine_device.bar0_physical);
    }

    // get bar 0 virtual address
    ep_engine_device.bar0_length = pci_resource_len(pdev, 0);
    if (ep_engine_device.bar0_length != 0) {
        ep_engine_device.bar0_virtual = (unsigned long)ioremap(ep_engine_device.bar0_physical, ep_engine_device.bar0_length);
        printk(KERN_ALERT "%s(%d): bar 0 physical remap to virt: 0x%08x\n", __func__, __LINE__, ep_engine_device.bar0_virtual);
        printk(KERN_ALERT "%s(%d): bar 0 length: %lu\n", __func__, __LINE__, ep_engine_device.bar0_length);
    } else {
        printk(KERN_ALERT "%s(%d): failed: pci_resource_len get zero!!\n", __func__, __LINE__);
    }

    // enable MSI
    result = pci_enable_msi(pdev);
    if (unlikely(result)) {
        printk(KERN_ALERT "%s(%d): failed: pci_enable_msi\n", __func__, __LINE__);
        goto free_bar0;
    } else {
        printk(KERN_ALERT "%s(%d): enable msi succeeded.\n", __func__, __LINE__);
    }

    result = request_irq(pdev->irq, pci_ep_engine_interrupt, 0, PCI_EP_ENGINE_NAME, ep_engine_device.pci_dev);
    if (unlikely(result)) {
        printk(KERN_ALERT "%s(%d): failed: request_irq\n", __func__, __LINE__);
        goto enable_msi_error;
    } else {
        printk(KERN_ALERT "%s(%d): request_irq succeeded.\n", __func__, __LINE__);
    }

    goto end;
  
enable_msi_error:
    pci_disable_msi(pdev);
free_bar0:
    iounmap((void*)ep_engine_device.bar0_virtual);
request_regions_err:
    pci_release_regions(pdev);
enable_device_err:
    pci_disable_device(pdev);
end:
    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
    return result;   
}

static void pci_ep_engine_remove(struct pci_dev * pdev)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;

    free_irq(pdev->irq, ep_engine_device.pci_dev);
    pci_disable_msi(pdev);

    iounmap((void*)ep_engine_device.bar0_virtual);
    pci_release_regions(pdev);
    pci_disable_device(pdev);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
}

static irqreturn_t pci_ep_engine_interrupt(int irq, void * dev)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;
  
    // do something at here 

    PCI_VIRTIO_ENGINE_FUNCTION_OUT_PRINT;
    return IRQ_HANDLED;
}

static struct pci_driver pci_ep_engine_driver = {
    .name = PCI_EP_ENGINE_NAME,
    .id_table = pci_ep_engine_ids,
    .probe = pci_ep_engine_probe,
    .remove = pci_ep_engine_remove,
};

static int pci_ep_engine_open(struct inode * inode, struct file * file)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

int pci_ep_engine_close(struct inode * inode, struct file * file)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

long pci_ep_engine_unlocked_ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static ssize_t pci_ep_engine_read(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static ssize_t pci_ep_engine_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
{
 PCI_EP_ENGINE_FUNCTION_IN_PRINT;
 PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
 return 0;
}

static struct file_operations pci_ep_engine_fops = {
    .owner = THIS_MODULE,
    .open = pci_ep_engine_open,
    .release = pci_ep_engine_close,
    .unlocked_ioctl = pci_ep_engine_unlocked_ioctl,
    .read = pci_ep_engine_read,
    .write = pci_ep_engine_write,
};

static int pci_ep_engine_driver_init(void)
{
    int ret;
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;
    PCI_Ep_ENGINE_PRINT_FUNCTION_CALLER;
    
    ret = pci_register_driver(&pci_ep_engine_driver);
    if (ret < 0) {
        printk("failed: pci_register_driver\n");
        return ret;
    }

#if    PCI_EP_ENGINE_DYNAMIC_ALLOC_CHR_NUM
    ret = alloc_chrdev_region(&ep_engine_device.devno, 0, PCI_EP_ENGINE_ALLOC_CHR_COUNT, PCI_EP_ENGINE_NAME);        
#else
    ep_engine_device.devno = MKDEV(PCI_EP_ENGINE_CHAR_MAJOR, PCI_EP_ENGINE_CHAR_MINOR);
    ret = register_chrdev_region(ep_engine_device.devno, PCI_EP_ENGINE_CHAR_MINOR, PCI_EP_ENGINE_ALLOC_CHR_COUNT, PCI_EP_ENGINE_NAME);
#endif
    if (ret < 0) {
        printk("failed: register_chrdev_region\n");
        return ret;
    }

    cdev_init(&ep_engine_device.chr_dev, &pci_ep_engine_fops);
    ret = cdev_add(&ep_engine_device.chr_dev, ep_engine_device.devno, PCI_EP_ENGINE_ALLOC_CHR_COUNT);
    if (ret < 0) {
        printk("failed: cdev_add\n");
        return ret;
    }

    g_p_pci_ep_engine_class = class_create(THIS_MODULE, PCI_EP_ENGINE_NAME);
    g_p_pci_ep_engine_device = device_create(g_p_pci_ep_engine_class, NULL, ep_engine_device.devno, NULL , PCI_EP_ENGINE_NAME);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
    return 0;
}

static void pci_ep_engine_driver_exit(void)
{
    PCI_EP_ENGINE_FUNCTION_IN_PRINT;

    device_destroy(g_p_pci_ep_engine_class, ep_engine_device.devno);
    class_destroy(g_p_pci_ep_engine_class);

    cdev_del(&(ep_engine_device.chr_dev));
    unregister_chrdev_region(ep_engine_device.devno, PCI_EP_ENGINE_ALLOC_CHR_COUNT);
    pci_unregister_driver(&pci_ep_engine_driver);

    PCI_EP_ENGINE_FUNCTION_OUT_PRINT;
}

module_init(pci_ep_engine_driver_init);
module_exit(pci_ep_engine_driver_exit);

MODULE_AUTHOR("yuutan");
MODULE_LICENSE("GPL");
MODULE_VERSION(PCI_EP_ENGINE_DRIVER_VERSION_NUMBER);
MODULE_DESCRIPTION("ep engine over PCIe: device driver with character device driver");



0 件のコメント:

コメントを投稿