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);
    
    posted @   相对维度  阅读(546)  评论(0编辑  收藏  举报
    相关博文:
    阅读排行:
    · 物流快递公司核心技术能力-地址解析分单基础技术分享
    · .NET 10首个预览版发布:重大改进与新特性概览!
    · 单线程的Redis速度为什么快?
    · 展开说说关于C#中ORM框架的用法!
    · Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
    点击右上角即可分享
    微信分享提示