常州网站外包,wordpress速度快,广州公司注册流程及费用,个人网站首页模板目录
一、 PCI协议简介
二、PCI和PCI-e
三、Linux PCI驱动
四、 PCI设备驱动实例
五、 总线类设备驱动开发习题 一、 PCI协议简介 PCI (Peripheral Component Interconnect#xff0c;外设部件互联) 局部总线是由Intel 公司联合其他几家公司一起开发的一种总线标准#…目录
一、 PCI协议简介
二、PCI和PCI-e
三、Linux PCI驱动
四、 PCI设备驱动实例
五、 总线类设备驱动开发习题 一、 PCI协议简介 PCI (Peripheral Component Interconnect外设部件互联) 局部总线是由Intel 公司联合其他几家公司一起开发的一种总线标准最初是为了替代 ISA 之类的总线用于解决当时的图形化界面显示器的带宽问题。相比于 ISA 总线它最大的特点是高带宽、突发传输和即插即用热插拔。在 PCI 3.0 的规范中PCI 局部总线的时钟速率有 33MHZ、66MHz 和133MHz 三种标准速率支持的数据位宽有 32 位和 64 位两种。所以最低的数据传输率为33MHz x 32bit 132MB/s即每秒 132M 字节这完全满足当时的图形显卡的要求。突发传输是指其地址总线和数据总线复用在传输开始时先发地址然后再连续传输若干个字节的数据这样做的好处是可以减少芯片的管脚并且一个传输周期可以完成若干个字节的传输。即插即用和前面谈到的 USB 类似总线上的设备存放有配置信息在初始化的过程中主机会主动获取这些信息从而分配其所需要的资源这会在后面做更详细的介绍。随着 PCI 局部总线的发展其应用的领域也越来越广泛现在 PC 中独立的网卡、声卡、数据采集卡等使用的都是 PCI 局部总线。后来又推出了串行的标准PCI-Express其传输速率相当高在 PCI-Express 3.0 规范中其传输率可以达到 8GT/s即每秒 8G 次传输。因为使用的广泛性,在某些嵌入式系统中也使用了 PCI或 PCI-Express局部总线。下面简单介绍一下 PCI3.0 规范中驱动开发者需要关心的内容下图是 PCI系统的连接框图(引自 PCI3.0 规范)。 处理器(Processor)通过 Host/PCI桥(Bridge)连接到了 0号 PCI局部总线(PCI LocalBus #0)在这条局部总线上有声卡 (Audio)、动态视频(Motion Video)、图形显卡(Graphics)、网卡(LAN)和 SCSI控制器等。通过 PCI-to-PCI Bridge又扩展出了1号PCI局部总线(PCILocal Bus #1)在这条总线上又接入了其他 PCI功能设备。另外还有PCI-ISA 桥可以将 PCI 总线转换为传统的ISA 总线。 PCI局部总线也是主从结构在 PCI的规范中主设备叫发起者Initiator)从设备叫目标(Target)传输由主设备发起从设备进行响应。一个 PCI 设备都要实现目标的功能但也可以实现发起者的功能也就是说一个设备既可以在某一时刻做主设备也可以在另一个时刻做从设备。并且一条总线上允许有多个主设备由仲裁器来决定哪个主设备可以获得总线的控制权。下面我们仅讨论 PCI的从设备。 PCI 定义了三个物理地址空间包括内存地址空间、I/O 地址空间和配置地址空间。 其中配置地址空间是必需的这个地址空间用于对设备的硬件进行配置。为了更好地理解这三个地址空间的访问我们先来看看 PCI 的典型写传输时序图如下图所示(引自PCI3.0 规范)。 当发起者要对目标进行写操作时会先将 FRAME 拉低在之后的第一个时钟周期AD 总线上是发送地址C/BE 是总线的命令用于确定一个更具体的写操作DEVSEI是被选中的目标发出的确认信号。在之后的若干个周期AD 总线上是要写入的数据C/BE 上是字节使能用于确定哪个字节是有效的。IRDY 和 TRDY 分别是发起者和目标的准备信号当任一个无效时都会自动插入等待周期。在最后一个数据周期FRAME无效但传输最终完成是在 FRAME 无效后 IRDY 也无效的时刻。PCI 的读传输操作和写基本类似只是数据的方向相反。上面涉及的总线命令如下图所示(引自 PCI3.0 规范)。 I/O 读、I/O 写、内存读、内存写、配置读和配置写即我们前面提到的三个物理地址空间的读写。我们首先来看配置空间是如何寻址的地址结构如下图所示。 PCI规范定义了两种类型的配置空间地址Type 0 用于选择总线上的一个设备Type1 用于将请求传递给另一条总线。地址中的各个字段的含义如下。 Bus Number:8 位总线地址在 256条PCI 局部总线中选择一条。 Device Number:5 位设备地址在一条总线上的 32 个物理设备中选择一个 Function Number:3 位功能地址在一个物理设备上的8个功能中选择一个功能也就是说PCI 设备和 USB 设备类似一个物理设备可以有多个功能从而实现多个逻辑设备。 Register Number:用于选择配置空间中的一个 32 位寄存器。 在 PCI规范中对配置空间的各寄存器都有具体的定义整个配置空间有 64 个字节我们并不需要关心配置空间中每个寄存器的含义下面列出最主要的一些寄存器(其他寄存器的定义及地址请参见 PCI规范)。 Vendor ID:16 位硬件厂商ID。 Device ID:16 位设备 ID。 Class Code: 24 位外设所属的类别如大容量存储设备控制器类、网络控制器类显示控制器类等。为 0表示不属于某一具体的类。 Subsystem Vendor ID: 16 位子系统厂商 ID。 Subsystem ID:16 位子系统ID。 Base Address Registers: 32 位在计算机启动的过程中会检查所有的 PCI 设备其中一个重要的操作就是要获取其使用的内存空间和 I/O 空间的大小,然后给每一个空间分配一个基址这个基址就是存放在基址寄存器中的。配置空间中共有 6 个这样的基址寄存器在 Linux 驱动中简称 bar。 上面的 ID 和 Class 用于匹配驱动程序基址则用于驱动进行资源获取和映射操作,后面会进行更详细的描述。有了基址寄存器后对内存空间和 IO 空间的访问问题也就迎刃而解了,因为我们只需要发出相应的内存地址或I/O地址就可以访问对应的空间了。 二、PCI和PCI-e PCI和PCIe都是计算机中用于连接设备的接口标准但它们之间存在一些重要区别。 PCIPeripheral Component Interconnect是一种早期的计算机总线标准它被设计用于连接各种高速外围设备如显卡、声卡、网卡等。PCI总线是一种共享总线这意味着所有设备共享相同的带宽。因此当多个设备同时尝试使用总线时可能会出现性能下降或冲突。 相比之下PCIePeripheral Component Interconnect Express是一种更现代的计算机总线标准也被广泛应用于连接各种高速设备。与PCI不同PCIe是一种点对点互连协议这意味着每个设备都有自己的专用连接不会与其他设备共享带宽。这使得PCIe在性能上大大超越PCI特别是在高带宽应用和多设备环境中。 总的来说PCIe在性能、灵活性和扩展性方面都优于PCI这也是为什么现在的计算机和设备大多采用PCIe接口的原因。但需要注意的是由于PCIe的复杂性和成本较高在一些低带宽和低成本的应用中PCI仍然可能被使用。 PCI-e在软件层面是兼容PCI的PCI是并行传输PCI-e是串行点对点传输。 三、Linux PCI驱动 下面我们还是只讨论 PCI 从设备。PCI 设备在内中用 struct pci_dev 结构来表示,该结构的成员非常多在此就不一一列出了可以参见内核源码中的 include/linux/pci.h 文件。在里面会发现我们前面提到的 ID、类等成员还有设备所使用的IRQ 线。设备的 ID 还有一个 struct pci_device_id 结构驱动中通常会定义这样一个数组来表示可以支持的设备列表和前面的 USB 设备列表类似。和 PCI 设备结构相关的主要函数和宏如下。
int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);
pci_resource_start(dev, bar)
pci_resource_end(dev, bar)
pci_resource_flags(dev, bar)
pci_resource_len(dev,bar)
int pci_request_regions (struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);
pci_enable_device: 使能 PCI设备在操作 PCI设备之前必须先使能设备
pci_disable_device:禁止PCI设备。pci_resource_start: 获取 dev 中第 bar 个基址寄存器中记录的资源起始地址.
pci_resource_end:获取 dev 中第 bar 个基址寄存器中记录的资源结束地址。pci_resource_flags:取 dev 中第 bar 个基址寄存器中记录的资源标志是内存资源还是 IO 资源。pci_resource_len:获取 dev 中第 bar 个基址寄存器中记录的资源大小。pci_request_regions: 申请 PCI 设备 pdev 内的内存资源和I/0 资源取名为res_name.
pci_release_regions: 释放 PCI设备 pdev 内的内存资源和 IO 资源。 内核中用 struct pci_driver 结构来表示 PCI 设备驱动相关的主要函数宏如下 void pci_unregister_driver(struct pci_driver *dev);
pci_register_driver(driver)
void pci_set_drvdata(struct pci_dev *pdev, void *data);
void *pci_get_drvdata(struct pci_dev *pdev);
pci_register_driver: 注册 PCI 设备驱动 driverpci_unregister_driver: 注销 PCI 设备驱动 dev。pci_set_drvdata:保存 data 指针到PCI设备pdev 中。pci_get_drvdata: 从 PCI 设备 pdev 中获取保存的指针 PCI设备的配置空间访问的主要函数如下。
int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val);
int pei_read_config_dword(const struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(const struct pci_dev *dev, int where, u16 val);
int pei_write_config_dword(const struct pci_dev *dev, int where, u32 val);上面的函数分别实现了对配置空间的字节、字 (16 位) 和双字(32 位) 的读写操作。 四、 PCI设备驱动实例 这里使用的 PCI 设备是南京沁恒公司的 CH368EVT 评估板该评估板使用了一片该公司设计的 CH368 PCI-Express 接口芯片虽然是 PCI-Express 协议但是在驱动上两者可以兼容只是 PCI-Express 速率更高能够支持更多的功能。选用该评估板的原因是其价格低廉完全国产也能够全面验证三个空间的读写操作。 使用L1~L4 这 4个 LED 显示 I/O 数据端口 D3~DO 位的状态。灯亮代表 1灯灭代表0。 CH368 的配置空间定义如下图所示。 厂商 ID 和设备 ID 是我们比较关心的内容驱动的设备列表中的 ID 要和这里的致。第一个基址寄存器是 I/O 地址空间的基址有 232 个字节定义如下图所示。另外CH368 的内存空间有 32KB。
该设备的 Linux 驱动代码如下为了尽量突出 PCI驱动的核心并没有加入并发控制相关的代码。
#include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/cdev.h
#include linux/fs.h
#include linux/slab.h
#include linux/pci.h
#include linux/io.h
#include linux/ioport.h
#include linux/uaccess.h#include ch368.h#define CH368_MAJOR 256
#define CH368_MINOR 11
#define CH368_DEV_NAME ch368struct ch368_dev {void __iomem *io_addr;void __iomem *mem_addr;unsigned long io_len;unsigned long mem_len;struct pci_dev *pdev;struct cdev cdev;dev_t dev;
};static unsigned int minor CH368_MINOR;static int ch368_open(struct inode *inode, struct file *filp)
{struct ch368_dev *ch368;ch368 container_of(inode-i_cdev, struct ch368_dev, cdev);filp-private_data ch368;return 0;
}static int ch368_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t ch368_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{int ret;struct ch368_dev *ch368 filp-private_data;count count ch368-mem_len ? ch368-mem_len : count;ret copy_to_user(buf, ch368-mem_addr, count);return count - ret;
}static ssize_t ch368_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{int ret;struct ch368_dev *ch368 filp-private_data;count count ch368-mem_len ? ch368-mem_len : count;ret copy_from_user(ch368-mem_addr, buf, count);return count - ret;
}static long ch368_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{union addr_data ad;struct ch368_dev *ch368 filp-private_data;if (_IOC_TYPE(cmd) ! CH368_MAGIC)return -ENOTTY;if (copy_from_user(ad, (union addr_data __user *)arg, sizeof(union addr_data)))return -EFAULT;switch (cmd) {case CH368_RD_CFG:if (ad.addr 0x3F)return -ENOTTY;pci_read_config_byte(ch368-pdev, ad.addr, ad.data);if (copy_to_user((union addr_data __user *)arg, ad, sizeof(union addr_data)))return -EFAULT;break;case CH368_WR_CFG:if (ad.addr 0x3F)return -ENOTTY;pci_write_config_byte(ch368-pdev, ad.addr, ad.data);break;case CH368_RD_IO:ad.data ioread8(ch368-io_addr ad.addr);if (copy_to_user((union addr_data __user *)arg, ad, sizeof(union addr_data)))return -EFAULT;break;case CH368_WR_IO:iowrite8(ad.data, ch368-io_addr ad.addr);break;default:return -ENOTTY;}return 0;
}static struct file_operations ch368_ops {.owner THIS_MODULE,.open ch368_open,.release ch368_release,.read ch368_read,.write ch368_write,.unlocked_ioctl ch368_ioctl,
};static int ch368_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{int ret;unsigned long io_start;unsigned long io_end;unsigned long io_flags;unsigned long io_len;void __iomem *io_addr NULL;unsigned long mem_start;unsigned long mem_end;unsigned long mem_flags;unsigned long mem_len;void __iomem *mem_addr NULL;struct ch368_dev *ch368;ret pci_enable_device(pdev);if(ret)goto enable_err;io_start pci_resource_start(pdev, 0);io_end pci_resource_end(pdev, 0);io_flags pci_resource_flags(pdev, 0);io_len pci_resource_len(pdev, 0);mem_start pci_resource_start(pdev, 1);mem_end pci_resource_end(pdev, 1);mem_flags pci_resource_flags(pdev, 1);mem_len pci_resource_len(pdev, 1);if (!(io_flags IORESOURCE_IO) || !(mem_flags IORESOURCE_MEM)) {ret -ENODEV;goto res_err;}ret pci_request_regions(pdev, ch368);if (ret)goto res_err;io_addr ioport_map(io_start, io_len);if (io_addr NULL) {ret -EIO;goto ioport_map_err;}mem_addr ioremap(mem_start, mem_len);if (mem_addr NULL) {ret -EIO;goto ioremap_err;}ch368 kzalloc(sizeof(struct ch368_dev), GFP_KERNEL);if (!ch368) {ret -ENOMEM;goto mem_err;}pci_set_drvdata(pdev, ch368);ch368-io_addr io_addr;ch368-mem_addr mem_addr;ch368-io_len io_len;ch368-mem_len mem_len;ch368-pdev pdev;ch368-dev MKDEV(CH368_MAJOR, minor);ret register_chrdev_region (ch368-dev, 1, CH368_DEV_NAME);if (ret 0)goto region_err;cdev_init(ch368-cdev, ch368_ops);ch368-cdev.owner THIS_MODULE;ret cdev_add(ch368-cdev, ch368-dev, 1); if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(ch368-dev, 1);
region_err:kfree(ch368);
mem_err:iounmap(mem_addr);
ioremap_err:ioport_unmap(io_addr);
ioport_map_err:pci_release_regions(pdev);
res_err:pci_disable_device(pdev);
enable_err:return ret;
}static void ch368_remove(struct pci_dev *pdev)
{struct ch368_dev *ch368 pci_get_drvdata(pdev);cdev_del(ch368-cdev);unregister_chrdev_region(ch368-dev, 1);iounmap(ch368-mem_addr);ioport_unmap(ch368-io_addr);kfree(ch368);pci_release_regions(pdev);pci_disable_device(pdev);
}static struct pci_device_id ch368_id_table[]
{{0x1C00, 0x5834, 0x1C00, 0x5834, 0, 0, 0},{0,}
};
MODULE_DEVICE_TABLE(pci, ch368_id_table);static struct pci_driver ch368_driver {.name ch368,.id_table ch368_id_table,.probe ch368_probe,.remove ch368_remove,
};module_pci_driver(ch368_driver);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name e-mail);
MODULE_DESCRIPTION(CH368 driver);#ifndef _CH368_H
#define _CH368_Hunion addr_data {unsigned char addr;unsigned char data;
};#define CH368_MAGIC c#define CH368_RD_CFG _IOWR(CH368_MAGIC, 0, union addr_data)
#define CH368_WR_CFG _IOWR(CH368_MAGIC, 1, union addr_data)
#define CH368_RD_IO _IOWR(CH368_MAGIC, 2, union addr_data)
#define CH368_WR_IO _IOWR(CH368_MAGIC, 3, union addr_data)#endif代码第 19 行至第 27 行是设备结构的定义包含了保存映射之后的 IO 地址和内存地址的 io_addr 和 mem_addr 指针成员、保存 IO 地址空间大小和内存地址空间大小的io_len 和 mem_len 成员、保存 PCI 设备结构的 pdev 指针成员。该 PCI设备实现为一个字符设备所以有 cdev 和 dev 成员。 代码第 224 行至第 242 行是 PCI驱动结构的定义、注册和注销。ch368_id_table 是该驱动支持的设备列表其中的 ID 号要和上图中的 ID 号一致。 当有匹配的 PCI设备被检测到后ch368_probe 函数自动被调用。代码第 134 行首先使能了 PCI 设备代码第 138 行至第 146 行分别获取了 IO 和内存的物理地址、标志和长度信息。代码第 148 行至第 151 行判断了获取的标志内的资源类型信息如果不和预期的相同则设备探测失败。代码第 153 行至第 167 行申请了 PCI 设备所声明的资源,然后进行了映射获得了对应的虚拟地址。代码第 169 行至第 180 行分配了 struct ch368_dev 结构的内存空间并对各成员进行了相应的初始化还使用 pci_set_drvdata函 总线类设备驱动微将该结构地址保存在了 PCI 设备结构之中方便之后从 PCI 设备结构中获得该地址该函数之后的代码是字符设备相关的注册操作。ch368 remove 做的工作和 h368 probe第亿函数相反。 ch368_open 和 ch368_release 没有做太多的工作,ch368_read 和 ch368_write 则是针对内存空间的读和写因为在这片内存空间没有对应外接的设备所以没有实际意义。比较实际的操作是在 ch368_ioctl 中CH368_RD_CFG 命今用来读取配置空间的数据,CH368_WR_CFG 命令用于向配置空间写入数据。CH368_RD_IO和CH368_WR_IO则分别是对 I/0 空间进行读和写。union addr_data 用于传送地址和返回数据这和ADC 驱动的例子是类似的。 应用层的测试代码如下
#include stdio.h
#include stdlib.h
#include sys/types.h
#include sys/stat.h
#include sys/ioctl.h
#include fcntl.h
#include errno.h#include ch368.hint main(int argc, char *argv[])
{int i;int fd;int ret;union addr_data ad;unsigned char id[4];fd open(/dev/ch368, O_RDWR);if (fd -1)goto fail;for (i 0; i sizeof(id); i) {ad.addr i;if (ioctl(fd, CH368_RD_CFG, ad))goto fail;id[i] ad.data;}printf(VID: 0x%02x%02x, DID: 0x%02x%02x\n, id[1], id[0], id[3], id[2]);i 0;ad.addr 0;while (1) {ad.data i;if (ioctl(fd, CH368_WR_IO, ad))goto fail;i % 15;sleep(1);}
fail:perror(pci test);exit(EXIT_FAILURE);
}上面的代码在打开设备后先读取了配置空间的前 4 个字节根据 PCI 规范这4字节刚好是厂商ID 和设备ID。接下来在 while 循环中对 I/0 空间的第一个字节依次写入了0~15这样 PCI 设备上的 4个 LED 灯就会按照此规律被点亮。前面说过4个LED反映了写入 I/ O空间的数据的低 4 位的状态数据位为 1 对应的灯被点亮数据位为0对应的灯熄灭。 使用下面的命令进行编译和测试。需要说明的是需要有一台安装了 Linux 系统的物理机并且物理机上要有对应的 PCIE 插槽才能插入该设备并进行测试。 五、 总线类设备驱动开发习题 1.I2C 总线协议规定是由 ( ) 来进行应答的。 [A] 数据发送者 [B] 数据接收者 2.I2C 总线协议规定所有访问都是由 ( )来发起的 [B] 从设备 [A] 主设备 3.SPI是 ( )总线。 [B] 异步 [A] 同步 4.SPI总线是 ( )的
[B] 半双工 [A] 单工
[C] 全双功 5.SPI 总线是 ( )的。
[A] 单主 [B] 多主 6.USB 的传输类型分为 ( [B] 等时传输 [A] 控制传输 [D] 块传输 [C] 中断传输 7.USB 的接口是由多个 ( ) 组成的。 [A] 配置 [B] 管道
[C] 端点 8.PCI的配置空间包括 ( ) 信息。 [B] 设备ID [A] 厂商ID [D] 地址空间大小 [C] 基地址 答案B A A A C ABCD C ABCD