pci设备驱动

pci设备驱动原文地址 https www cnblogs com xiaoya901109 archive 2012 12 14 2818057 html 一 初始化设备模块 nbsp nbsp 当 Linux 内核启动并完成对所有 PCI 设备进行扫描 登录和分配资源等初始化操作的同时 会建立起系统中所有 PCI 设备的拓扑结构 此后当 PCI 驱动程序需要对设备进行初始化时 一般都会调用如下的代码 staticint init

原文地址:https://www.cnblogs.com/xiaoya901109/archive/2012/12/14/2818057.html

一,初始化设备模块

  Linux内核启动并完成对所有PCI设备进行扫描、登录和分配资源等初始化操作的同时,会建立起系统中所有PCI设备的拓扑结构,此后当PCI驱动程序需要对设备进行初始化时,一般都会调用如下的代码:

复制代码
 static int __init example_init_module (void) { /* 注册硬件驱动程序 */ if(!pci_register_driver(&example_pci_driver)){ pci_unregister_driver(&example_pci_driver); return-ENODEV; } /* ... */ return 0; }
复制代码

 从上面的省略号可以看出,这仅仅只是核心的一部分,其他的东西,就要看你具体的应用是在什么地方。

仅仅使用上面的init函数是不够的,因为此时你并不知道你的设备是什么样的,是不是都已经准备好了,因此,还有更重要的一步—probe。探测完成对硬件的检测工作。

我们先将相关代码进行列示:

复制代码
 static int __init example_probe(struct pci_dev *pci_dev,conststruct pci_device_id *pci_id) { struct example_pci *my_pci; /* 启动PCI设备 */ if(pci_enable_device(pci_dev)) return-EIO; /* 设备DMA标识 */ if(pci_set_dma_mask(pci_dev, EXAMPLE_DMA_MASK)) return-ENODEV; /* 在内核空间中动态申请内存 */ if((my_pci = kmalloc(sizeof(struct example_pci), GFP_KERNEL))== NULL){ printk(KERN_ERR "example_pci: out of memory\n"); return-ENOMEM; } memset(my_pci,0,sizeof(*my_pci)); /* 读取PCI配置信息 */ my_pci->iobase = pci_resource_start(pci_dev,1); my_pci->pci_dev = pci_dev; my_pci->pci_id = pci_id->device; my_pci->irq = pci_dev->irq; my_pci->next= devs; my_pci->magic = EXAMPLE_MAGIC; /* 设置成总线主DMA模式 */ pci_set_master(pci_dev); /* 申请I/O资源 */ request_region(my_pci->iobase,64,my_pci_names[pci_id->driver_data]); return 0; }
复制代码

 

整个程序的思路很清晰,并不需要去太多的讲解,只是对里面的一些函数需要进行一下讲解.

1.pci_enable_device

激活PCI设备,在驱动程序可以访问PCI设备的任何设备资源之前(I/O区域或者中断),驱动程序必须调用该函数:

 int pci_enable_device(struct pci_dev *dev); /*driver/pci/pci.c*/

 该函数实际的激活设备。它把设备唤醒,在某些情况下还指派它的中断线和I/O区域。

2.访问PCI地址空间

在驱动程序检测到设备之后,它通常需要读取或写入三个地址空间:内存,端口和配置。对驱动程序来说,对配置空间的访问至关重要,因为这是它找到设备映射到内存和I/O空间的什么位置的唯一途径。

因而,首先来看看配置空间的访问:

Linux内核为我们想的很周到,在内核中就已经提供了访问配置空间的标准接口,我们只要去直接调用就好了。

对于驱动程序而言,可通过8位,16位,32位的数据传输访问配置空间。相关函数定义在 中:

 int pci_read_config_byte(conststruct pci_dev *dev,intwhere, u8 *val);/*8位,读入一个字节*/ int pci_read_config_word(conststruct pci_dev *dev,intwhere, u16 *val);/*16位,读入两个字节*/ int pci_read_config_dword(conststruct pci_dev *dev,intwhere, u32 *val);/*32位,读入四个字节*/

      const struct pci_dev *dev:由dev标识的设备配置空间;

      int where:从配置空间起始位置计算的字节偏移量;

      u8/u16/u32 *val:从配置空间获得的值通过val指针返回;

      函数本身返回的值是错误码。

