Linux设备驱动程序_第1页
Linux设备驱动程序_第2页
Linux设备驱动程序_第3页
Linux设备驱动程序_第4页
Linux设备驱动程序_第5页
已阅读5页,还剩75页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、大1- 设备驱动程序入门2- 中断处理3- 等待队列4- 定时处理5- 实例分析字符设备驱动程序6- 实例分析网络设备驱动程序7- 用户与内核的接口 驱动程序分类驱动程序分类 模块方式驱动程序模块方式驱动程序 内核方式驱动程序内核方式驱动程序 用户模块驱动程序用户模块驱动程序 字符设备驱动程序字符设备驱动程序 表现为文件,面向字节,即时收发数据 块设备驱动程序块设备驱动程序 表现为文件,面向块,通过缓存区进行缓冲 网络设备驱动程序网络设备驱动程序 表现为net_device结构链表中的一项,面向流或数据 报,通过sk_buff结构进行收发 编写步骤编写步骤 1) 写入口函数 2) 写模块函数

2、3) 编译为.O文件 4) 插入模块 5) 创建设备文件 调试方式调试方式 实例实例一、入口函数一、入口函数 Open() 打开设备 增加使用记数;分配内存空间;初始化变量、函数;申请中断、I/O空间Release() 关闭设备 减少使用记数;释放内存空间;释放中断、 I/O空间Write() 写设备 Copy_from_user()Read() 写设备 Copy_to_user()其他函数如ioctl()和中断处理函数等二、模块函数二、模块函数 Init_module() 模块初始化函数。在插入模块时执行 也可以用module_init(your_init_func) 主要执行设备的注册Cl

3、eanup_module() 模块清理函数。在移除模块时执行 也可以用module_exit(your_cleanup_func) 主要执行设备的反注册三、编译三、编译 用如下命令将mydriver.c编译为mydriver.oPpc_8xx-gcc-DLINUX-DMODULE-D_KERNEL_-Wall-Wstrict-prototypes-fno-builtin-nostdinc -O2-I/opt/hardhat/devkit/lsp/embeddedplanet-cllf-ppc_8xx/linux-2.4.17_mvl21/include/-I/opt/hardhat/devki

4、t/ppc/8xx/lib/lib/gcc-Lib/powerpc-hardhat-linux/2.95.3/include/-I/opt/hardhat/devkit/lsp/embeddedplanet-cllf-ppc_8xx/linux-2.4.17_mvl21/arch/ppc/-cMydriver.c四、插入模块四、插入模块 insmod mydriver.o该命令将驱动程序模块插入到内核中,并执行init_module()函数。该命令也可以向驱动程序中传递一些参数。rmmod mydriver该命令将驱动程序模块从内核中先移除,并执行cleanup_module()函数。其它命令

5、:modprobe、depmod、modinfo。五、创建设备文件五、创建设备文件 Mknod/dev/mydriver c major minor该命令创建一个字符设备文件mydriver,它的主设备号是major,次设备号是minor。设备号信息可以在/proc/devices文件中获得。网络设备驱动程序不需要此步骤,因为它不出现在文件系统中。模块驱动程序的调试模块驱动程序的调试 使用printk函数 在程序的开始加入 #define MY_debug 在需要打印调试信息的位置加入 #ifdef MY_DEBUG printk(“my debug info”); #endif模块驱动程序实

6、例模块驱动程序实例Modexample.c#include #include #include #include #include #include unsigned int test_major=0ssize_t read_test(struct file *file,char *buf,size_t count,loff_t *offset)int left;if(verify_area(VERIFY_WRITE,buf,count)=-EFAULT)Return EFAULT;for(left=count;left0;left-)put_user(1,buf);buf+Return co

7、unt;ssize_t write_test(struct file *file,const char *buf,size_t count,loff_t *offset)Return count;Int open_test(struct inode *inode,strut file *file)MOD_INC_USE_COUNT;Return 0;Int release_test(struct inode *inode,struct file *file)MOD_DEC_USE_COUNT;Return 0;Static struct file_operationsTest_fops= Re

