Linux下的PCI设备驱动自动加载问题MODULE_DEVICE_TABLE

51 篇文章 0 订阅

以前做驱动时,一般将驱动复制到/lib/modules/$(uname -r)/kernel/driver/目录后,运行depmod都可以自动加载,但是客户反映公司一款驱动无法自动加载。后经过与其它版本代码对比,才发现是MODULE_DEVICE_TABLE没有设置引起的异常。

在Linux IIC驱动中看到一段代码:

static struct platform_device_id xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data   = TYPE_S3C2410,
}, {
.name = "s3c2440-i2c",
.driver_data   = TYPE_S3C2440,
}, { },
};
MODULE_DEVICE_TABLE(platform, xx_driver_ids);

MODULE_DEVICE_TABLE一般用在热插拔的设备驱动中。

上述xx_driver_ids结构,是此驱动所支持的设备列表

作用是:将xx_driver_ids结构输出到用户空间,这样模块加载系统在加载模块时,就知道了什么模块对应什么硬件设备。

用法是:MODULE_DEVICE_TABLE(设备类型,设备表),其中,设备类型,包括USB,PCI等,也可以自己起名字,上述代码中是针对不同的平台分的类;设备表也是自己定义的,它的最后一项必须是空,用来标识结束。

按照此方法安装后系统后,在/lib/modules/$(uname -r)/目录中,modules.alias.modules.pcimap文件中应该均可以找到驱动模块名称。此时就可以正常开机自动加载设备驱动了。