注意:word和dword函数会将读取到的little-endian值转换成处理器固有的字节序。我们自己无需处理字节序。

上面的是读的情况,写的情况也是类似的 :

 int pci_write_config_byte(conststruct pci_dev *dev,intwhere, u8 *val);/*8位,写入一个字节*/ int pci_write_config_word(conststruct pci_dev *dev,intwhere, u16 *val);/*16位,写入两个字节*/ int pci_write_config_dword(conststruct pci_dev *dev,intwhere, u32 *val);/*32位,写入四个字节*/

 因此,我们可以利用上面的函数读取和修改设备的信息。

讲完配置空间,接下来唠叨一下I/O和内存空间。

一个PCI设备可实现多达6个I/O地址区域,每个区域既可以使内存也可以是I/O地址。在内核中PCI设备的I/O区域已经被集成到通用资源管理器。因此,我们无需访问配置变量来了解设备被映射到内存或者I/O空间的何处。获得区域信息的首选接口是下面的宏定义:

 #define pci_resource_start(dev, bar)((dev)->resource[(bar)].start)

 该宏返回六个PCI I/O区域之一的首地址(内存地址或者I/O端口号).该区域由整数的bar(base address register,基地址寄存器)指定,bar取值为0到5。

 #define pci_resource_end(dev, bar)((dev)->resource[(bar)].end)

 该宏返回第bar个I/O区域的首地址。注意这是最后一个可用的地址,而不是该区域之后的第一个地址。

 #define pci_resource_flags(dev, bar)((dev)->resource[(bar)].flags)

 该宏返回和该资源相关联的标志。

资源标志用来定义单个资源的特性,对与PCI I/O区域相关的PCI资源,该信息从基地址寄存器中获得,但对于和PCI无关的资源,它可能来自其他地方。所有资源标志定义在

二,打开设备模块

复制代码
static int example_open(struct inode *inode, struct file *file) { /* 申请中断,注册中断处理程序 */ request_irq(my_pci->irq, &example_interrupt, SA_SHIRQ, my_pci_names[pci_id->driver_data], my_pci)) ; /* 检查读写模式 */ if(file->f_mode & FMODE_READ) { /* ... */ } if(file->f_mode & FMODE_WRITE) { /* ... */ } /* 申请对设备的控制权 */ down(&my_pci->open_sem); while(my_pci->open_mode & file->f_mode) { if (file->f_flags & O_NONBLOCK) { /* NONBLOCK模式,返回-EBUSY */ up(&my_pci->open_sem); return -EBUSY; } else { /* 等待调度,获得控制权 */ my_pci->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE); up(&my_pci->open_sem); /* 设备打开计数增1 */ MOD_INC_USE_COUNT; /* ... */ } } }
复制代码

 在这个模块里主要实现申请中断、检查读写模式以及申请对设备的控制权等。在申请控制权的时候,非阻塞方式遇忙返回,否则进程主动接受调度,进入睡眠状态,等待其它进程释 放对设备的控制权。

三,数据读写和信息控制模块

复制代码
static int example_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { /* ... */ switch(cmd) { case EXAMPLE_RDATA: /* 从I/O端口读取4字节的数据 */ val = inl(my_pci->iobae + 0x10); /* 将读取的数据传输到用户空间 */ return 0; } /* ... */ }
复制代码

  PCI设备驱动程序可以通过example_fops结构中的函数example_ioctl( ),向应用程序提供对硬件进行控制的接口。例如,通过它可以从I/O寄存器里读取一个数据,并传送到用户空间里。

四,中断模块

复制代码
 static void example_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct example_pci *my_pci = (struct example_pci *)dev_id; u32 status; spin_lock(&my_pci->lock); /* 中断 */ status = inl(my_pci->iobase + GLOB_STA); if(!(status & INT_MASK)) { spin_unlock(&my_pci->lock); return; /* not for us */ } /* 告诉设备已经收到中断 */ outl(status & INT_MASK, my_pci->iobase + GLOB_STA); spin_unlock(&my_pci->lock); /* 其它进一步的处理 */ }