8、ad: read_test, write: write_test, open: open_test, release: release_test,;Int my_init_module(void)Int result;Result=register_chrdev(test_major,”test”, &test_fops);If(resultload Ox200000 nete860.bin BINBDIti Ox200000五五. .在主机上执行在主机上执行dddddd debugger debugger/opt/hardhat/devkit/ppc/8xx/bin/ppc_8xx-

9、gdb gdb/opt/hardhat/devkip/lsp/embeddedplanet-cllf-8xx/linux2.4.17_mvl21/vmlinux六六. .在在dddddd中输入中输入: :Target remote 192.168.1.20:2001 适用范围及特点适用范围及特点 I/O映射映射 内存映射内存映射 读写设备读写设备 适用范围及特点适用范围及特点 测试新的硬件设备 测试一个新的设备是否可用 观察设备的工作情况快速创建一个硬件应用 比如控制小马达的转动 点亮某个指示灯优点:1)可以连接完整的C库,编程容易2)可以使用传统调试器,而不必调试内容缺点:1)不支持设备中断

10、2)不支持设备定时 I/O映射映射 ioperm()为用户应用程序打开一块I/O空间iopl()为用户应用程序打开整个I/O地址空间MontaVista Linux不支持 内存映射内存映射 通过设备/dev/mem来访问硬件设备 使用mmap()函数来选择要访问的内存物理基址和块大小,它返回已经映射到物理基址的虚拟地址 void *mmap (void *start, size_t length, int prot, int flags,in fd,off_t offset) fd=open(“/dev/men”,O_WRONLY);ledptr=mmap(0,sizeof(LED_AREA)

11、,PROT_WRITE,MAP_SHARED,fd,LED_ADDRESS);*ledptr=value; 读写设备读写设备 使用函数inb(), inw(), inl() 或readb(), readw(), readl()来读设备 使用函数outb(), outw(), outl()或writeb(), writew(),writel()来写设备 它们的头文件是,基本概述基本概述探测中断探测中断安装中断安装中断取消中断取消中断 分类分类 硬件中断由硬件设备产生的中断执行相应的中断处理程序可以产生软中断(tasklet)来实现耗时的中断处理任务,也就是下半部中断 软中断(softIRQ)内核

