学习Linux源码要先从大方向去把握,掌握好代码的整体框架,设计思想以后再去探究细节上的东西。这样可以让整个学习事半功倍,有效的提升学习的效率。分析这个代码我们也采用这样的方式,先从大方向入手,了解了整个脉络了以后,再针对细枝末节一个个解析。
static struct pci_driver lpc_sich_driver = {
.name = "lpc_sich",
.id_table = lpc_sich_ids,
.probe = lpc_sich_probe,
.remove = lpc_sich_remove,
};
首先是驱动结构体,大体上所有驱动都需要这么一个结构体,module_pci_driver会将这个驱动注册进内核。
name就是这个驱动的名称,用一个字符串表示;id_table是使用到这个驱动的设备列表;
probe很重要,是匹配设备和驱动的关键,没有它设备无法正常工作起来; remove则是在删除驱动时用的上的东西。
// id_table代码
static DEFINE_PCI_DEVICE_TABLE(lpc_sich_ids) = {
// PCI_VENDOR_ID_JN 厂商号
// PCI_DEVICE_ID_SICH_LPC 设备ID,本文件是配置的lpc驱动
{ PCI_DEVICE(PCI_VENDOR_ID_JN, PCI_DEVICE_ID_SICH_LPC) },
{ 0, }
};
接着解析lpc_sich_probe,即probe函数。 probe函数在设备驱动注册最后收尾工作,当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用函数以及其他相关工作。
文件作者在这里使用了一个自定义的结构体
struct lpc_sich_adapter {
void __iomem *hst_regs; // 驱动的基地址
struct device *dev; // 设备信息
int irq; // 驱动
struct irq_chip_generic *gc; // 中断片信息
unsigned features; // 使用到的技术
};
注册过程开始前首先就为结构体分配内存
lpc_adapter = kzalloc(sizeof(*lpc_adapter), GFP_KERNEL);
lpc_sich_adapter内存分配成功后,调用pci_enable_device(pdev)使能设备。
调用pci_request_selected_regions(pdev, 1, KBUILD_MODNAME)为设备申请内存.
// 为lpc_adaper变量赋值
lpc_adapter->dev = &pdev->dev;
lpc_adapter->hst_regs = pci_iomap(pdev, LPC_HST_BAR, 0); // hst是High-Speed Transfer
lpc_adapter->features = 0;
// 设置主设备
pci_set_master(pdev);
// todo
set_bit(LPC_USE_MSI, &lpc_adapter->features);
ret = pci_enable_msi(pdev);
irq_chip_generic结构体是用来描述驱动中断的结构体,该结构体需要进行很多设置,最主要的是ct属性,也就是chip_type。
c = irq_alloc_generic_chip("LPC_SICH", num_ct, irq_base,
lpc_adapter->hst_regs,
handle_level_irq);
ct = gc->chip_types;
ct->regs.mask = LPC_IRQ_MASK;
ct->regs.ack = LPC_IRQ;
ct->chip.irq_mask = irq_gc_mask_set_bit;
ct->chip.irq_unmask = irq_gc_mask_clr_bit;
ct->chip.irq_ack = irq_gc_ack_set_bit;
irq_setup_generic_chip(gc, IRQ_MSK(LPC_NR_IRQS), 0, 0, IRQ_NOPROBE | IRQ_LEVEL);
后续会调用一些函数对细节上进行一些设置,irq_set_handler_data用来设置中断,
lpc_enable自定义函数,通过修改寄存器的方式使能设备
lpc_mem_flash_init/lpc_fw_flash_init 这两个函数初始化内存资源,因为是通过pci进行连接的,所以直接走的总线。
irq_set_handler_data(lpc_adapter->irq, lpc_adapter);
irq_set_chained_handler(lpc_adapter->irq, (irq_flow_handler_t) lpc_irq_handler_mfd);
pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
pci_command |= PCI_COMMAND_IO;
pci_write_config_word(pdev, PCI_COMMAND, pci_command);
pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
dev_info(&pdev->dev, "pci command = %#x\n", pci_command);
lpc_enable(lpc_adapter);
lpc_mem_flash_init(pdev,lpc_adapter);
lpc_fw_flash_init(pdev,lpc_adapter);
下面这段话需要注意下,因为lpc也属于mfd设备(mutilfunction device), 因此需要调用mfd_add_devices
lpc_sich_cells, ARRAY_SIZE(lpc_sich_cells), NULL,
0, NULL);
后续再使能设备中断, 将pdev写入适配器,就基本上完成了probe函数
lpc_enable_irqs(lpc_adapter);
pci_set_drvdata(pdev, lpc_adapter);
一路分析下来, 实际上整个文件都是在围绕着probe这个点做文章,在probe中需要对各个结构体做大量设置,以及调用若干函数,使得设备和驱动在总线上做匹配, 这些都需再以后做大量的积累。只有多去学习,理解,多去写,才能做到看到驱动也不发慌。