1.参考文档
网 址:http://en.wikipedia.org/wiki/Conventional_PCI ;
网 址:http://en.wikipedia.org/wiki/PCI_configuration_space ;
2.配置与初始化
在系统启动时,在 x86 上,BIOS 负责配置 PCI 设备。在其他平台上,Linux内核可以完成这项工作。但是,无论硬件目标如何,当您到达初始化Linux / Xenomai驱动程序时,PCI设备已经“配置”,您不必这样做。“配置”的PCI设备意味着其内存地址范围已被保留,其IRQ已分配给它等;设备尚未初始化! 初始化确实特定于每个设备,这是驱动程序的角色。Xenomai PCI驱动程序将使用Linux来发现设备的配置,检索其IRQ以及它使用的IO或内存的地址范围。我们不会尝试通过我们的驱动程序使Linux与此设备进行通信。Linux在这里被用作“发现”PCI总线的一种方式,仅此而已。 使用 Linux PCI API 和适当的 IO/内存操作函数可以创建跨平台 PCI 驱动程序。
3.逐步编写驱动程序
Xenomai PCI 驱动程序必须:
static struct pci_device_id peak_pci_tbl[] = { {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { } }; MODULE_DEVICE_TABLE (pci, peak_pci_tbl);
与大多数Linux“驱动程序层”一样,还需要注册将调用以驱动我们的PCI设备的回调。例如,tty,网卡,USB等就是这种情况。在这里,我们只关注PCI和Xenomai驱动程序。 因此,我们将仅使用Linux提供的最低限度:Linux在发现PCI设备或系统关闭时调用的“probe”和“remove”函数。其他回调与我们无关,我们无论如何都不想通过Linux直接访问硬件。 具体来说,在代码中,它给出了:
static struct pci_driver rtcan_peak_pci_driver = { .name = RTCAN_DRV_NAME, .id_table = peak_pci_tbl, .probe = peak_pci_init_one, .remove = __devexit_p(peak_pci_remove_one), }; static int __init rtcan_peak_pci_init(void) { return pci_register_driver(&rtcan_peak_pci_driver); } pci_read_config_word(pdev, 0x2e, &sub_sys_id)
另一个示例来自 Essential Linux Device Drivers(42)一书,用于读取与该卡关联的 IRQ:
unsigned char irq ; pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &irq);
因此,您不需要知道配置区域中的哪一个字节表示 BIOS 分配的 IRQ,在 include/linux/pci_regs.h 中找到的宏PCI_INTERRUPT_LINE就足够了。有关 PCI 卡配置区域中可恢复的宏和值的详细信息,请参阅此文件。 每个PCI设备最多有六个“基址寄存器”或“BAR”。这是什么? 简而言之,在64字节的配置中,6 * 4字节表示PCI卡将使用的内存区域。这六个地址范围的使用由卡制造商自行决定。值得参考卡的配置,以了解使用了多少个这些“BAR”。 在这些“BAR”中,一些是指IO地址,另一些是指内存地址;同样,是map的文档表明了这一点。 或联系制造商。 或者通过从Linux源代码“改造”。 ********************************************************************** 要开发 PCI 驱动程序,您必须具有卡的“开发人员”文档。有两种方法可以获得它。 理想的情况仍然是两者兼而有之(理论和实践都有效)。 但是,一些制造商不一定玩游戏,只在他们的卡上提供Linux模块(.ko文件)。 因此,在选择 材料时,这必须特别注意。 还应该注意的是,一些制造商单独出售其Linux驱动程序的代码,或者在签署保密协议(43)(例如TEWS Technologies)的情况下免费提供它们。 ****************************************** Linux在其PCI API中提供了一些函数,以了解是否定义了“BAR”以及它所引用的地址范围。因此,使用这些Linux功能,可以在驱动程序的初始化阶段检索我们感兴趣的地址范围,从而能够读取/写入来驱动我们的硬件。您可以引用函数 pci_resource_[start|len|end|flags] 这些函数将 Linux 传递的“PCI 描述符”作为第一个参数,将 bar 编号作为第二个参数。 例:
unsigned long addr; addr = pci_resource_start(pdev, 0);
注意: 32 位 PCI 总线处理 32 位地址,因此通常使用“无符号长整型”而不是“指针”,后者的大小因处理器的地址总线而异。“Long”由ANSI C标准在32位上定义,并且不因编译器/平台而异。因此,代码是可移植的。 对设备的读/写访问在 IO(PIO 访问方法)中完成,或直接在内存映射区域(MMIO 访问方法)中完成。是设备的文档提供了此信息。在驱动程序编程方面,唯一改变的是需要使用的API。
· io_base = pci_ressouce_start(my_pci_dev, 0);//这给出了 BAR0 的基本地址 · request_region(io_base, 1024, “my_driver_xenomai_pci”); //我们向 Linux 宣布,我们将使用 BAR0 基址中的 1024 个字节。在这里,假设设备的文档为我们提供了此信息。此功能主要用于检查其他驱动程序是否尚未访问此区域。在这种情况下,存在冲突(重叠),应返回错误代码。 · data = inb(io_base + 5);//我们访问 IO BAR0 区域的第 6 个字节。我们可以用同样的方式编写:outb(0x01,iobase+5) è 我们在IO BAR0区域的第6个字节中写入0x01。 · io_base = pci_ressouce_start(my_pci_dev, 0);//这给出了 BAR0 的基址。 · io_longueur = pci_resource_length(my_pci_dev, 0);//我们获取 bar0 内存区域的宣布长度。在这里,信息直接在PCI配置中找到。不使用设备文档。 · request_mem_region(io_base, io_longueur, “my_driver_xenomai_pci”);//我们宣布 Linux,我们将使用从基址“io_base”开始的内存区域,长度为“io_longueur”字节。此功能主要用于检查其他驱动程序是否尚未访问此区域。在这种情况下,存在冲突,建议返回错误代码。
注意:pci_request_region是Linux提供的一个功能,允许您同时执行上述三个步骤。 ·您将能够在驱动程序中访问的“内核”内存地址与 BAR0 找到的值不同,这是很正常的。实际上,内核使用虚拟地址,并且正是 MMU 使转换成为“内核地址到总线地址”。要获得“总线”地址的“内核”等效物,我们必须使用 ioremap() 函数。
示例: adresse_noyau = ioremap(io_base, io_longueur);
有时,这些与 PCI 设备相对应的内存映射区域不支持由处理器进行缓存。实际上,在访问内存总线(MMIO方法)时,处理器可以将此数据放在其内部缓存中,以加快下次访问时获取数据的速度。但是这个“内存”直接由设备控制,并且可以在设备更新时自发地更改其值。因此,处理器缓存可能包含一个过时的值,该值将在下次读取 [b|l|..] 中返回。 ioremap_nocache() 是与上述 ioremap() 相同的函数,但确保返回的内核地址不会被 CPU 缓存。 注 1:pci_resource_flag() 函数提供对此类信息的访问,以了解处理器是否可以缓存“BAR”区域。 注 2:参见 lib/iomap.c 了解 pci_iomap() 函数的使用,该函数在单个操作中对到目前为止描述的内容进行分组。
· data = readb(adresse_noyau+5)// 我们访问 BAR0 内存区域的第 6 个字节。
我们可以用同样的方式写:
writeb(0x01,adresse_noyau +5) //我们在内存盒BAR0的第6个字节中写0x01。
提示:关于驱动程序对IO或内存的“保留”,您可以通过/proc/ioports和/proc/iomem来观察它们。 以下是Xenomai提供的CAN SJA1000驱动程序的示例:rtan_peak_pci.c。这里重点介绍了上面提到的功能。此简洁的驱动程序允许您查找实现 Xenomai PCI 驱动程序所需的步骤。在此驱动程序中,访问方法是 MMIO。 此代码仅包含“可见”PCI 部分,卡的控制使用其他源文件。
/* * Copyright (C) 2006 Wolfgang Grandegger <wg@grandegger.com> * * Derived from the PCAN project file driver/src/pcan_pci.c: * * Copyright (C) 2001-2006 PEAK System-Technik GmbH * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <linux/module.h> #include <linux/ioport.h> #include <linux/delay.h> #include <linux/pci.h> #include <asm/io.h> #include <rtdm/rtdm_driver.h> /* CAN device profile */ #include <rtdm/rtcan.h> #include <rtcan_dev.h> #include <rtcan_raw.h> #include <rtcan_sja1000.h> #include <rtcan_sja1000_regs.h> #define RTCAN_DEV_NAME "rtcan%d" #define RTCAN_DRV_NAME "PEAK-PCI-CAN" static char *peak_pci_board_name = "PEAK-PCI"; MODULE_AUTHOR("Wolfgang Grandegger <wg@grandegger.com>"); MODULE_DESCRIPTION("RTCAN board driver for PEAK-PCI cards"); MODULE_SUPPORTED_DEVICE("PEAK-PCI card CAN controller"); MODULE_LICENSE("GPL"); struct rtcan_peak_pci { struct pci_dev *pci_dev; struct rtcan_device *slave_dev; int channel; volatile void __iomem *base_addr; volatile void __iomem *conf_addr; }; #define PEAK_PCI_CAN_SYS_CLOCK (16000000 / 2) #define PELICAN_SINGLE (SJA_CDR_CAN_MODE | SJA_CDR_CBP | 0x07 | SJA_CDR_CLK_OFF) #define PELICAN_MASTER (SJA_CDR_CAN_MODE | SJA_CDR_CBP | 0x07 ) #define PELICAN_DEFAULT (SJA_CDR_CAN_MODE ) #define CHANNEL_SINGLE 0 /* this is a single channel device */ #define CHANNEL_MASTER 1 /* multi channel device, this device is master */ #define CHANNEL_SLAVE 2 /* multi channel device, this is slave */ // important PITA registers #define PITA_ICR 0x00 // interrupt control register #define PITA_GPIOICR 0x18 // general purpose IO interface control register #define PITA_MISC 0x1C // miscellanoes register #define PEAK_PCI_VENDOR_ID 0x001C // the PCI device and vendor IDs #define PEAK_PCI_DEVICE_ID 0x0001 #define PCI_CONFIG_PORT_SIZE 0x1000 // size of the config io-memory #define PCI_PORT_SIZE 0x0400 // size of a channel io-memory static struct pci_device_id peak_pci_tbl[] = { {PEAK_PCI_VENDOR_ID, PEAK_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { } }; MODULE_DEVICE_TABLE (pci, peak_pci_tbl); static u8 rtcan_peak_pci_read_reg(struct rtcan_device *dev, int port) { struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv; return readb(board->base_addr + ((unsigned long)port << 2)); } static void rtcan_peak_pci_write_reg(struct rtcan_device *dev, int port, u8 data) { struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv; writeb(data, board->base_addr + ((unsigned long)port << 2)); } static void rtcan_peak_pci_irq_ack(struct rtcan_device *dev) { struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv; u16 pita_icr_low; /* Select and clear in Pita stored interrupt */ pita_icr_low = readw(board->conf_addr + PITA_ICR); if (board->channel == CHANNEL_SLAVE) { if (pita_icr_low & 0x0001) writew(0x0001, board->conf_addr + PITA_ICR); } else { if (pita_icr_low & 0x0002) writew(0x0002, board->conf_addr + PITA_ICR); } } static void rtcan_peak_pci_del_chan(struct rtcan_device *dev, int init_step) { struct rtcan_peak_pci *board; u16 pita_icr_high; if (!dev) return; board = (struct rtcan_peak_pci *)dev->board_priv; switch (init_step) { case 0: /* Full cleanup */ printk("Removing %s %s device %s\n", peak_pci_board_name, dev->ctrl_name, dev->name); rtcan_sja1000_unregister(dev); case 5: pita_icr_high = readw(board->conf_addr + PITA_ICR + 2); if (board->channel == CHANNEL_SLAVE) { pita_icr_high &= ~0x0001; } else { pita_icr_high &= ~0x0002; } writew(pita_icr_high, board->conf_addr + PITA_ICR + 2); case 4: iounmap((void *)board->base_addr); case 3: if (board->channel != CHANNEL_SLAVE) iounmap((void *)board->conf_addr); case 2: rtcan_dev_free(dev); case 1: break; } } static int rtcan_peak_pci_add_chan(struct pci_dev *pdev, int channel, struct rtcan_device **master_dev) { struct rtcan_device *dev; struct rtcan_sja1000 *chip; struct rtcan_peak_pci *board; u16 pita_icr_high; unsigned long addr; int ret, init_step = 1; dev = rtcan_dev_alloc(sizeof(struct rtcan_sja1000), sizeof(struct rtcan_peak_pci)); if (dev == NULL) return -ENOMEM; init_step = 2; chip = (struct rtcan_sja1000 *)dev->priv; board = (struct rtcan_peak_pci *)dev->board_priv; board->pci_dev = pdev; board->channel = channel; if (channel != CHANNEL_SLAVE) { addr = pci_resource_start(pdev, 0); board->conf_addr = ioremap(addr, PCI_CONFIG_PORT_SIZE); if (board->conf_addr == 0) { ret = -ENODEV; goto failure; } init_step = 3; /* Set GPIO control register */ writew(0x0005, board->conf_addr + PITA_GPIOICR + 2); if (channel == CHANNEL_MASTER) writeb(0x00, board->conf_addr + PITA_GPIOICR); /* enable both */ else writeb(0x04, board->conf_addr + PITA_GPIOICR); /* enable single */ writeb(0x05, board->conf_addr + PITA_MISC + 3); /* toggle reset */ mdelay(5); writeb(0x04, board->conf_addr + PITA_MISC + 3); /* leave parport mux mode */ } else { struct rtcan_peak_pci *master_board = (struct rtcan_peak_pci *)(*master_dev)->board_priv; master_board->slave_dev = dev; board->conf_addr = master_board->conf_addr; } addr = pci_resource_start(pdev, 1); if (channel == CHANNEL_SLAVE) addr += 0x400; board->base_addr = ioremap(addr, PCI_PORT_SIZE); if (board->base_addr == 0) { ret = -ENODEV; goto failure; } init_step = 4; dev->board_name = peak_pci_board_name; chip->read_reg = rtcan_peak_pci_read_reg; chip->write_reg = rtcan_peak_pci_write_reg; chip->irq_ack = rtcan_peak_pci_irq_ack; /* Clock frequency in Hz */ dev->can_sys_clock = PEAK_PCI_CAN_SYS_CLOCK; /* Output control register */ chip->ocr = SJA_OCR_MODE_NORMAL | SJA_OCR_TX0_PUSHPULL; /* Clock divider register */ if (channel == CHANNEL_MASTER) chip->cdr = PELICAN_MASTER; else chip->cdr = PELICAN_SINGLE; strncpy(dev->name, RTCAN_DEV_NAME, IFNAMSIZ); /* Register and setup interrupt handling */ chip->irq_flags = RTDM_IRQTYPE_SHARED; chip->irq_num = pdev->irq; pita_icr_high = readw(board->conf_addr + PITA_ICR + 2); if (channel == CHANNEL_SLAVE) { pita_icr_high |= 0x0001; } else { pita_icr_high |= 0x0002; } writew(pita_icr_high, board->conf_addr + PITA_ICR + 2); init_step = 5; printk("%s: base_addr=%p conf_addr=%p irq=%d\n", RTCAN_DRV_NAME, board->base_addr, board->conf_addr, chip->irq_num); /* Register SJA1000 device */ ret = rtcan_sja1000_register(dev); if (ret) { printk(KERN_ERR "ERROR %d while trying to register SJA1000 device!\n", ret); goto failure; } if (channel != CHANNEL_SLAVE) *master_dev = dev; return 0; failure: rtcan_peak_pci_del_chan(dev, init_step); return ret; } static int __devinit peak_pci_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) { int ret; u16 sub_sys_id; struct rtcan_device *master_dev = NULL; printk("%s: initializing device %04x:%04x\n", RTCAN_DRV_NAME, pdev->vendor, pdev->device); if ((ret = pci_enable_device (pdev))) goto failure; if ((ret = pci_request_regions(pdev, RTCAN_DRV_NAME))) goto failure; if ((ret = pci_read_config_word(pdev, 0x2e, &sub_sys_id))) goto failure_cleanup; /* Enable memory space */ if ((ret = pci_write_config_word(pdev, 0x04, 2))) goto failure_cleanup; if ((ret = pci_write_config_word(pdev, 0x44, 0))) goto failure_cleanup; if (sub_sys_id > 3) { if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_MASTER, &master_dev))) goto failure_cleanup; if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_SLAVE, &master_dev))) goto failure_cleanup; } else { if ((ret = rtcan_peak_pci_add_chan(pdev, CHANNEL_SINGLE, &master_dev))) goto failure_cleanup; } pci_set_drvdata(pdev, master_dev); return 0; failure_cleanup: if (master_dev) rtcan_peak_pci_del_chan(master_dev, 0); pci_release_regions(pdev); failure: return ret; } static void __devexit peak_pci_remove_one (struct pci_dev *pdev) { struct rtcan_device *dev = pci_get_drvdata(pdev); struct rtcan_peak_pci *board = (struct rtcan_peak_pci *)dev->board_priv; if (board->slave_dev) rtcan_peak_pci_del_chan(board->slave_dev, 0); rtcan_peak_pci_del_chan(dev, 0); pci_release_regions(pdev); pci_disable_device(pdev); pci_set_drvdata(pdev, NULL); } static struct pci_driver rtcan_peak_pci_driver = { .name = RTCAN_DRV_NAME, .id_table = peak_pci_tbl, .probe = peak_pci_init_one, .remove = __devexit_p(peak_pci_remove_one), }; static int __init rtcan_peak_pci_init(void) { return pci_register_driver(&rtcan_peak_pci_driver); } static void __exit rtcan_peak_pci_exit(void) { pci_unregister_driver(&rtcan_peak_pci_driver); } module_init(rtcan_peak_pci_init); module_exit(rtcan_peak_pci_exit);