xenomai驱动开发-PCI驱动开发-1


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);

原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/276740.html

(0)
上一篇 2022年7月24日
下一篇 2022年7月24日

相关推荐

发表回复

登录后才能评论