复制代码

 PCI的中断资源比较有限,只有0~15的中断号,因此大部分外部设备都是以共享的形式申请中断号的。当中断发生的时候,中断处理程序首先负责对中断进行识别,然后再做进一步的处理。

五,释放设备模块

复制代码
static int example_release(struct inode *inode, struct file *file) { /* ... */ /* 释放对设备的控制权 */ my_pci->open_mode &= (FMODE_READ | FMODE_WRITE); /* 唤醒其它等待获取控制权的进程 */ wake_up(&my_pci->open_wait); up(&my_pci->open_sem); /* 释放中断 */ free_irq(my_pci->irq, my_pci); /* 设备打开计数增1 */ MOD_DEC_USE_COUNT; /* ... */
复制代码

 释放设备模块主要负责释放对设备的控制权,释放占用的内存和中断等,所做的事情正好与打开设备模块相反。

以上就是我对PCI驱动程序的现阶段理解,比较初浅,若有任何不合适的地方,请大家指出。

 参考资料:

Alessandro Rubini,,Linux Device Drivers(3rd Edition) USA:O’Reilly,2005

http://os.chinaunix.net/a2008/0307/978/000000978057.shtml

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198868.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午2:19
下一篇 2026年3月26日 下午2:19


相关推荐

  • tensorflow tensor转numpy

    tensorflow tensor转numpysess=tf.Session()withsess.as_default():change=tf.expand_dims(tf.reshape(batch_rodrigues((np.array(gt_data[‘pose_0’]).flatten()).reshape(-1,3).astype(np.float32)),(24,3,3)),0)gt_data[‘po.

    2022年10月19日
    3
  • 谷歌滚动条样式(滚动条样式按钮)

    有不少可以帮你修改Chrome默认滚动条样式的扩展,ScrollBarX就是其中的佼佼者,但是这个扩展在安装的时候提示说会存取所有的网站数据,这就让人感觉很难以接受了。其实咱们可以自己动手,丰衣足食。首先在自己的系统里找到如下这个.css文件:Windows:%LOCALAPPDATA%\Google\Chrome\UserData\Default\UserStyleSheets\…

    2022年4月17日
    43
  • 腾讯翻译大模型教程:多语言聊天机器人开发

    腾讯翻译大模型教程:多语言聊天机器人开发

    2026年3月12日
    3
  • java抽象类中必须有抽象方法吗_011 抽象类必须要有抽象方法吗?

    java抽象类中必须有抽象方法吗_011 抽象类必须要有抽象方法吗?抽象类必须要有抽象方法吗 答 不需要 抽象类不一定有抽象方法 但是包含一个抽象方法的类一定是抽象类 有抽象方法就是抽象类 是抽象类可以没有抽象方法 解释 抽象方法 java 中的抽象方法就是以 abstract 修饰的方法 这种方法只声明返回的数据类型 方法名称和所需的参数 没有方法体 也就是说抽象方法只需要声明而不需要实现 抽象方法与抽象类 当一个方法为抽象方法时 意味着这个方法必须被子类的方法所重

    2026年3月20日
    3
  • 千问大模型负责人离职后,阿里回应

    千问大模型负责人离职后,阿里回应

    2026年3月13日
    2
  • 编写java判断闰年_Java 判断闰年代码实例

    编写java判断闰年_Java 判断闰年代码实例importjava.util.Scanner;/*5.1判断闰年(时间:20分钟)5.1.1作业任务(1)由用户输入任意一个年份,能被4整除但不能被100整除,或者能被400整除,是闰年。(结果:输出闰年或平年)5.1.2任务要求(1)本题仅要求判断一个年份是否为闰年。5.1.3难点提示用?:运算符条件运算符是三目运算符,其格式为:表达式?语句1:语句2;其中表达式的值是布尔类型,…

    2022年7月17日
    13

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号