12、中共有32个softIRQ, 其中一个就是TASKLET_SOFTIRQ在内核执行do_softirq()函数时,轮询这32个softirq,如果相应的softirq可以执行,就执行它指导的函数TASKLET_SOFTIRQ对应的函数是tasklet_action(),它会依次执行挂在TASKLET_SOFTIRQ上的tasklet Read执行流程执行流程 中断处理程序举例中断处理程序举例 struct tasklet_struct my_tasklet; elsewhere, in some initialization section tasklet_init(&my_taskl

13、et, my_bh, NULL); void th_interrupt(int irq, void *dev_id, struct pt_regs *regs) dl_gettimeofday(tv_head); tv_head+; if(tv_head=(tv_data+NR_TIMEVAL) tv_head=tv_data; tasklet_schedule(&my_tasklet); /*queue the tasklet*/ short_bh_count+; /*record that an interrupt arrived*? 1. probe_irq_on(void) 返

14、回一个unsigned long 位掩码2. 使设备发中断使设备发中断3. probe_irq_oof(unsigned long) 第一步返回的位掩码传给此函数 返回值即为探测到的中断号(如果为0或为负则没有可用中断) 探测中断例子探测中断例子 unsigned long mask=probe_irq_on(); outb_p(0 x10,short_base+2); /*enable reporting*/ outb_p(0 x00,short_base); /*clear the bit*/ outb_p(0 xFF,short_base); /*set the bit:interrup

15、t*/ outb_p(0 x00,short_base+2); /*disable reporting*/ irq=probe_irq_off(mask); 安装安装SIU中断中断 SIU中断向量表16个(/include/asm-asm-ppc/irq.h) 外部中断 根据硬件连线确定IRQ号码,比如是IRQ3 调用函数request_8xxirq(SIU_IRQ3, my_handler, flag, devname, devpointer) 内部中断 选择一个未用的LEVEL号码,比如是LEVEL3 调用函数request_8xxirq(SIU_LEVEL3, my_handler, f

16、lag, devname, NULL) 安装安装SIU中断例子中断例子 #define CPM_INTERRUPTSIU_LEVEL2 (在文件irq.h中定义) cpm_interrupt_init()函数中 *(arch/ppc/8xx_io/commproc.c) /* Set our interrup handler with the core CPU. */ If (request_8xxirq(CPM_interrupt, cpm_interrupt, 0,”cpm”, NULL)!=0)Panic( “could not allocate CPM IRQ!”); 安装安装CPM中

17、断中断 CPM中断向量表32个(/arch/ppc/8xx_io/commproc.h) 确定相应的中断向量,比如是SCC1 调用函数 cpm_install_handler(CPMVEC_SCC1, scc1_handler, devpointer) 例子例子 cpm_interrupt_init() 函数中 *(/arch/ppc/8xx_io/commproc.c) /* Install our own error handler. */ cpm_install_handler(CPMVEC_ERROR, cpm_error_interrupt, NULL); 取消中断取消中断 取消SI

18、U中断 (do_free_irq(int irq,void* dev_id) 例:do_free_irq(SIU_IRQ3, devpointer) 取消CPM中断 cpm_free_handler(int vec) 例:cpm_free_handler(CPMVEC_ERROR) 中断处理程序任务中断处理程序任务 响应中断 查看中断状态寄存器,判断中断源 根据中断源进行相应处理概述概述 使用时机 请求暂不可用的资源 版本相关(/linux/wait.h) 2.4版以前使用结构体 wait_queue 2.4版使用结构体 wait_queue_head_t主要函数主要函数 (/kernel/s

19、ched.c) sleep_on(wait_queue_head_t *q) interruptible_sleep_on(wait_queue_head_t *q) (/linux/sched.h) wake_up(wait_queue_head_t *q) wake_up_interruptible(wait_queue_head_t *q) (/inclued/linux/wait.h) DECLARE_wait_queue_head(name) DECLARE_waitqueue (name,tsk) add_wait_queue(wait_queue_head_t *head, wa

20、it_queue_t *new) remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)使用实例使用实例 情景:内核中有一块内存,在写时发现满了 首先在程序开始定义全局变量 strut wait_queue_head_t wqh; /struct wait_queue_t wq; 或者使用宏 DECLARE_WAIT_QUEUE_HEAD(wqh); /DECLARE_WAITQUEUE(wq,current); 在write函数中: while(is_full) interruptible_sleep_on(&wqh

21、); /add_wait_queue(&wqh,Qwq); /current-state=TASK_interruptible; /schedule();使用实例使用实例(续续) write_to_buffer(); is_empty=0; 在read函数中: if(!is_empty) read_from_buffer(); if_full=0; wake_up_interruptible(&wqh); 概述概述 数据结构 include/linux/timer.h struct timer_list struct timer_list *next; struct timer

22、_list *prev; unsigned long expires; unsigned long data; void(*function)(unsigned loing); 主要函数主要函数 初始化timer void init_timer(struct timer_list *timer); 添加timer void add_timer(struct timer_list *timer); 删除timer void del_timer(struct timer_list *timer);使用实例使用实例 struct timer_list my_timer; init_timer(&am

23、p;my_timer); my_timer.function = my_timerfunc; my_timer.data = my_argu; my_timer.expires = jiffies+HZ; /delay 1 sec add_timer(my_timer); 等待队列和定时器综合实例等待队列和定时器综合实例 init_timer_out; struct timer_list timer; void timeout_func(unsigned long who) timed_out = 1; debug(“Timing outn”) wake_up_interruptible(wa

24、it_queue_head_t*)who); int sleep_or_timerout(wait_queue_head_t*wait,int timeout) timed_out=0; timer.data=(unsigned long) wait; timer.function = timeout_func; 等待队列和定时器综合实例等待队列和定时器综合实例 timer.expires= jiffies + timeout; add_timer(&timer); debug(“going to sleepn”); interruptible_sleep_on(wait); del_

25、timer(&timer); if(timed_out) timed_out = 0; return 1; else return 0; 源程序源程序:mydriver.c mydriver.h 该程序是一个典型的模块方式字符设备驱动程序该程序是一个典型的模块方式字符设备驱动程序 该程序包含了中断该程序包含了中断(上下半部上下半部),时钟,等待队列,时钟,等待队列 的处理例程的处理例程 其特点在于用时钟中断来模拟硬件中断其特点在于用时钟中断来模拟硬件中断 函数列表函数列表 my_init_module(); mydriver_init(); my_cleanup_module(); m

