系统相关
首页 > 系统相关> > xenomai驱动开发-PCI驱动开发-1

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

标签:rtcan,PCI,dev,pci,board,xenomai,驱动,peak
来源: https://www.cnblogs.com/wangjirui/p/16514201.html