1. MODULE_DEVICE_TABLE (pci, skel_table);
该宏生成一个名为__mod_pci_device_table的局部变量,该变量指向第二个参数。内核构建时,depmod程序会在所有模块中搜索符号__mod_pci_device_table,把数据(设备列表)从模块中抽出,添加到映射文件/lib/modules/KERNEL_VERSION/modules.pcimap中,当depmod结束之后,所有的PCI设备连同他们的模块名字都被该文件列出。当内核告知热插拔系统一个新的PCI设备被发现时,热插拔系统使用modules.pcimap文件来找寻恰当的驱动程序。 
MODULE_DEVICE_TABLE的第一个参数是设备的类型,如果是USB设备,那自然是usb(如果是PCI设备,那将是pci,这两个子系统用同一个宏来注册所支持的设备)。后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。例:假如代码定义了USB_SKEL_VENDOR_ID是 0xfff0,USB_SKEL_PRODUCT_ID是0xfff0,也就是说,当有一个设备接到集线器时,usb子系统就会检查这个设备的 vendor ID和product ID,如果他们的值是0xfff0时,那么子系统就会调用这个模块作为设备的驱动。
2. 其他相关宏的定义      
这些宏定义在<linux/module.h>下
1)MODULE_AUTHOR(name) 定义驱动的编程者,name为string
2)MODULE_LICENSE(license) 定义驱动的license,一般为GPL,或相关公司的license
3)MODULE_DESCRIPTION(desc) 对驱动程序的描述,string
4)MODULE_SUPPORTED_DEVICE(name) 驱动程序所支持的设备,string
5)MODULE_PARM(var,type)
提供在运行时通过控制台将参数传递给模块(在insmod时)。如果我们想用这个宏来传递命令行参数,那么在我们的模块中定义一个全局变量.在insmod模块时,便可以用参数的形式,将具体的实参传递给模块中的那个全局变量.
MODULE_PARM(name,type)
MODULE_PARM(name,type)
MODULE_PARM(name,type)有两个参数,一个是这个全局变量的名称,另一个是这个全局变量的类型.
而他的类型有一下几种:
b:
b:
b:比特型
h:短整型
i:整型
l:长整型
s:字符串型
在传递字符串型的参数时,这个全局变量需要在模块中用Char *来声明!insmod会自动为其分配内存空间。
例如:
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
int a = 3;
char *st;
MODULE_PARM(a,”i”);
MODULE_PARM(st,”s”);
在insmod是我们加这样的参数:
insmode a.o “a = 3″, “st = hello world”
这里最重要的是,MODULE_PARM()也支持我们最常用的数组类型。用短线‘-’把两个数字分开,分别表示数组参数中的最小位数和最大位数。例如:
int array[8];
MODULE_PARM(array,”1-8i”);
在命令行我们使用加这样的参数:
insmod a.o “array = 38745,123,4000″
insmod a.o “array = 38745,123,4000″
insmod a.o “array = 38745,123,4000″ 
在那些模块编程时,我们往往给这些全局变量以默认值,如果我们才insmod时没有传入参数时,模块会使用这些默认值,而如果我们传入参数时,这些默认值便被覆盖掉。
6)MODULE_PARM_DESC(var,desc) 对变量的描述
7)GPL_HEADER()
8)THIS_MODULE 指向全局变量 __this_module (struct module)的指针。
9)系统对每个模块维护一个usage counter,以便决定何时可以安全的卸载模块。
下面的宏用来对该usage counter操作,usage counter可以通过/proc/modules文件查看
MOD_INC_USE_COUNT 
MOD_DEC_USE_COUNT
MOD_IN_USE
MODULE_DEVICE_TABLE
10)EXPORT_SYMTAB 预处理宏,当在程序中用EXPORT_SYMBOL等宏时需要定义该宏。例如,可以在Makefile中定义:-DEXPORT_SYMTAB
__EXPORT_SYMBOL(sym,str)
EXPORT_SYMBOL(var)
11)EXPORT_SYMBOL_NOVERS(var) 导出一个符合到内核符号表,导出后,该符合可以供其他模块使用。这个宏有助于编写驱动程序时清楚的划分出层次。可以通过/proc/ksyms文件或ksyms命令查看内核符号表。EXPORT_SYMBOL_NOVERS(var),导出是不带版本信息。在使用该宏时,需定义 EXPORT_SYMBOL_GPL(var)
12)EXPORT_NO_SYMBOLS 显示指出,该模块不向内核符合表导出符号
13)SET_MODULE_OWNER


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以给你一个简单的Linux PCI驱动程序示例,它使用remap_pfn_range将BAR0映射到用户空间。请注意,这个示例代码只是用于说明如何使用remap_pfn_range,你需要根据你的具体需求进行修改和扩展。 首先,我们需要定义一个`pci_driver`结构体,用于注册我们的PCI设备驱动程序。下面是一个简单的例子: ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/pci.h> static struct pci_device_id my_pci_ids[] = { { PCI_DEVICE(0x1234, 0xabcd) }, /* 请替换成你的PCI设备的厂商ID和设备ID */ { 0, }, }; MODULE_DEVICE_TABLE(pci, my_pci_ids); static int my_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { int err; resource_size_t bar0_start, bar0_len; void __iomem *bar0_virt; printk(KERN_INFO "my_pci_probe: found device %04x:%04x\n", dev->vendor, dev->device); /* 映射BAR0 */ bar0_start = pci_resource_start(dev, 0); bar0_len = pci_resource_len(dev, 0); bar0_virt = pci_iomap(dev, 0, bar0_len); if (!bar0_virt) { printk(KERN_ERR "my_pci_probe: failed to map BAR0\n"); return -ENOMEM; } /* 将BAR0映射到用户空间 */ err = remap_pfn_range(vma, vma->vm_start, virt_to_phys(bar0_virt) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); if (err) { printk(KERN_ERR "my_pci_probe: failed to remap BAR0\n"); pci_iounmap(dev, bar0_virt); return err; } /* 在这里执行你的PCI设备初始化代码 */ return 0; } static void my_pci_remove(struct pci_dev *dev) { void __iomem *bar0_virt; bar0_virt = pci_iomap(dev, 0, pci_resource_len(dev, 0)); if (bar0_virt) { pci_iounmap(dev, bar0_virt); } /* 在这里执行你的PCI设备清理代码 */ } static struct pci_driver my_pci_driver = { .name = "my_pci_driver", .id_table = my_pci_ids, .probe = my_pci_probe, .remove = my_pci_remove, }; static int __init my_pci_init(void) { int err; err = pci_register_driver(&my_pci_driver); if (err) { printk(KERN_ERR "my_pci_init: failed to register PCI driver\n"); } return err; } static void __exit my_pci_exit(void) { pci_unregister_driver(&my_pci_driver); } module_init(my_pci_init); module_exit(my_pci_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); ``` 在上面的驱动程序中,我们定义了一个`pci_driver`结构体,其中包含了我们的PCI设备的厂商ID和设备ID。在驱动程序初始化时,我们使用`pci_register_driver`函数注册了这个驱动程序。当系统检测到我们的PCI设备时,内核会自动调用`my_pci_probe`函数。 在`my_pci_probe`函数中,我们首先映射了PCI设备的BAR0,然后使用`remap_pfn_range`函数将BAR0映射到用户空间。最后,我们执行了一些初始化代码。当我们要卸载驱动程序时,内核会自动调用`my_pci_remove`函数,其中我们清理了映射的内存区域,并执行了一些清理代码。 请注意,上面的代码只是一个简单的示例,如果你想要实际使用它,你需要根据你的具体需求进行修改和扩展。此外,我们还需要为我们的PCI设备添加一些设备树(Device Tree)节点,以便内核能够正确地识别和配置我们的设备

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值