26、ydriver_stop(); mydriver_open(); mydriver_release(); mydriver_timer_expiration(); mydriver_interrupt(); mydriver_task(); mydriver_read(); mydriver_write() 程序分析程序分析 my_init_module(void 调用mydriver_init() my_cleanup_module(void) 调用mydriver_stop()注册设备注册设备 if(rc=unregister_chrdev(mydriver_major, mydriver

27、_name, &my_fops):初始化等待队列初始化等待队列Int_waitqueue_head(&mydriver_zz);取消设备注册取消设备注册If(rc=unregister_chrdev(mydriver_major, mydriver_name) mydriver_open(pinode,pfile) 增加打开记数 mydriver_open_count+,; 为文件的缓存区分配内存空间 mydriver_buffer= (char *) kmalloc(MYDRIVER_BUFFERSIZE,GEP_KERNEL); 初始化所分配的内存空间 memset(myd

28、river_buffer, 0, MYDRIVER_BUFFERSIZE); 申请中断号 #ifndef MYDRIVER_SIMULATE_INTERRUPT rc=request_irq(mydriver_irq,mydriver_interrupt, SA_SHIRQ, mydriver_name, NULL); 初始化定时器 init_timer(&mydriver_timer); 增加使用记数 MOD_INC_USE_COUNT; mydriver_release(pinode,pfile) 增加释放记数 mydriver_release_count+,; 删除定时器 if

29、(rc=del_timer(&mydriver_timer); 释放中断号 #ifndef MYDRIVER_SIMULATE_INTERRUPT free_irq(mydriver_irq,mydriver_name); 释放用户缓存区内存空间 kfree(mydriver_buffer); 增加使用记数 MOD_DEC_USE_COUNT; mydriver_timer_expiration(mydriver_data) 说明 定义MYDRIVER_SIMULATE_INTERRUPT时才编译,其作用是用定时器来模拟硬件中断 增加定时器超时记数 mydriver_timer_cou

30、nt+; 执行中断处理函数 mydriver_interupt( MYDRIVER_IRQ,(void*) mydriver_data,(NULL); mydriver_interrupt(irq,dev_id,fp) 增加中断记数 mydriver_interrup_count+; 设置I/O成功标志 mydriver_final_status=0; 初始化tasklet(下半部) tasklet_init(&mydriver_tasklet, mydriver_task, (unsigned long) dev_id); 调度tasklet(下半部) tasklet_schedul

31、e(&mydriver_tasklet); mydriver_task(data) 唤醒等待队列 wake_up_interruptible(&mydriver_zz); mydriver_read(pfile,user_buf,count,poffset) 增加读记数 mydriver_read_count+; 给定时器赋值,并添加定时器 mydriver_timer.function=mydriver_timer_expiration; mydriver_timer.data=0; mydriver_timer.expires=jiffies+(MYDRIVER_IO_DU

32、RATION *HZ); add_timer(&mydriver_timer); 睡眠在等待队列mydriver_zz上 interruptible_sleep_on(&mydriver_zz); mydriver_read(pfile,user_buf,count,poffset)续续 处理异常情况 if (signal_pending(current) del_timer(&mydriver_timer); printk(“ABNORMALLY terminated:”); 正常读出数据,将数据拷贝给用户,返回读出的字节数 if (mydriver_final_st

33、atus = 0) if (copy_to_user(user_buf, mydriver_buffer, count) printk(“NORMALLY TERMINATTED:”); return mydriver_final_count; mydriver_write(pfile,user_buf,count,poffset) 增加写记数 mydriver_write_count+; 拷贝数据到文件缓存区 if (copy_from_user(mydriver_buffer, user_buf, count); 给定时器赋值,并添加定时器 与read中相同 睡眠在等待队列mydriver

34、_zz上 interruptible_sleep_on(&mydriver_zz); mydriver_write(pfile,user_buf,count,poffset)续续 处理异常情况 if (signal_pending(current) del_timer(&mydriver_timer); printk(“ABNORMALLY terminated:”); 正常写入数据,返回写入的字节数 if (mydriver_final_status = 0) printk(“NORMALLY TERMINATTED:”); return mydriver_final_cou

35、nt; 思考思考 如何将此模块方式驱动程序修改为内核方式驱动程序? 源程序源程序enet.c commproc.h Motorola MPC8xx以太网驱动以太网驱动 在在SCCx上实现以太网功能上实现以太网功能 函数列表函数列表 scc_enet_init(); scc_enet_open(); scc_enet_start_xmit(); scc_enet_rx(); scc_enet_close(); scc_enet_interrupt(); scc_enet_get_stats(); scc_enet_timeout(): scc_enet_t 调用路径 (/init/main.c)

36、start_kernel()-init()-(do_basic_setup()-do_initcalls()- (/fs/partitions/check.c)_initcall(partition_setup)-partion_setup()- (/drivers/block/genhd.c)device_init()-(/net/core/dev.c) net_dev_init()-network_probe(),使用pci_probes 初始化数组,其中包含scc_enet_init() 初始化net_device结构的各个域; dev=init_etherdev(0,0) 初始化MPC

37、860的parameter ram; ep=(scc_enet_t *)(&cp-cp_dparamPROFF_ENET); 初始化MPC860的寄存器 immap=(immap_t*)(_get_IMMR()&0 xFFFF0000); sccp=(volatile scc_t*) (&cp-cp_sccSCC_ENET_; 初始化buffer; 初始化board; 设置并安装中断; sccp-scc_sccm=(SCCE_ENET_TXE | SCCE_ENET_RXF | SCCE_ENET_TXB); cpm_inistall_handler(CPMVEC_EN

38、ET, scc_enet_interrupt, dev); 将函数指针赋给net_device结构中相应的入口 dev-open = scc_enet_open; dev-hard_star_xmit = scc_enet_start_xmit; dev-tx_timeout= scc_enet_timeout; dev-watchdog_timeo = TX_TIMEOUT; dev-stop = scc_enet_close; dev-get_stats = scc_enet_get_stats; dev-set_multicast_list = set_multicast_list; s

39、cc_enet_open 更改dev-state域的指示位 netif_start_queue(dev); 相当于下面的语句 clear_bit(_LINK_STATE_XOFF,&dev-state); scc_enet_close(dev) 更改dev-state域的指示位 netif_stop_queue(dev); 相当于下面的语句 set_bit(_LINK_STATE_XOFF,&dev-state); 当用户通过enet发送数据时,执行该函数 将skb中相应域的值赋给bdp(即cep-cur_tx)的相应域 包长度:bdp-cbd_datlen = skb-len

40、; 包数据指针:bdp-cbd_vufaddr = _pa(skb-data); 保存skb指针 cep-tx_skbuff cep-skb_cur = skb 增加统计域中的发送字节数 (cep-stats.tx_bytes+=skb-len;); 将cep-skb_cur域加一, 如果超过TX_RING_SIZE, 则置为0 cep-skb_cur = (cep-skb_cur+1) & TX_RING_MOD_MASK; 清理相应数据缓冲区中的内容 flush_dcache_range(unsigned long) (skb-data), (unsigned long) (skb

41、-data+skb-len) 锁中断 spin_lock_irq(&cep-lock); 开始发送 bdp-cbd_sc |=(BD_ENET_TX_READY |BD_ENET_TX_TC); 设定发送开始时间 dep-trans_start=jiffies; 调理BD指针:将BD的指针向下一个可用BD,并判断发送缓冲区是否满 设置cep-cur_tx指针 cep-cur_tx= (cbd_t*)bdp; 解锁中断 spin_unlock_irq(&cep-lock); 当产生RXF中断,即收到帧后,执行 获取cep指针 cep=(struct scc_enet_privat

42、e *)dev-priv; 获取bdp指针 bdp= cep-cur_rx; 检查收到的是否是完整帧 更新统计信息 为skb申请内存 skb= dev_alloc_skb(pkt_len-4); 将skb与设备相关连 skb-dev = dev; 在skb中分配pkt_len-4长度的空间 skb_put(skb,pkt_len-4); (skbuff.h中) 将数据拷贝到skb中 eht_copy_and_sum(skb,(unsigned char *)_va(bdp-cbd_bufaddr),pkt_len-4;0); (etherdevice.h中) 判断并获取协议的ID skb-pr

43、otocol = eth_type_trans(skb,dev); (该函数在eth.c中) 将skb传送到上层 netif_rx(skb); 设置BD 进入for循环继续读取收到的包,如果bd为空,则退出循环 设置cep-cur_rx指针 cep-cur_rx = (cbd_t *) bodp; 当CPMVEC_ENET产生中断时,执行 获取cep指针 cep=(struct scc_enet_private *)dev-priv; 获取中断事件 int_events=cep-sccp-scc_scce; 处理接收中断: if(int_events & SCCE_ENET_RXF) scc_enet_rx(dev_id); 处理写送中断; 加读写锁spin_lock(&cep-lock); while循环 如果发送缓冲区满,则退出循环 if(bdp=cep-cur_tx) & (cep-tx_full=0) break; 更新统计信息; 出错重发 if (bdp-cbd_sc & (BD_ENET_TX

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论