Upload
ray
View
95
Download
0
Embed Size (px)
DESCRIPTION
Linux 设备驱动程序. 04/05/2006 应忍冬. 内容. 设备分类 设备驱动程序的框架 字符型设备 网络设备 文件系统 User Space File System USB 设备 FrameBuffer 例子和使用 Debug 原理和 Debug 方法 常用设备 /fb/ram/loopback/zero. 设备驱动程序的任务. 设备初始化 硬件操作和管理 外部硬件和内核空间的数据传递 内核空间和用户空间的数据传递. 设备驱动程序的功能. 用户空间. 用户程序. 内核空间. 设备驱动程序. 存储缓冲. 外部硬件. 用户程序 - PowerPoint PPT Presentation
Citation preview
Linux 设备驱动程序
04/05/2006
应忍冬
内容• 设备分类• 设备驱动程序的框架• 字符型设备• 网络设备• 文件系统
– User Space File System
• USB 设备• FrameBuffer 例子和使用• Debug 原理和 Debug 方法• 常用设备 /fb/ram/loopback/zero
设备驱动程序的任务• 设备初始化• 硬件操作和管理• 外部硬件和内核空间的数据传递• 内核空间和用户空间的数据传递
设备驱动程序的功能
外部硬件
设备驱动程序
用户程序
存储缓冲
用户空间
内核空间
用户态程序 vs 内核态程序用户程序
• 权限受限• 虚拟运行环境
– 逻辑地址– 关键资源访问受监管
• 函数调用由用户控制
内核程序• 最高权限• 实际的运行环境
– 物理地址– 可访问所有资源
• 函数由内核直接调用
可以运行驱动程序
地址映射与物理地址访问
物理地址空间
用户进程 1 用户进程 2 用户进程 3
虚拟地址映射
用户利用指针访问的是虚地址,不是物理地址, IO 设备的物理地址可能是用户进程不可触
及的
虚拟地址映射 虚拟地址映射
直接访问内核内存 (/dev/kmem)
kmfd = open("/dev/kmem", O_RDONLY ); lseek( kmfd, offset, SEEK_SET ); read( kmfd, byteArray, byteArrayLen ); close(kmfd);
• 直接访问内核地址(内核态的虚地址)• 一般内核地址起始于 0xC0000000
直接访问物理地址 (/dev/mem)
mem_fd = open("/dev/mem", O_RDONLY ); b=mmap(0, 0x10000, PROT_READ|PROT_WRITE,MAP_SHARED, mem_fd,0xA0000)…close(memfd);
0xA0000
0xB0000
Pointer bmmap 将文件中的数据映射成数组
这里是将物理内存(由特殊文件 /dev/mem 访问)映射成指针 b 指向的数组。
注意,指针 b 的值不一定是 0xA0000 ,它是和物理地址 0xA0000 对应的用户态的虚拟地址
Linux 中 /dev/mem 主要是用于设备内存的访问 ( 比如显卡内存 ) ,而不是普通存储器
直接访问 IO端口 (/dev/port)
port_fd = open("/dev/port", O_RDWR); lseek(port_fd, port_addr, SEEK_SET); read(port_fd, …);write(port_fd, …); close(port_fd);
• 注意:不能用 fopen/fread/fwrite/fclose因为它们有数据缓冲,对读写操作不是立即完成的
outb()/outw()/inb()/inw() 函数#include <stdio.h>#include <unistd.h>#include <asm/io.h>#define BASEPORT 0x378 // printerint main(){ ioperm(BASEPORT, 3, 1)); // get access
permission outb(0, BASEPORT); usleep(100000); printf("status: %d\n", inb(BASEPORT + 1)); ioperm(BASEPORT, 3, 0)); // give up exit(0);}
• ioperm(from,num,turn_on) • 用 ioperm 申请的操作端口地址在 0x000~0x3FF ,利用
iopl() 可以申请所有的端口地址• 必须以 root 运行• 用 “ gcc -02 –o xxx.elf xxx.c” 编译
• outb(value, port); inb(port); // 8-bit• outw(value, port); inw(port); // 16-bit• 访问时间大约 1us
设备驱动程序内访问设备地址• 设备驱动程序可以通过指针访问设备地址• 设备驱动程序接触到的还是虚拟地址,但
对于外界设备有固定的设备地址映射(设备的地址在移植 Linux 时候确定)
物理内存地址空间
设备驱动程序
虚拟地址映射
设备地址空间
设备地址映射
设备驱动程序
虚拟地址映射 设备地址映射
直接访问 IO端口 vs 设备驱动程序
IO直接访问• 用户态• 程序编写 / 调试简单• 查询模式,响应慢• 设备共享管理困难
设备驱动访问• 核心态• 编程调试困难• 可用中断模式访问、快
• 设备共享管理简单(由内核帮助完成)
设备分类•字符设备
–鼠标、串口、游戏杆•块设备
–磁盘、打印机•网络设备
–由 BSD Socket访问
字符设备 vs 块设备
字符设备• 字符设备发出读 / 写请求时,对应的硬件I/O一般立即发生。
• 数据缓冲可有可无
• ADC/DAC、按钮、 LED、传感器等
块设备• 利用一块系统内存作
缓冲区,一般读写由缓冲区直接提供,尽量减少 IO操作
• 针对磁盘等慢速设备
可装卸的设备驱动程序和静态连接到内核的设备驱动程序•静态连接到内核的设备驱动程序
–修改配置文件、重新编译和安装内核•可装卸的设备驱动程序
– insmod 装载– rmmod 卸载– lsmod 查询
Linux对硬件设备的抽象
设备文件• Open/Close/Read/Write•例子
– /dev/mouse– /dev/lp0
驱动程序与设备文件
设备驱动程序
设备文件
用 mknod命令创建
用 insmod 命令安装,或直接编译到内核中
用户程序
用 open/read/write/close等命令访问
通过主设备号找到设备驱动
驱动程序代码结构
驱动程序注册与注销
设备文件的操作函数(*open)()(*write)()(*flush)()(*llseek)()
…
中断服务程序
LED 设备驱动程序的例子
CPU
struct file_operations LED_fops = { read: LED_read, write: LED_write, open: LED_open, release: LED_release,};
int LED_init_module(void) { SET_MODULE_OWNER(&LED_fops); LED_major = register_chrdev(0, "LED", &LED_fops); LED_off(); LED_status=0; return 0; }
void LED_cleanup_module(void) { unregister_chrdev(LED_major, "LED"); }
module_init(LED_init_module);module_exit(LED_cleanup_module);
程序列表 ( 1 )
程序列表 ( 2 )int LED_open(struct inode *inode, struct file *filp){ printk("LED_open()\n"); MOD_INC_USE_COUNT; return 0;}
int LED_release(struct inode *inode, struct file *filp){ printk(“LED_release()\n“); MOD_DEC_USE_COUNT; return 0;}
程序列表 ( 3 )ssize_t LED_read (struct file *filp, char *buf, size_t count, loff_t *f_pos){ int i; for (i=0; i<count; i++) *((char*)(buf+i)) = LED_Status; return count;}ssize_t LED_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos){ int i; for (i=0; i<count; i++)
if (*((char*)(buf+i))) Data->LED_on(); else Data->LED_off(); return count;}
(*((volatile unsigned int *)(0xXXXXXXXX))) |= MASK;(*((volatile unsigned int *)(0xXXXXXXXX))) &=~MASK;
#ifndef __KERNEL__ #define __KERNEL__#endif#ifndef MODULE #define MODULE#endif#include <linux/config.h>#include <linux/module.h>#include <linux/sched.h>#include <linux/kernel.h>#include <linux/malloc.h>#include <linux/errno.h> #include <linux/types.h> #include <linux/interrupt.h>#include <linux/in.h>#include <linux/netdevice.h>#include <linux/etherdevice.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/skbuff.h>#include <sysdep.h>#include <linux/ioctl.h> #include <linux/in6.h>#include <asm/checksum.h>MODULE_AUTHOR("Rendong Ying");int LED_major, LED_status;
程序列表 ( 4 )
头文件
程序编译 ( Makefile )CC = arm-elf-linux-gccLD = arm-elf-linux-ldINCLUDE = /usr/local/src/bspLinux/includeLIB_INC = /usr/local/lib/gcc-lib/arm-elf-linux/2.95.3/includeCFLAGS = -O6 -Wall -DCONFIG_KERNELD -DMODULE -D__KERNEL__ -DLinux -nostdinc -I- -I . -I$(INCLUDE) -idirafter $(LIB_INC)
LED.o: LED.c$(CC) $(CFLAGS) -c LED.c
clean:rm -f LED.o 生成 o 文件
设备装载和设备文件建立• chmod +x /tmp/LED.o• /sbin/insmod -f ./LED.o• cat /proc/devices得到装入内核的主设备号
• mknod /dev/Lamp c Num1 Num2Num1为主设备号Num2为次设备号 强制安装,忽略版本检查
设备的测试和使用• 命令行
echo 8 > /proc/sys/kernel/printkcat /dev/Lamp cat > /dev/Lamp
• 程序void main(){ int fd=open(“/dev/Lamp, O_RDWR); write(fd, &data, 1); close(fd);}
开启 printk ,也可以从 /var/log/messages 看 printk 的记录
设备卸载/sbin/rmmod LEDrm -f /dev/Lamp
Function of
MOD_INC_USE_COUNT;
MOD_DEC_USE_COUNT;
复杂的设备驱动程序
驱动程序注册与注销(注册 / 注销 设备、中断)
设备文件的操作函数(*open)()(*write)()(*flush)()(*llseek)()
…
中断服务程序内核数据缓冲区
用户数据空间
复杂设备驱动程序的例子(USB Device)
中断资源申请和释放• if (request_irq(USB_INTR_SOURCE1, usb_ep1_int, SA_INTERRUPT, "USB EP1", 0) < 0) printk("Int. req. failed !\n");
• free_irq(USB_INTR_SOURCE0, 0);
cat /proc/interrupts
中断服务程序• 没有返回参数• 简短快速
void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { //…}
数据接收中断服务程序void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_hardware_FIFO(); put_data_into_data_buffer();}
数据发送中断服务程序void usb_ep2_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_buffer(); send_data_hardware_FIFO ();}
设备文件接口函数 (read)ssize_t usb_ep1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos){ if (data_buffer_empty()) return 0; else copy_data_to_user_space(); return data_copyed;}
copy_to_user(user_buf, device_driver_buf, size);
设备文件接口函数(read, blocking mode)
ssize_t usb_ep1_read (struct file *filp, char *buf, size_t count, loff_t *f_pos){ while(device_driver_buf_empty()) { if (wait_event_interruptible(q_ep2, device_driver_buf_not_empty)) return -ERESTARTSYS; }
copy_data_to_user_space(); return data_copyed;}
wait_queue_head_t rq_EP2;
init_waitqueue_head(&rq_EP2);
设备文件接口函数 (write)ssize_t usb_ep2_write (struct file *filp, char *buf, size_t count, loff_t *f_pos){ if (data_buffer_full()) return 0; else copy_data_to_device_driver_buf(); if (no_transmission_now) send_1st_data(); return data_copyed;}
copy_from_user(device_driver_buf, user_buf, size);
内存申请• malloc ? X
• kmalloc• kfree
• vmalloc• vfree
禁止设备打开多次int LED_flag;int LED_init_module(void){ LED_flag=0; …
} int LED_open(struct inode *inode, struct file *filp){ if (LED_flag=0) { LED_flag=1; MOD_INC_USE_COUNT; return 0; } else
return -ENODEV;}
int LED_release(struct inode *inode, struct file *filp){ LED_flag=0; MOD_DEC_USE_COUNT; return 0;}
同一设备驱动管理几个接口
Serial Port Device Driver
UART 0 UART 0
应用程序
同一设备驱动管理几个接口int dev_open(struct inode *inode, struct file *filp){ int minor = MINOR(inode->i_rdev); filp->private_data=sub_dev_dat[minor]; …}
ssize_t dev_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{ switch(*(filp->private_data)) { … }}