LINUX驱动程序接口
- 格式:pdf
- 大小:237.89 KB
- 文档页数:8
linux 驱动的 ioctl 详细说明【实用版】目录1.驱动概述2.ioctl 的作用3.ioctl 的参数4.ioctl 的返回值5.ioctl 的错误码6.设备相关的 ioctl 调用7.总结正文1.驱动概述在 Linux 系统中,驱动程序是一种特殊的程序,它们用于控制和管理硬件设备。
驱动程序通过系统调用接口与操作系统内核进行交互,以实现对硬件设备的控制和管理。
在 Linux 中,声卡驱动程序是一个重要的驱动程序类型,它用于控制和管理声卡设备。
2.ioctl 的作用ioctl(input/output control)是 Linux 系统中的一个重要系统调用,它用于实现对设备驱动程序的控制和管理。
ioctl 函数通过传递特定的参数,可以实现对设备进行配置、控制和查询等操作。
对于声卡驱动程序来说,ioctl 函数可以用于实现对声卡设备的各种控制和管理操作。
3.ioctl 的参数ioctl 函数的参数主要包括两个部分:一个是设备文件描述符,它是通过 open、create 等系统调用创建的;另一个是参数缓冲区,它用于存储 ioctl 函数所需的参数。
此外,ioctl 函数还可能需要一些其他参数,具体取决于所使用的设备类型和操作。
4.ioctl 的返回值ioctl 函数的返回值表示函数执行的结果。
如果函数执行成功,则返回 0;如果发生错误,则返回 -1,并设置相应的错误码。
错误码可以通过 errno 系统变量获取。
5.ioctl 的错误码ioctl 函数返回的错误码可以用来判断函数执行是否成功。
常见的错误码包括:- EINVAL:无效的参数。
- EIO:设备 I/O 错误。
- EAGAIN:设备繁忙,需要重试。
- ENODEV:指定的设备不存在。
- ENOENT:指定的设备文件描述符无效。
6.设备相关的 ioctl 调用不同的设备类型可能需要使用不同的 ioctl 函数进行控制和管理。
对于声卡设备,常见的 ioctl 调用包括:- audio_ioctl:用于实现对声卡设备的音频输入输出控制。
Linux2.6内核中的Framebuffer驱动程序设计虽然Framebuffer驱动技术在PC上已经逐渐被淘汰,但是在嵌入式等考虑成本的平台下,由于其使用简单,成本低廉的优势,使用相当的广泛。
在Linux2.4和Linux2.6内核之间,Framebuffer的框架结构发生了很大的变化,网络上很多介绍Framebuffer的文档都是基于2.4内核下的,这就使得在2.6内核开发Framebuffer驱动增加了难度,本文介绍2.6内核下如何编写Framebuffer驱动,以适应最新版本的Linux。
Framebuffer是出现在Linux 2.2.xx及以后版本内核当中的一种驱动程序接口,这种接口将显示设备抽象为帧缓冲区设备。
帧缓冲区为图像硬件设备提供了一种抽象化处理,它代表了一些视频硬件设备,允许应用软件通过定义明确的界面来访问图像硬件设备。
这样软件无须了解任何涉及硬件底层驱动的东西(如硬件寄存器)。
它允许上层应用程序在图形模式下直接对显示缓冲区进行读写和I/O控制等操作。
通过专门的设备节点可对该设备进行访问,如/dev/fb*。
用户可以将它看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以进行读写操作,而读写操作可以反映到LCD。
二、 Framebuffer驱动的主要数据结构fb_fix_screeninfo记录了帧缓冲设备和指定显示模式的固件信息。
它包含了屏幕缓冲区的物理地址和长度等信息。
fb_var_screeninfo记录了帧缓冲设备和指定显示模式的可修改信息。
它包括显示屏幕的分辨率、每个像素的比特数和一些时序变量。
其中变量 xres定义了屏幕一行所占的像素数,yres定义了屏幕一列所占的像素数。
fb_info info是Linux为帧缓冲设备定义的驱动层接口。
它不仅包含了底层函数,而且还有记录设备状态的数据。
每个帧缓冲设备都与一个fb_info结构相对应。
其中成员变量包含fb_fix_screeninfo、fb_var_screeninfo这两个数据结构,另外还有Framebuffer的回调函数。
⼀、如何编写LinuxPCI驱动程序PCI的世界是⼴阔的,充满了(⼤部分令⼈不快的)惊喜。
由于每个CPU体系结构实现不同的芯⽚集,并且PCI设备有不同的需求(“特性”),因此Linux内核中的PCI⽀持并不像⼈们希望的那么简单。
这篇简短的⽂章介绍⽤于PCI设备驱动程序的Linux APIs。
1.1 PCI驱动程序结构PCI驱动程序通过pci_register_driver()在系统中"发现"PCI设备。
事实上,恰恰相反。
当PCI通⽤代码发现⼀个新设备时,具有匹配“描述”的驱动程序将被通知。
详情如下。
pci_register_driver()将设备的⼤部分探测留给PCI层,并⽀持在线插⼊/删除设备[因此在单个驱动程序中⽀持热插拔PCI、CardBus和Express-Card]。
pci_register_driver()调⽤需要传⼊⼀个函数指针表,从⽽指⽰驱动程序的更⾼⼀级结构体。
⼀旦驱动程序知道了⼀个PCI设备并获得了所有权,驱动程序通常需要执⾏以下初始化:启⽤设备请求MMIO / IOP资源设置DMA掩码⼤⼩(⽤于⼀致性DMA和流式DMA)分配和初始化共享控制数据(pci_allocate_coherent())访问设备配置空间(如果需要)注册IRQ处理程序(request_irq())初始化non-PCI(即LAN/SCSI/等芯⽚部分)启⽤DMA /处理引擎当使⽤设备完成时,可能需要卸载模块,驱动程序需要采取以下步骤:禁⽌设备产⽣irq释放IRQ (free_irq())停⽌所有DMA活动释放DMA缓冲区(包括流式DMA和⼀致性DMA)从其他⼦系统注销(例如scsi或netdev)释放MMIO / IOP资源禁⽤该设备下⾯⼏节将介绍这些主题中的⼤部分。
其余部分请查看LDD3或<linux/pci.h>。
如果PCI⼦系统没有配置(没有设置CONFIG_PCI),下⾯描述的⼤多数PCI函数都被定义为内联函数,要么完全空,要么只是返回⼀个适当的错误代码,以避免在驱动程序中出现⼤量ifdefs。
linux内核驱动中读写磁盘的接口函数Linux内核驱动中读写磁盘的接口函数在Linux操作系统中,内核驱动程序负责与硬件设备进行通信和控制。
磁盘作为一种常见的外部存储设备,对于内核驱动程序而言,读写磁盘是一项重要的功能。
本文将介绍Linux内核驱动中用于读写磁盘的接口函数。
1. read函数read函数是用于从磁盘读取数据的接口函数。
其原型如下:ssize_t read(struct file *file, char __user *buf, size_t count, loff_t *offset);参数解析:- file:表示要读取的文件对象,包含了文件在磁盘上的位置等信息。
- buf:表示接收读取数据的缓冲区。
- count:表示要读取的字节数。
- offset:表示读取的偏移量。
read函数的返回值表示实际读取到的字节数。
在读取数据时,内核驱动程序会根据文件对象中的位置信息,将数据从磁盘读取到缓冲区中,并更新文件对象的位置信息。
2. write函数write函数是用于向磁盘写入数据的接口函数。
其原型如下:ssize_t write(struct file *file, const char __user *buf, size_t count, loff_t *offset);参数解析:- file:表示要写入的文件对象,包含了文件在磁盘上的位置等信息。
- buf:表示待写入数据的缓冲区。
- count:表示要写入的字节数。
- offset:表示写入的偏移量。
write函数的返回值表示实际写入的字节数。
在写入数据时,内核驱动程序会根据文件对象中的位置信息,将缓冲区中的数据写入磁盘,并更新文件对象的位置信息。
3. open函数open函数是用于打开文件的接口函数。
其原型如下:int open(const char *pathname, int flags, mode_t mode);参数解析:- pathname:表示要打开的文件路径。
基于rk3568的linux驱动开发——gpio知识点基于rk3568的Linux驱动开发——GPIO知识点一、引言GPIO(General Purpose Input/Output)通用输入/输出,是现代计算机系统中的一种常用接口,它可以根据需要配置为输入或输出。
通过GPIO 接口,我们可以与各种外设进行通信,如LED灯、按键、传感器等。
在基于Linux系统的嵌入式设备上开发驱动程序时,熟悉GPIO的使用是非常重要的一环。
本文将以RK3568芯片为例,详细介绍GPIO的相关知识点和在Linux驱动开发中的应用。
二、GPIO概述GPIO是系统中的一个基本的硬件资源,它可以通过软件的方式对其进行配置和控制。
在嵌入式设备中,通常将一部分GPIO引脚连接到外部可编程电路,以实现与外部设备的交互。
在Linux中,GPIO是以字符设备的形式存在,对应的设备驱动为"gpiolib"。
三、GPIO的驱动开发流程1. 导入头文件在驱动程序中,首先需要导入与GPIO相关的头文件。
对于基于RK3568芯片的开发,需要导入头文件"gpiolib.h"。
2. 分配GPIO资源在驱动程序中,需要使用到GPIO资源,如GPIO所在的GPIO Bank和GPIO Index等。
在RK3568芯片中,GPIO资源的分配是通过设备树(Device Tree)来进行的。
在设备树文件中,可以定义GPIO Bank和GPIO Index等信息,以及对应的GPIO方向(输入或输出)、电平(高电平或低电平)等属性。
在驱动程序中,可以通过设备树接口(Device Tree API)来获取这些GPIO资源。
3. GPIO的配置与控制在驱动程序中,首先要进行GPIO的初始化与配置。
可以通过函数"gpiod_get()"来打开指定的GPIO,并判断其是否有效。
如果成功打开GPIO,则可以使用函数"gpiod_direction_output()"或"gpiod_direction_input()"来设置GPIO的方向,分别作为输出或输入。
alsa 原理ALSA原理解析什么是ALSA?ALSA(Advanced Linux Sound Architecture)是Linux操作系统中的一种音频处理架构。
它提供了一种标准的音频设备驱动程序接口,用于操作和控制音频设备,如声卡、麦克风、扬声器等。
ALSA的组成部分ALSA由以下几个主要的组成部分组成:1.音频设备驱动程序:负责与硬件之间的通信,将音频设备的输入输出转换为数字信号,并通过内核提供的接口向应用程序提供访问。
2.音频库:提供高级的音频操作接口,为应用程序提供简化的音频处理功能。
3.控制工具:用于配置和控制音频设备的命令行工具,如alsactl和amixer。
ALSA的工作原理ALSA的工作原理可以简单概括为以下几个步骤:1.设备检测:当系统启动或插入音频设备时,ALSA会进行设备检测,并加载相应的设备驱动程序。
2.设备配置:ALSA会自动对音频设备进行配置,包括设定采样率、位深度、声道数等参数。
3.应用程序访问:应用程序通过调用ALSA提供的API接口来访问音频设备。
应用程序可以使用ALSA提供的库函数来进行音频采集、播放、混音等操作。
4.数据传输:ALSA通过DMA(Direct Memory Access)技术将音频数据从内存传输到音频设备,或者从音频设备传输到内存。
5.数据处理:音频数据在传输过程中,可以经过一系列的处理,例如音频编码、解码、混音等。
6.音量控制:ALSA提供了音量控制接口,应用程序可以通过调用API来调整音频设备的输入和输出音量。
7.音频事件处理:ALSA可以捕捉音频设备发出的事件,并通过回调函数通知应用程序。
例如,当音频设备的状态发生变化时,应用程序可以接收到相应的通知。
总结通过以上解析,我们了解了ALSA的基本原理和工作流程。
ALSA提供了一个标准的音频处理框架,使应用程序能够方便地访问和控制音频设备。
它的底层驱动程序负责和硬件进行通信,而高级的音频库则提供了简化和抽象的接口,方便应用程序进行音频处理。
关于linuxmtd的理解MTD 设备是象闪存芯片、小型闪存卡、记忆棒等之类的设备,它们在嵌入式设备中的使用正在不断增长。
MTD 驱动程序是在 Linux 下专门为嵌入式环境开发的新的一类驱动程序。
相对于常规块设备驱动程序,使用 MTD 驱动程序的主要优点在于 MTD 驱动程序是专门为基于闪存的设备所设计的,所以它们通常有更好的支持、更好的管理和基于扇区的擦除和读写操作的更好的接口。
Linux 下的 MTD 驱动程序接口被划分为两类模块:用户模块和硬件模块。
MTD 驱动程序设置为了访问特定的闪存设备并将文件系统置于其上,需要将 MTD 子系统编译到内核中。
这包括选择适当的 MTD 硬件和用户模块。
当前,MTD 子系统支持为数众多的闪存设备― 并且有越来越多的驱动程序正被添加进来以用于不同的闪存芯片。
有两个流行的用户模块可启用对闪存的访问:MTD_CHAR 和MTD_BLOCK 。
MTD_CHAR 提供对闪存的原始字符访问,而 MTD_BLOCK 将闪存设计为可以在上面创建文件系统的常规块设备(象IDE 磁盘)。
与MTD_CHAR 关联的设备是/dev/mtd0、mtd1、mtd2(等等),而与MTD_BLOCK 关联的设备是/dev/mtdblock0、mtdblock1(等等)。
由于 MTD_BLOCK 设备提供象块设备那样的模拟,通常更可取的是在这个模拟基础上创建象 FTL 和 JFFS2 那样的文件系统。
为了进行这个操作,可能需要创建分区表将闪存设备分拆到引导装载程序节、内核节和文件系统节中。
Linux 中 MTD 子系统的主要目标是在系统的硬件驱动程序和上层,或用户模块之间提供通用接口。
硬件驱动程序不需要知道象JFFS2 和FTL 那样的用户模块使用的方法。
所有它们真正需要提供的就是一组对底层闪存系统进行 read 、 write 和 erase 操作的简单例程。
MTD 驱动程序是专门针对嵌入式Linux的一种驱动程序,相对于常规块设备驱动程序(比如PC中的IDE硬盘)而言,MTD驱动程序能更好的支持和管理闪存设备,因为它本身就是专为闪存设备而设计的。
linux 安装virtio驱动的方法一、什么是virtio驱动virtio驱动是一种用于虚拟化环境中的通用驱动程序,它提供了一种标准的接口,使得虚拟机可以与宿主机之间进行高效的数据传输和通信。
virtio驱动支持各种不同的设备类型,如网络适配器、磁盘控制器等,可以提高虚拟机的性能和稳定性。
二、安装virtio驱动的准备工作在安装virtio驱动之前,需要先准备以下工作:1. 确认虚拟化环境:确保已经在虚拟机中安装了KVM或者其他虚拟化软件,并且虚拟机已经开启了virtio设备的支持。
2. 下载virtio驱动:从官方网站或其他可信来源下载virtio驱动的安装包,一般以ISO镜像的形式提供。
三、安装virtio驱动安装virtio驱动的具体步骤如下:1. 将virtio驱动ISO镜像挂载到虚拟机中,可以通过以下命令挂载: ```# mount /dev/cdrom /mnt```其中,/dev/cdrom是ISO镜像所在的设备,/mnt是挂载点。
2. 进入virtio驱动的安装目录,一般是/mnt目录,可以通过以下命令进入:```# cd /mnt```3. 根据需要安装相应的virtio驱动,如网络适配器驱动、磁盘控制器驱动等。
执行以下命令安装网络适配器驱动:```# ./install.sh -n```执行以下命令安装磁盘控制器驱动:```# ./install.sh -d```其中,-n表示安装网络适配器驱动,-d表示安装磁盘控制器驱动。
根据实际需要选择对应的选项。
4. 完成安装后,重新启动虚拟机以使驱动生效。
四、验证virtio驱动是否安装成功安装完成后,可以通过以下方法验证virtio驱动是否成功安装:1. 检查设备:执行以下命令查看是否存在virtio设备:```# lspci | grep Virtio```如果输出中存在virtio设备,则说明驱动安装成功。
2. 检查驱动模块:执行以下命令查看是否加载了virtio驱动的模块: ```# lsmod | grep virtio```如果输出中存在virtio驱动的模块,则说明驱动加载成功。
⏹ Linux 网络设备驱动结构Linux的加载和卸载设备的注册初始化和注销设备的打开和释放据包的发送和接收络连接状况数设置和统计数据此驱动所支持的网卡系列初始化网络设备注销网络设备设备挂起函数设备恢复函数打开网络设备关闭网络设备读取包的网卡收发包的状态,统计数据用户的ioctl 命令系统调用硬件处理数据包发送ISR 数据包发送和接收⏹ struct pci_driver如果网络设备(包括wireless )是PCI 规范的,则先是向内核注册该PCI 设备(pci_register_driver),然后由pci_driver 数据结构中的probe 函数指针所指向的侦测函数来初始化该PCI 设备,并且同时注册和初始化该网络设备。
如果网络设备(包括wireless )是PCMCIA 规范的,则先是向内核注册该PCMCIA 设备(register_pccard_driver),然后driver_info_t 数据结构中的attach 函数指针所指向的侦测函数来初始化该PCMCIA 设备,并且同时注册和初始化该网络设备。
1. 申明为PCI 设备:static struct pci_driver tg3_driver = {.name = DRV_MODULE_NAME,//此驱动所支持的网卡系列,vendor_id, device_id.id_table = tg3_pci_tbl,//初始化网络设备的回调函数.probe = tg3_init_one,//注销网络设备的回调函数.remove = __devexit_p(tg3_remove_one),//设备挂起函数.suspend = tg3_suspend,//设备恢复函数.resume = tg3_resume};2. 驱动模块的加载和卸载static int __init tg3_init(void){//先注册成PCI设备,并初始化,如果是其他的ESIA,PCMCIA,用其他函数return pci_module_init(&tg3_driver);}static void __exit tg3_cleanup(void){pci_unregister_driver(&tg3_driver);//注销PCI设备}module_init(tg3_init); //驱动模块的加载module_exit(tg3_cleanup); //驱动模块的卸载3. PCI设备探测函数probe,初始化网络设备主要工作:申请并设置pci资源(内存),申请并设置net_device网络设备结构,IO映射,注册网络设备static int __devinit tg3_init_one(struct pci_dev *pdev, const struct pci_device_id *ent){//初始化设备,使I/O,memory可用,唤醒设备pci_enable_device(pdev);//申请内存空间,配置网卡的I/O,memory资源pci_request_regions(pdev, DRV_MODULE_NAME);pci_set_master(pdev);//设置DMA属性pci_set_dma_mask(pdev, (u64) 0xffffffffffffffff);//网卡 I/O,memory资源的启始地址tg3reg_base = pci_resource_start(pdev, 0);//网卡I/O,memory资源的大小tg3reg_len = pci_resource_len(pdev, 0);//分配并设置网络设备dev = alloc_etherdev(sizeof(*tp));//申明为内核设备模块SET_MODULE_OWNER(dev);//初始化私有结构中的各成员值tp = dev->priv;tp->pdev = pdev;tp->dev = dev;……//锁的初始化spin_lock_init(&tp->lock);//映射I/O,memory地址到私有域中的寄存器结构tp->regs = (unsigned long) ioremap(tg3reg_base, tg3reg_len);dev->irq = pdev->irq;//网络设备回调函数赋值dev->open = tg3_open;dev->stop = tg3_close;dev->get_stats = tg3_get_stats;dev->set_multicast_list = tg3_set_rx_mode;dev->set_mac_aDDRess = tg3_set_mac_addr;dev->do_ioctl = tg3_ioctl;dev->tx_timeout = tg3_tx_timeout;dev->hard_start_xmit= tg3_start_xmit;//网卡的MAC地址赋值dev->addrtg3_get_device_address(tp);//注册网络设备register_netdev(dev);//把网络设备指针地址放入PCI设备中的设备指针中pci_set_drvdata(pdev, dev);}4. 注销网络设备主要工作:注销并释放网络设备,取消地址映射,释放PCI资源static void __devexit tg3_remove_one(struct pci_dev *pdev){struct net_device *dev = pci_get_drvdata(pdev);//注销网络设备unregister_netdev(dev);//取消地址映射iounmap((void *) ((struct tg3 *)(dev->priv))->regs);//释放网络设备kfree(dev);//释放PCI资源pci_release_regions(pdev);//停用PCI设备pci_disable_device(pdev);//PCI设备中的设备指针赋空pci_set_drvdata(pdev, NULL);}5. 网络设备挂起主要工作:停用网卡的中断寄存器,停止网卡收发包,停用网卡某些硬件,设置电源状态static int tg3_suspend(struct pci_dev *pdev, u32 state){//停用网卡的中断寄存器tg3_disable_ints(tp);//停止网卡收发包netif_device_detach(dev);//停止网卡某些硬件,fireware的一些功能tg3_halt(tp);//设置网卡的电源状态tg3_set_power_state(tp, state);}6. 网络设备恢复主要工作:恢复网卡电源,允许收发包,初始化收发包的缓冲区,初始化网卡硬件,打开网卡中断寄存器static int tg3_resume(struct pci_dev *pdev){//恢复网卡电源tg3_set_power_state(tp, 0);//允许网卡收发包netif_device_attach(dev);//初始化收发包的缓冲区tg3_init_rings(tp);//初始化网卡硬件tg3_init_hw(tp);//打开网卡中断寄存器tg3_enable_ints(tp);}struct net_device1. 打开网络设备主要工作:分配中断及注册中断处理函数,初始化硬件及收发缓冲区,初始化定时器及注册超时函数,允许网卡开始传送包static int tg3_open(struct net_device *dev){//分配一个中断request_irq(dev->irq, tg3_interrupt, SA_SHIRQ, dev->name, dev);/* int request_irq(unsigned int irq,void (*handler)(int irq, void *dev_id, struct pt_regs *regs),unsigned long irqflags,const char * devname,void *dev_id);irq是要申请的硬件中断号。
发信人: olly (剑胆琴心), 信区: Linux标题: LINUX下的设备驱动程序三、UNIX系统下的设备驱动程序3.1、UNIX下设备驱动程序的基本结构在UNIX系统里,对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,一般来说是把设备映射为一个特殊的设备文件,用户程序可以象对其它文件一样对此设备文件进行操作。
UNIX对硬件设备支持两个标准接口:块特别设备文件和字符特别设备文件,通过块(字符)特别设备文件存取的设备称为块(字符)设备或具有块(字符)设备接口。
块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行,它可以支持几乎任意长度和任意位置上的I/O请求,即提供随机存取的功能。
字符设备接口支持面向字符的I/O操作,它不经过系统的快速缓存,所以它们负责管理自己的缓冲区结构。
字符设备接口只支持顺序存取的功能,一般不能进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长的倍数。
显然,本程序所驱动的串行卡只能提供顺序存取的功能,属于是字符设备,因此后面的讨论在两种设备有所区别时都只涉及字符型设备接口。
设备由一个主设备号和一个次设备号标识。
主设备号唯一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。
次设备号仅由设备驱动程序解释,一般用于识别在若干可能的硬件设备中,I/O请求所涉及到的那个设备。
设备驱动程序可以分为三个主要组成部分:(1) 自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否能正常工作。
如果该设备正常,则对这个设备及其相关的、设备驱动程序需要的软件状态进行初始化。
这部分驱动程序仅在初始化的时候被调用一次。
(2) 服务于I/O请求的子程序,又称为驱动程序的上半部分。
调用这部分是由于系统调用的结果。
这部分程序在执行的时候,系统仍认为是和进行调用的进程属于同一个进程,只是由用户态变成了核心态,具有进行此系统调用的用户程序的运行环境,因此可以在其中调用sleep()等与进程运行环境有关的函数。
正如Linux torvalds所说\"we\re back to the times when men were men and wrote their device drivers\",come on,then!以下我将我的小心得与大家分享,并请高手指点江山啊!§1.Linux驱动程序接口系统调用是操作系统内核与应用程序之间的接口,设备驱动程序则是操作系统内核与机器硬件的接口。
几乎所有的系统操作最终映射到物理设备,除了CPU、内存和少数其它设备,所有的设备控制操作都由该设备特殊的可执行代码实现,此代码就是设备驱动程序。
操作系统内核需要访问两类主要设备:字符设备和块设备。
与此相关主要有两类设备驱动程序,字符设备驱动程序和块设备驱动程序。
Linux(也是所有UNIX)的基本原理之一是:系统试图使它对所有各类设备的输入、输出看起来就好象对普通文件的输入、输出一样。
设备驱动程序本身具有文件的外部特征,它们都能使用象open(),close(),read(),write()等系统调用。
为使设备的存取能象文件一样处理,所有设备在目录中应有对应的文件名称,才可使用有关系统调用。
通常Linux驱动程序接口分为如下四层:1).应用程序进程与内核的接口;2).内核与文件系统的接口;3).文件系统与设备驱动程序的接口;4).设备驱动程序与硬件设备的接口。
§2.驱动程序文件操作数据结构每个驱动程序都有一个file-operation的数据结构,包含指向驱动程序内部函数的指针。
file-operation的数据结构为:struct file-operation{int(*lseek)();int(*read)();int(*write)();int(*readdir)();int(*select)();int(*ioctl)();int(*mmap)();int(*open)();int(*close)();int(*release)();int(*fsync)();int(*fasync)();int(*check-media-change)();int(*revalidate)();}内核中有两个表,一个用于字符设备驱动程序,一个用于块设备驱动程序。
这两个表用于保存指向file-operation数据结构的指针,驱动程序内部函数的地址保存在这一结构。
内核用主设备号作为索引访问file-operation结构,可以访问驱动程序子程序地址。
SBS617设备采用了PCI总线字符设备的驱动程序实现方式。
完成了设备驱动程序,经GNU软件编译,链接,产生一可加载模块,可以用于动态装入Linux操作系统内核,也可以在需要时从内核中卸除。
§3.file_operations介绍在结构file_operations里,指出了设备驱动程序所提供的入口点位置,分别是:(1)lseek,移动文件指针的位置,显然只能用于可以随机存取的设备。
(2)read,进行读操作,参数buf为存放读取结果的缓冲区,count为所要读取的数据长度。
返回值为负表示读取操作发生错误,否则返回实际读取的字节数。
对于字符型,要求读取的字节数和返回的实际读取字节数都必须是inode->i_blksize的的倍数。
(3)write,进行写操作,与read类似。
(4)readdir,取得下一个目录入口点,只有与文件系统相关的设备驱动程序才使用。
(5)selec,进行选择操作,如果驱动程序没有提供select入口,select操作将会认为设备已经准备好进行任何的I/O操作。
(6)ioctl,驱动程序特殊控制入口点,进行读、写以外的其它操作,参数cmd为自定义的命令。
这是很有意思的部分,之后我会详尽介绍;(7)mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。
(8)open,打开设备准备进行I/O操作。
返回0表示打开成功,返回负数表示失败。
如果驱动程序没有提供open入口,则只要/dev/driver文件存在就认为打开成功。
(9)release,即close操作。
设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进行登记,以便系统在适当的时候调用。
§4PCI字符设备驱动程序要设计PCI设备驱动程序,必须进一步结合硬件设备和PCI总线的特性。
设计PCI设备驱动程序的重要任务是找寻相应的硬件并实现对它的访问。
作为外围设备的硬件必须响应三种地址空间的访问,即内存,IO,寄存器地址空间。
前两种地址空间可以为PCI总线上的所有设备共享。
寄存器空间占用物理地址,可以通过特殊的函数来访问配置寄存器。
一旦可以访问配置寄存器,设备驱动程序就可以访问硬件了。
每个设备的PCI配置寄存器均由256Bytes构成,其中64Bytes是标准化的,4Bytes标识了一个唯一的函数ID,通过这个ID驱动程序就可以定位该设备。
存取系统中的字符设备和存取系统文件一样。
应用程序使用标准的系统调用来打开、读写和关闭设备,就像使用一个文件-样。
当字符设备初始化时,通过向chrdevs数组中添加一个入口,设备驱动程序在系统内核中注册。
chrdevs数组由device_struct数据结构组成。
设备的主设备号用来作为此chrdevs的索引,因为一个设备的主设备号是固定的。
LINUX系统里,通过调用register_chrdev向系统注册字符型设备驱动程序。
register_chrdev定义为:#include linux/fs.h#include linux/errno.hint register_chrdev(unsigned int major,const char*name,struct file_operations*fops);其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。
name是设备名。
fops就是前面所说的对各个调用的入口点的说明。
此函数返回0表示成功。
返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。
返回-EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。
如果是动态分配主设备号成功,此函数将返回所分配的主设备号。
§5PCI设备启动与检测PC主板BIOS在系统启动时,可以自动检测PCI设备并配置设备的每一地址区。
当驱动程序访问设备时,它的内存、I/O地址空间已经映射到进程的地址空间了。
在驱动程序init_module()中,通过调用函数pcibios_find_device()函数返回设备在总线上的位置及函数指针,其中的包含文件及函数原型为:#include Linux/pci.h#include Linux/config.h#include Linux/bios32.hint pcibios_find_device(unsigned short vendor,unsigned short id,unsigned short index,unsigned char*bus,unsigned short*function)§6地址空间访问在设备驱动程序检测到设备之后,通常要从三个地址空间读写数据,其中寄存器空间的读写尤为重要,因为只有通过它驱动程序才可能找到设备内存和I/O空间的映射地址。
设备驱动程序通过调用以下函数实现寄存器空间的访问,其中的包含文件及函数原型为:#include Linux/bios32.hint pcibios_read_config_byte(unsigned char bus,unsigned char function,unsigned char where,unsigned char b*ptr)int pcibios_write_config_byte(unsigned char bus,unsigned char function,unsigned char where,unsigned char b*ptr)类似的还有:pcibios_read_config_word(),pcibios_write_config_word(),pcibios_read_config_dword(),pcibios_write_config_dword()调用。
PCI设备最多有6个地址区,类型可以为内存区或I/O区。
接口板可以通过配置寄存器的PCI_BASE_ADDRESS_0到PCI_BASE_ADDRESS_5来报告各地址区的实际地址位置。
内存、IO空间的访问通过inb(),memcpy()等调用。
当然可以通过pcibios_read_config_byte(),pcibios_write_config_byte()来访问配置寄存器的相应基地址值。
§7中断处理对中断的处理是属于系统核心的部分,PC主板BIOS为多数设备分配了一个唯一的中断号,在配置寄存器中保存,设备驱动程序通过pcibios_read_config_byte()函数读取相应的值,格式为:xxx_irq=pcibios_read_config_byte(pci_bus,pci_device_fn,PCI_INTERRUPT_LINE,&pci_cofig->int_line)操作系统中有中断寄存器,将特定的中断请求与中断处理函数联系在一起,当中断发生时调用相应的中断处理函数处理。
Linux操作系统下可用request_irq(),free_irq()实现中断的请求,释放,其中包含文件及形式为:#include Linux/sched.hint request_irq(unsigned int irq,void(*handler)(int irq,void dev_id,struct pt_regs*regs),unsigned long flags,const char*device,void*dev_id);void free_irq(unsigned int irq,void*dev_id);参数irq表示所要申请的硬件中断号。
handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。
device为设备名,将会出现在/proc/interrupts文件里。
flag是申请时的选项,它决定中断处理程序的一些特性,有两种方式写中断方式设备驱动程序:即快中断方式和定时等待方式。
采取快中断方式需要将request_irq()的第三个type类型参数设为SA_INTERRUPT。