39
Linux 设设设设设设 04/05/2006 设设设

Linux 设备驱动程序

  • 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

Page 1: Linux 设备驱动程序

Linux 设备驱动程序

04/05/2006

应忍冬

Page 2: Linux 设备驱动程序

内容• 设备分类• 设备驱动程序的框架• 字符型设备• 网络设备• 文件系统

– User Space File System

• USB 设备• FrameBuffer 例子和使用• Debug 原理和 Debug 方法• 常用设备 /fb/ram/loopback/zero

Page 3: Linux 设备驱动程序

设备驱动程序的任务• 设备初始化• 硬件操作和管理• 外部硬件和内核空间的数据传递• 内核空间和用户空间的数据传递

Page 4: Linux 设备驱动程序

设备驱动程序的功能

外部硬件

设备驱动程序

用户程序

存储缓冲

用户空间

内核空间

Page 5: Linux 设备驱动程序

用户态程序 vs 内核态程序用户程序

• 权限受限• 虚拟运行环境

– 逻辑地址– 关键资源访问受监管

• 函数调用由用户控制

内核程序• 最高权限• 实际的运行环境

– 物理地址– 可访问所有资源

• 函数由内核直接调用

可以运行驱动程序

Page 6: Linux 设备驱动程序

地址映射与物理地址访问

物理地址空间

用户进程 1 用户进程 2 用户进程 3

虚拟地址映射

用户利用指针访问的是虚地址,不是物理地址, IO 设备的物理地址可能是用户进程不可触

及的

虚拟地址映射 虚拟地址映射

Page 7: Linux 设备驱动程序

直接访问内核内存 (/dev/kmem)

kmfd = open("/dev/kmem", O_RDONLY ); lseek( kmfd, offset, SEEK_SET ); read( kmfd, byteArray, byteArrayLen ); close(kmfd);

• 直接访问内核地址(内核态的虚地址)• 一般内核地址起始于 0xC0000000

Page 8: Linux 设备驱动程序

直接访问物理地址 (/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 主要是用于设备内存的访问 ( 比如显卡内存 ) ,而不是普通存储器

Page 9: Linux 设备驱动程序

直接访问 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因为它们有数据缓冲,对读写操作不是立即完成的

Page 10: Linux 设备驱动程序

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

Page 11: Linux 设备驱动程序

设备驱动程序内访问设备地址• 设备驱动程序可以通过指针访问设备地址• 设备驱动程序接触到的还是虚拟地址,但

对于外界设备有固定的设备地址映射(设备的地址在移植 Linux 时候确定)

物理内存地址空间

设备驱动程序

虚拟地址映射

设备地址空间

设备地址映射

设备驱动程序

虚拟地址映射 设备地址映射

Page 12: Linux 设备驱动程序

直接访问 IO端口 vs 设备驱动程序

IO直接访问• 用户态• 程序编写 / 调试简单• 查询模式,响应慢• 设备共享管理困难

设备驱动访问• 核心态• 编程调试困难• 可用中断模式访问、快

• 设备共享管理简单(由内核帮助完成)

Page 13: Linux 设备驱动程序

设备分类•字符设备

–鼠标、串口、游戏杆•块设备

–磁盘、打印机•网络设备

–由 BSD Socket访问

Page 14: Linux 设备驱动程序

字符设备 vs 块设备

字符设备• 字符设备发出读 / 写请求时,对应的硬件I/O一般立即发生。

• 数据缓冲可有可无

• ADC/DAC、按钮、 LED、传感器等

块设备• 利用一块系统内存作

缓冲区,一般读写由缓冲区直接提供,尽量减少 IO操作

• 针对磁盘等慢速设备

Page 15: Linux 设备驱动程序

可装卸的设备驱动程序和静态连接到内核的设备驱动程序•静态连接到内核的设备驱动程序

–修改配置文件、重新编译和安装内核•可装卸的设备驱动程序

– insmod 装载– rmmod 卸载– lsmod 查询

Page 16: Linux 设备驱动程序

Linux对硬件设备的抽象

设备文件• Open/Close/Read/Write•例子

– /dev/mouse– /dev/lp0

Page 17: Linux 设备驱动程序

驱动程序与设备文件

设备驱动程序

设备文件

用 mknod命令创建

用 insmod 命令安装,或直接编译到内核中

用户程序

用 open/read/write/close等命令访问

通过主设备号找到设备驱动

Page 18: Linux 设备驱动程序

驱动程序代码结构

驱动程序注册与注销

设备文件的操作函数(*open)()(*write)()(*flush)()(*llseek)()

中断服务程序

Page 19: Linux 设备驱动程序

LED 设备驱动程序的例子

CPU

Page 20: Linux 设备驱动程序

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 )

Page 21: Linux 设备驱动程序

程序列表 ( 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;}

Page 22: Linux 设备驱动程序

程序列表 ( 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;

Page 23: Linux 设备驱动程序

#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 )

头文件

Page 24: Linux 设备驱动程序

程序编译 ( 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 文件

Page 25: Linux 设备驱动程序

设备装载和设备文件建立• chmod +x /tmp/LED.o• /sbin/insmod -f ./LED.o• cat /proc/devices得到装入内核的主设备号

• mknod /dev/Lamp c Num1 Num2Num1为主设备号Num2为次设备号 强制安装,忽略版本检查

Page 26: Linux 设备驱动程序

设备的测试和使用• 命令行

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 的记录

Page 27: Linux 设备驱动程序

设备卸载/sbin/rmmod LEDrm -f /dev/Lamp

Function of

MOD_INC_USE_COUNT;

MOD_DEC_USE_COUNT;

Page 28: Linux 设备驱动程序

复杂的设备驱动程序

驱动程序注册与注销(注册 / 注销 设备、中断)

设备文件的操作函数(*open)()(*write)()(*flush)()(*llseek)()

中断服务程序内核数据缓冲区

用户数据空间

Page 29: Linux 设备驱动程序

复杂设备驱动程序的例子(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

Page 30: Linux 设备驱动程序

中断服务程序• 没有返回参数• 简短快速

void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { //…}

Page 31: Linux 设备驱动程序

数据接收中断服务程序void usb_ep1_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_hardware_FIFO(); put_data_into_data_buffer();}

Page 32: Linux 设备驱动程序

数据发送中断服务程序void usb_ep2_int(int irq, void *dev_id, struct pt_regs *regs) { read_data_from_buffer(); send_data_hardware_FIFO ();}

Page 33: Linux 设备驱动程序

设备文件接口函数 (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);

Page 34: Linux 设备驱动程序

设备文件接口函数(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);

Page 35: Linux 设备驱动程序

设备文件接口函数 (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);

Page 36: Linux 设备驱动程序

内存申请• malloc ? X

• kmalloc• kfree

• vmalloc• vfree

Page 37: Linux 设备驱动程序

禁止设备打开多次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;}

Page 38: Linux 设备驱动程序

同一设备驱动管理几个接口

Serial Port Device Driver

UART 0 UART 0

应用程序

Page 39: Linux 设备驱动程序

同一设备驱动管理几个接口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)) { … }}