1。 I/O Port。 和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。 有两个重要的kernel函数可以保证驱动程序做到这一点。 1)check_region(int io_port, int off_set) 这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。 参数1:io端口的基地址, 参数2:io端口占用的范围。 返回值:0 没有占用, 非0,已经被占用。 2)request_region(int io_port, int off_set,char *devname) 如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports文件中可以看到你登记的io口。 参数1:io端口的基地址。 参数2:io端口占用的范围。 参数3:使用这段io地址的设备名。 在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。 在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当于访问一段内存。经常性的,我们要获得一块内存的物理地址。 2。内存操作 在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages直接申请页。释放内存用的是kfree,或free_pages。 请注意,kmalloc等函数返回的是物理地址! 注意,kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。 内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得重新映射以后的地址。 另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块程序需要一直驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。 这可以通过牺牲一些系统内存的方法来解决。 3。中断处理 同处理I/O端口一样,要使用一个中断,必须先向系统登记。 int request_irq(unsigned int irq ,void(*handle)(int,void *,struct pt_regs *), unsigned int long flags, const char *device); irq: 是要申请的中断。 handle:中断处理函数指针。 flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。 device:设备名。 如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的中断。 4。一些常见的问题。 对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作的话,gcc往往会对你的程序进行优化,这样时序会发生错误。如果用汇编写呢,gcc同样会对汇编代码进行优化,除非用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现出来。
由于设备种类繁多,相应的设备驱动程序也非常之多。尽管设备驱动程序是内核的一部分,但设备驱动程序的开发往往由很多人来完成,如业余编程高手、设备厂商等。为了让设备驱动程序的开发建立在规范的基础上,就必须在驱动程序和内核之间有一个严格定义和管理的接口,例如SVR4提出了DDI/DDK规范,其含义就是设备与驱动程序接口/设备驱动程序与内核接口(Device-Driver Interface/Driver-Kernel Interface)。通过这个规范,可以规范设备驱动程序与内核之间的接口。
Linux的设备驱动程序与外接的接口与DDI/DKI规范相似,可以分为三部分: (1) 驱动程序与内核的接口,这是通过数据结构file_operations来完成的。 (2) 驱动程序与系统引导的接口,这部分利用驱动程序对设备进行初始化。 (3) 驱动程序与设备的接口,这部分描述了驱动程序如何与设备进行交互,这与具体设备密切相关。
根据功能,驱动程序的代码可以分为如下几个部分: (1)驱动程序的注册和注销 (2)设备的打开与释放 (3)设备的读和写操作 (4)设备的控制操作 (5)设备的中断和查询处理 前三点我们已经给予简单说明,后面我们还会结合具体程序给出进一步的说明。关于设备的控制操作可以通过驱动程序中的ioctl()来完成,例如,对光驱的控制可以使用cdrom_ ioctl()。 与读写操作不同,ioctl()的用法与具体设备密切相关,例如,对于软驱的控制可以使用floppy_ioctl(),其调用形式为: static int floppy_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long param) 其中cmd的取值及含义与软驱有关,例如,FDEJECT表示弹出软盘。 除了ioctl(),设备驱动程序还可能有其他控制函数,如lseek()等。 对于不支持中断的设备,读写时需要轮流查询设备的状态,以便决定是否继续进行数据传输,例如,打印机驱动程序在缺省时轮流查询打印机的状态。如果设备支持中断,则可按中断方式进行 |
|