当前位置:首页 » 操作系统 » linux设备驱动模型

linux设备驱动模型

发布时间: 2023-08-24 17:28:27

linux驱动程序开发实例的目录

前言
第1章 Linux设备驱动程序模型 1
1.1 设备驱动程序基础 1
1.1.1 驱动程序的概念 1
1.1.2 驱动程序的加载方式 2
1.1.3 编写可加载模块 3
1.1.4 带参数的可加载模块 5
1.1.5 设备驱动程序的分类 6
1.2 字符设备驱动程序原理 7
1.2.1 file_operations结构 7
1.2.2 使用register_chrdev注册字符
设备 9
1.2.3 使用cdev_add注册字符设备 11
1.2.4 字符设备的读写 13
1.2.5 ioctl接口 14
1.2.6 seek接口 16
1.2.7 poll接口 18
1.2.8 异步通知 22
1.3 proc文件系统 24
1.3.1 proc文件系统概述 24
1.3.2 seq_file机制 25
1.3.3 使用proc文件系统 27
1.4 块设备驱动程序 32
1.4.1 Linux块设备驱动程序原理 32
1.4.2 简单的块设备驱动程序实例 35
1.5 网络设备驱动程序 39
1.5.1 网络设备的特殊性 39
1.5.2 sk_buff结构 40
1.5.3 Linux网络设备驱动程序架构 42
1.5.4 虚拟网络设备驱动程序实例 46
1.6 Linux 2.6设备管理机制 50
1.6.1 kobject和kset 50
1.6.2 sysfs文件系统 51
1.6.3 设备模型层次 52
1.6.4 platform的概念 54
第2章 Linux内核同步机制 58
2.1 锁机制 58
2.1.1 自旋锁 58
2.1.2 读写锁 60
2.1.3 RCU 61
2.2 互斥 64
2.2.1 原子操作 64
2.2.2 信号量 65
2.2.3 读写信号量 67
2.3 等待队列 68
2.3.1 等待队列原理 68
2.3.2 阻塞式I/O实例 68
2.3.3 完成事件 70
2.4 关闭中断 71
第3章 内存管理与链表 72
3.1 物理地址和虚拟地址 72
3.2 内存分配与释放 72
3.3 IO端口到虚拟地址的映射 73
3.3.1 静态映射 73
3.3.2 动态映射 75
3.4 内核空间到用户空间的映射 76
3.4.1 内核空间到用户空间的地址
映射原理 76
3.4.2 mmap地址映射实例 78
3.5 内核链表 80
3.5.1 Linux内核中的链表 80
3.5.2 内核链表实例 81
第4章 延迟处理 83
4.1 内核线程 83
4.2 软中断机制 85
4.2.1 软中断原理 85
4.2.2 tasklet 87
4.3 工作队列 89
4.3.1 工作队列原理 89
4.3.2 工作队列实例 91
4.4 内核时间 92
4.4.1 Linux中的时间概念 92
4.4.2 Linux中的延迟 93
4.4.3 内核定时器 93
第5章 简单设备驱动程序 96
5.1 寄存器访问 96
5.1.1 S3C6410地址映射 96
5.1.2 S3C6410看门狗驱动程序实例 98
5.1.3 S3C6410蜂鸣器驱动程序实例 102
5.2 电平控制 107
5.2.1 S3C6410 LED驱动程序实例 107
5.2.2 扫描型S3C6410按键驱动
程序实例 109
5.3 时序产生 112
5.3.1 时序图原理 112
5.3.2 AT24C02芯片原理 112
5.3.3 AT24C02驱动程序开发实例 115
5.4 硬中断处理 123
5.4.1 硬中断处理原理 123
5.4.2 中断型S3C6410按键驱动
程序实例 127
5.5 Linux I/O端口控制 132
5.5.1 Linux I/O端口读写 132
5.5.2 在应用层访问Linux I/O
端口 133
5.5.3 /dev/port设备 134
第6章 深入Linux内核 135
6.1 嵌入式Linux系统构成 135
6.2 Linux内核导读 136
6.2.1 Linux内核组成 136
6.2.2 Linux的代码结构 137
6.2.3 内核Makefile 138
6.2.4 S3C6410硬件初始化 139
6.3 Linux文件系统 141
6.3.1 虚拟文件系统 141
6.3.2 根文件系统 143
6.3.3 文件系统加载 143
6.3.4 ext3文件系统 145
6.4 Flash文件系统 145
6.4.1 MTD设备 145
6.4.2 MTD字符设备 148
6.4.3 MTD块设备 150
6.4.4 cramfs文件系统 153
6.4.5 JFFS2文件系统 153
6.4.6 YAFFS文件系统 155
6.4.7 文件系统总结 156
6.5 Linux内核移植 156
6.5.1 体系配置 156
6.5.2 添加yaffs2 157
6.5.3 Nand flash驱动程序移植 157
6.5.4 配置启动参数 159
6.5.5 移植RTC驱动程序 160
6.6 根文件系统制作 162
6.6.1 Busybox 162
6.6.2 shell基础 165
6.6.3 根文件系统构建实例 166
6.7 udev模型 167
6.7.1 udev模型原理 167
6.7.2 mdev的使用 167
第7章 I2C总线驱动程序 169
7.1 Linux的I2C驱动程序架构 169
7.1.1 I2C适配器 169
7.1.2 I2C算法 170
7.1.3 I2C驱动程序结构 170
7.1.4 I2C从设备 171
7.1.5 i2c-dev设备层 171
7.2 Linux I2C驱动程序开发 174
7.2.1 S3C2410X的I2C控制器 174
7.2.2 S3C2410X的I2C驱动程序
分析 175
7.3 S3C2410的I2C访问实例 182
7.4 I2C客户端驱动程序 185
第8章 TTY与串口驱动程序 190
8.1 TTY概念 190
8.2 Linux TTY驱动程序体系 190
8.2.1 TTY驱动程序调用关系 190
8.2.2 TTY驱动程序原理 191
8.3 线路规程 194
8.4 串口驱动程序与TTY 196
8.4.1 串口设备驱动程序原理 196
8.4.2 S3C6410的串口驱动程序
实例 199
8.5 TTY应用层 202
第9章 网络设备驱动程序 205
9.1 DM9000网卡驱动程序
开发 205
9.1.1 DM9000原理 205
9.1.2 DM9000X驱动程序分析 207
9.1.3 DM9000网口驱动程序移植 215
9.2 NFS根文件系统搭建 219
9.2.1 主机配置 219
9.2.2 NFS根文件系统搭建实例 220
9.3 netlink Socket 224
9.3.1 netlink机制 224
9.3.2 netlink应用层编程 228
9.3.3 netlink驱动程序实例 229
第10章 framebuffer驱动程序 232
10.1 Linux framebuffer驱动
程序原理 232
10.1.1 framebuffer核心数据结构 232
10.1.2 framebuffer操作接口 234
10.1.3 framebuffer驱动程序的文件
接口 236
10.1.4 framebuffer驱动程序框架 236
10.2 S3C6410 显示控制器 238
10.3 S3C6410 LCD驱动程序实例 243
10.4 framebuffer应用层 250
10.5 Qt4界面系统移植 251
第11章 输入子系统驱动程序 253
11.1 Linux输入子系统概述 253
11.1.1 input_dev结构 253
11.1.2 输入事件 255
11.2 input_handler 256
11.2.1 Input Handler层 256
11.2.2 常用的Input Handler 259
11.3 输入设备应用层 261
11.4 键盘输入设备驱动程序
实例 262
11.5 event接口 267
11.6 触摸屏驱动程序实例 270
11.6.1 S3C6410触摸屏控制器 270
11.6.2 S3C6410触摸屏驱动程序
设计 273
11.7 触摸屏校准 282
11.7.1 触摸屏校准原理 282
11.7.2 利用TSLIB库校准触摸屏 282
第12章 USB驱动程序 284
12.1 USB体系概述 284
12.1.1 USB系统组成 284
12.1.2 USB主机 284
12.1.3 USB设备逻辑层次 285
12.2 Linux USB驱动程序体系 287
12.2.1 USB总体结构 287
12.2.2 USB设备驱动程序 287
12.2.3 主机控制器驱动程序 288
12.2.4 USB请求块urb 289
12.2.5 USB请求块的填充 291
12.3 S3C6410 USB主机控制器
驱动程序 292
12.3.1 USB主机控制器驱动程序
分析 292
12.3.2 S3C6410 USB驱动程序
加载 294
12.4 USB键盘设备驱动程序
分析 296
12.5 USB Gadget驱动程序 301
12.5.1 Linux USB Gadget驱动程序 301
12.5.2 Linux USB Gadget驱动程序
实例 302
第13章 音频设备驱动程序 303
13.1 ALSA音频体系 303
13.2 ALSA驱动层API 304
13.2.1 声卡和设备管理 304
13.2.2 PCM API 304
13.2.3 控制与混音API 305
13.2.4 AC97 API 306
13.2.5 SOC层驱动 307
13.3 ALSA驱动程序实例 308
13.3.1 S3C6410的AC97控制
单元 308
13.3.2 S3C6410声卡电路原理 309
13.3.3 S3C6410的数字音频接口 310
13.3.4 wm9713的数字音频接口 313
13.4 ALSA音频编程接口 316
13.4.1 ALSA PCM接口实例 316
13.4.2 ALSA MIDI接口实例 320
13.4.3 ALSA mixer接口实例 321
13.4.4 ALSA timer接口实例 322
第14章 video4linux2视频
驱动程序 327
14.1 video4linux2驱动程序
架构 327
14.1.1 video4linux2驱动程序的
注册 327
14.1.2 v4l2_fops接口 331
14.1.3 常用的结构 332
14.1.4 video4linux2的ioctl函数 333
14.2 S3C6410摄像头驱动程序
分析 333
14.2.1 电路原理 333
14.2.2 驱动程序分析 334
14.3 video4linux2应用层实例 339
第15章 SD卡驱动程序 346
15.1 Linux SD卡驱动程序体系 346
15.1.1 SD卡电路原理 346
15.1.2 MMC卡驱动程序架构 347
15.1.3 MMC卡驱动程序相关
结构 347
15.1.4 MMC卡块设备驱动程序 350
15.1.5 SD卡主机控制器接口驱动
程序 356
15.2 S3C6410 SD卡控制器驱动
程序分析 360
15.2.1 电路原理 360
15.2.2 S3C6410 SDHCI驱动
程序原理 360
15.2.3 SD卡的加载实例 364
参考文献 366

② 如何编译一个linux下的驱动模块

linux下编译运行驱动
嵌入式linux下设备驱动的运行和linux x86 pc下运行设备驱动是类似的,由于手头没有嵌入式linux设备,先在vmware上的linux上学习驱动开发。
按照如下方法就可以成功编译出hello world模块驱动。
1、首先确定本机linux版本
怎么查看Linux的内核kernel版本?
'uname'是Linux/unix系统中用来查看系统信息的命令,适用于所有Linux发行版。配合使用'uname'参数可以查看当前服务器内核运行的各个状态。
#uname -a
Linux whh 3.5.0-19-generic #30-Ubuntu SMPTue Nov 13 17:49:53 UTC 2012 i686 i686 i686 GNU/Linux

只打印内核版本,以及主要和次要版本:
#uname -r
3.5.0-19-generic

要打印系统的体系架构类型,即的机器是32位还是64位,使用:
#uname -p
i686

/proc/version 文件也包含系统内核信息:
# cat /proc/version
Linux version 3.5.0-19-generic(buildd@aatxe) (gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) ) #30-UbuntuSMP Tue Nov 13 17:49:53 UTC 2012

发现自己的机器linux版本是:3.5.0-19-generic
2、下载机器内核对应linux源码
到下面网站可以下载各个版本linux源码https://www.kernel.org/
如我的机器3.5.0版本源码下载地址为:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.5.tar.bz2
下载完后,找一个路径解压,如我解压到/linux-3.5/
然后很重要的一步是:执行命令uname -r,可以看到Ubuntu的版本信息是3.5.0-19-generic
。进入linux源码目录,编辑Makefile,将EXTRAVERSION = 修改为EXTRAVERSION= -19-generic。
这些都是要配置源码的版本号与系统版本号,如果源码版本号和系统版本号不一致,在加载模块的时候会出现如下错误:insmod: error inserting 'hello.ko': -1 Invalid mole format。
原因很明确:编译时用的hello.ko的kenerl 不是我的pc的kenerl版本。

执行命令cp /boot/config-3.5.0-19-generic ./config,覆盖原有配置文件。
进入linux源码目录,执行make menuconfig配置内核,执行make编译内核。

3、写一个最简单的linux驱动代码hello.c

/*======================================================================
Asimple kernel mole: "hello world"
======================================================================*/
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("zeroboundaryBSD/GPL");
static int hello_init(void)
{
printk(KERN_INFO"Hello World enter\n");
return0;
}

static void hello_exit(void)
{
printk(KERN_INFO"Hello World exit\n ");
}

mole_init(hello_init);
mole_exit(hello_exit);

MODULE_AUTHOR("zeroboundary");
MODULE_DESCRIPTION("A simple HelloWorld Mole");
MODULE_ALIAS("a simplestmole");

4、写一个Makefile对源码进行编译
KERN_DIR = /linux-3.5
all:
make-C $(KERN_DIR) M=`pwd` moles
clean:
make-C $(KERN_DIR) M=`pwd` clean

obj-m += hello.o

5、模块加载卸载测试
insmod hello.ko
rmmod hello.ko

然后dmesg|tail就可以看见结果了

最后,再次编译驱动程序hello.c得到hello.ko。执行insmod ./hello.ko,即可正确insert模块。

使用insmod hello.ko 将该Mole加入内核中。在这里需要注意的是要用 su 命令切换到root用户,否则会显示如下的错误:insmod: error inserting 'hello.ko': -1 Operation not permitted

内核模块版本信息的命令为modinfo hello.ko
通过lsmod命令可以查看驱动是否成功加载到内核中
通过insmod命令加载刚编译成功的time.ko模块后,似乎系统没有反应,也没看到打印信息。而事实上,内核模块的打印信息一般不会打印在终端上。驱动的打印都在内核日志中,我们可以使用dmesg命令查看内核日志信息。dmesg|tail

可能还会遇到这种问题insmod: error inserting 'hello.ko': -1 Invalid mole format
用dmesg|tail查看内核日志详细错误
disagrees about version of symbolmole_layout,详细看这里。
http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmoles/index.html
在X86上我的办法是:
make -C/usr/src/linux-headers-3.5.0-19-generic SUBDIRS=$PWD moles

③ Linux网络设备驱动的结构

Linux网络设备驱动程序的体系结构从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示。
1)网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是ARP,还是IP,都通过dev_queue_xmit() 函数发送数据,并通过netif rx ()函数接收数据。这一层的存在使得上层协议独立于具体的设备。
2)网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
3)设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_ xmit ()函数启动发送操作,并通过网络设备上的中断触发接收操作。
4)网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。

④ linux驱动程序结构框架及工作原理分别是什么

一、Linux device driver 的概念x0dx0ax0dx0a系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:x0dx0ax0dx0a1、对设备初始化和释放;x0dx0ax0dx0a2、把数据从内核传送到硬件和从硬件读取数据;x0dx0ax0dx0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;x0dx0ax0dx0a4、检测和处理设备出现的错误。x0dx0ax0dx0a在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。x0dx0ax0dx0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。x0dx0ax0dx0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。x0dx0ax0dx0a二、实例剖析x0dx0ax0dx0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。x0dx0ax0dx0a由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:x0dx0ax0dx0aSTruct file_operatiONs {x0dx0ax0dx0aint (*seek) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*read) (struct inode * ,struct file *, char ,int);x0dx0ax0dx0aint (*write) (struct inode * ,struct file *, off_t ,int);x0dx0ax0dx0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int);x0dx0ax0dx0aint (*select) (struct inode * ,struct file *, int ,select_table *);x0dx0ax0dx0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);x0dx0ax0dx0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);x0dx0ax0dx0aint (*open) (struct inode * ,struct file *);x0dx0ax0dx0aint (*release) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fsync) (struct inode * ,struct file *);x0dx0ax0dx0aint (*fasync) (struct inode * ,struct file *,int);x0dx0ax0dx0aint (*check_media_change) (struct inode * ,struct file *);x0dx0ax0dx0aint (*revalidate) (dev_t dev);x0dx0ax0dx0a}x0dx0ax0dx0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。x0dx0ax0dx0a下面就开始写子程序。x0dx0ax0dx0a#include 基本的类型定义x0dx0ax0dx0a#include 文件系统使用相关的头文件x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0aunsigned int test_major = 0;x0dx0ax0dx0astatic int read_test(struct inode *inode,struct file *file,char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0aint left; 用户空间和内核空间x0dx0ax0dx0aif (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )x0dx0ax0dx0areturn -EFAULT;x0dx0ax0dx0afor(left = count ; left > 0 ; left--)x0dx0ax0dx0a{x0dx0ax0dx0a__put_user(1,buf,1);x0dx0ax0dx0abuf++;x0dx0ax0dx0a}x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0a这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。x0dx0ax0dx0astatic int write_test(struct inode *inode,struct file *file,const char *buf,int count)x0dx0ax0dx0a{x0dx0ax0dx0areturn count;x0dx0ax0dx0a}x0dx0ax0dx0astatic int open_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0astatic void release_test(struct inode *inode,struct file *file )x0dx0ax0dx0a{x0dx0ax0dx0aMOD_DEC_USE_COUNT;x0dx0ax0dx0a}x0dx0ax0dx0a这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。x0dx0ax0dx0astruct file_operations test_fops = {?x0dx0ax0dx0aread_test,x0dx0ax0dx0awrite_test,x0dx0ax0dx0aopen_test,x0dx0ax0dx0arelease_test,x0dx0ax0dx0a};x0dx0ax0dx0a设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(moles),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。x0dx0ax0dx0aint init_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aint result;x0dx0ax0dx0aresult = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口x0dx0ax0dx0aif (result < 0) {x0dx0ax0dx0aprintk(KERN_INFO "test: can't get major number\n");x0dx0ax0dx0areturn result;x0dx0ax0dx0a}x0dx0ax0dx0aif (test_major == 0) test_major = result; /* dynamic */x0dx0ax0dx0areturn 0;x0dx0ax0dx0a}x0dx0ax0dx0a在用insmod命令将编译好的模块调入内存时,init_mole 函数被调用。在这里,init_mole只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。x0dx0ax0dx0a如果登记成功,返回设备的主设备号,不成功,返回一个负值。x0dx0ax0dx0avoid cleanup_mole(void)x0dx0ax0dx0a{x0dx0ax0dx0aunregister_chrdev(test_major,"test");x0dx0ax0dx0a}x0dx0ax0dx0a在用rmmod卸载模块时,cleanup_mole函数被调用,它释放字符设备test在系统字符设备表中占有的表项。x0dx0ax0dx0a一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。x0dx0ax0dx0a下面编译 :x0dx0ax0dx0a$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c _c表示输出制定名,自动生成.o文件x0dx0ax0dx0a得到文件test.o就是一个设备驱动程序。x0dx0ax0dx0a如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后x0dx0ax0dx0ald ?-r ?file1.o ?file2.o ?-o ?molename。x0dx0ax0dx0a驱动程序已经编译好了,现在把它安装到系统中去。x0dx0ax0dx0a$ insmod ?_f ?test.ox0dx0ax0dx0a如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :x0dx0ax0dx0a$ rmmod testx0dx0ax0dx0a下一步要创建设备文件。x0dx0ax0dx0amknod /dev/test c major minorx0dx0ax0dx0ac 是指字符设备,major是主设备号,就是在/proc/devices里看到的。x0dx0ax0dx0a用shell命令x0dx0ax0dx0a$ cat /proc/devicesx0dx0ax0dx0a就可以获得主设备号,可以把上面的命令行加入你的shell script中去。x0dx0ax0dx0aminor是从设备号,设置成0就可以了。x0dx0ax0dx0a我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0a#include x0dx0ax0dx0amain()x0dx0ax0dx0a{x0dx0ax0dx0aint testdev;x0dx0ax0dx0aint i;x0dx0ax0dx0achar buf[10];x0dx0ax0dx0atestdev = open("/dev/test",O_RDWR);x0dx0ax0dx0aif ( testdev == -1 )x0dx0ax0dx0a{x0dx0ax0dx0aprintf("Cann't open file \n");x0dx0ax0dx0aexit(0);x0dx0ax0dx0a}x0dx0ax0dx0aread(testdev,buf,10);x0dx0ax0dx0afor (i = 0; i < 10;i++)x0dx0ax0dx0aprintf("%d\n",buf[i]);x0dx0ax0dx0aclose(testdev);x0dx0ax0dx0a}x0dx0ax0dx0a编译运行,看看是不是打印出全1 x0dx0ax0dx0a以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法。

⑤ 如何使用linux的Documentation来写驱动

Linux I2C驱动是嵌入式Linux驱动开发人仔裂员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。
Linux I2C驱动涉及的知识点还是挺多的,主要分为Linux I2C的总线驱动(I2C BUS Driver)和设备驱动(I2C Clients Driver),本文主要关注如何快速地完成一个具体的I2C设备驱动(I2C Clients Driver)。关于Linux I2C驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。
本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。
1. i2c_driver结构体对象
每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C设备探测和注销的一些基本方法和信息,示例如下:
static struct i2c_driver tvp5158_i2c_driver = { .driver = { .name = "tvp5158_i2c_driver", }, .attach_adapter = &tvp5158_attach_adapter, .detach_client = &tvp5158_detach_client, .command = NULL, };

其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detach_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。
2. i2c_client 结构体对象
上面定义的i2c_driver对象,抽象为一个i2c的驱动模型,提供对i2C设备的探测和注销方法,而i2c_client结构体则是代表着一个具体的i2c设备,该结构体有一个data指针,可以指向任何私有的设备数据,在复杂点的驱动中可能会用到。示例如下:
struct tvp5158_obj{ struct i2c_client client; int users; // how many users using the driver }; struct tvp5158_obj* g_tvp5158_obj;

其中,users为示例,用户可以自郑戚裤己在tvp5158_obj这个结构体里面添加感兴趣的字段,但是i2c_client字段不可少。具体用法后面再详细讲。
3. 设备注册及探测功能
这一步很关键,按照标准的要求来写,则Linux系统会自动调用相关的代码去探测你的I2C设备,并且添加到系统的I2C设备列表中以供后面访问。
我们知道,每一个I2C设备芯片,都通过硬件连接设定好了该设备的I2C设备地址。因此,I2C设备的探测一般是靠设备地址来完成的。那么,首先要在驱动代码中声明你要探测的I2C设备地址列表,以及一个宏。示例如下:
static unsigned short normal_i2c[] = { 0xbc >> 1, 0xbe >> 1, I2C_CLIENT_END }; I2C_CLIENT_INSMOD;

normal_i2c 数组包含了你需要探测的I2C设备地址列表,并且必须以I2C_CLIENT_END作为结尾,注意,上述代码中的0xbc和0xbe是我在硬件上为我的tvp5158分配的地址,硬件上我支持通过跳线将喊简该地址设置为 0xbc 或者 0xbe,所以把这两个地址均写入到探测列表中,让系统进行探测。如果你的I2C设备的地址是固定的,那么,这里可以只写你自己的I2C设备地址,注意必须向右移位1。
宏 I2C_CLIENT_INSMOD 的作用网上有许多文章进行了详细的讲解,这里我就不详细描述了,记得加上就行,我们重点关注实现。
下一步就应该编写第1步中的两个回调函数,一个用于注册设备,一个用于注销设备。探测函数示例如下:
static int tvp5158_attach_adapter(struct i2c_adapter *adapter) { return i2c_probe(adapter, &addr_data, &tvp5158_detect_client); }

这个回调函数系统会自动调用,我们只需要按照上述代码形式写好就行,这里调用了系统的I2C设备探测函数,i2c_probe(),第三个参数为具体的设备探测回调函数,系统会在探测设备的时候调用这个函数,需要自己实现。示例如下:
static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind) { struct tvp5158_obj *pObj; int err = 0; printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address); if( g_tvp5158_obj != NULL ) { //already allocated,inc user count, and return the allocated handle g_tvp5158_obj->users++; return 0; } /* alloc obj */ pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL); if (pObj==0){ return -ENOMEM; } memset(pObj, 0, sizeof(struct tvp5158_obj)); pObj->client.addr = address; pObj->client.adapter = adapter; pObj->client.driver = &tvp5158_i2c_driver; pObj->client.flags = I2C_CLIENT_ALLOW_USE; pObj->users++; /* attach i2c client to sys i2c clients list */ if((err = i2c_attach_client(&pObj->client))){ printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address); return err; } // store the pObj g_tvp5158_obj = pObj; printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address); return 0; }

到此为止,探测并且注册设备的代码已经完成,以后对该 I2C 设备的访问均可以通过 g_tvp5158_obj 这个全局的指针进行了。

⑥ 6. Linux-LCD 驱动程序概述

入局:应用程序是如何操控LCD显示器的?
      我们知道应用程序的调用接口,无非 open/read/write ...然后通过驱动程序最终作用到硬件设备上。以字符设备为例,对于驱动的开发者,实现了应用程序调用的驱动层中与之相匹配的 drv_open/drv_read/drv_write 函数,为应用层序提供了操作实际硬件设备的通道。那么,对于LCD驱动程序又是如何?先来了解下两个非常重要的概念。

      LCD控制器的功能是控制驱动信号,进而驱动LCD。用户只需要通过读写一系列的寄存器,完成配置和显示驱动。在驱动LCD设计的过程中首要的是配置LCD控制器,而在配置LCD控制器中最重要的一步则是帧缓冲区(Frame Buffer)的指定。用户所要显示的内容皆是从缓冲区中读出,从而显示到屏幕上的。帧缓冲区的大小由屏幕的分辨率和显示色彩数决定。驱动帧缓冲的实现是整个驱动开发过程的重点。
      帧缓冲区是出现在Linux 2.2.xx及以后版本内核当中的一种驱动程序接口,这种接口将显示设备抽象为帧缓冲区设备区。帧缓冲区为图像硬件设备提供了一种抽象化处理,它代表了一些视频硬件设备,允许应用软件通过定义明确的界面来访问图像硬件设备。这样软件无须了解任何涉及硬件底层驱动的东西(如硬件寄存器)。它允许上层应用程序在图形模式下直接对显示缓冲区进行读写和I/O控制等操作。通过专门的设备节点可对该设备进行访问,如/dev/fb*。用户可以将它看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以进行读写操作,而读写操作可以反映到LCD。

      帧缓冲(Frame Buffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由Frame Buffer设备驱动来完成的。帧缓冲设备属于字符设备。
      Linux系统Frame Buffer本质上只是提供了对图形设备的硬件抽象,在开发者看来,Frame Buffer是一块显示缓存,向显示缓存中写入特定格式的数据就意味着向屏幕输出内容。

由于有了frambuffer的抽象,使得应用程序通过定义好的接口就可以访问硬件。所以应用程序不需要考虑底层的(寄存器级)的操作。应用程序对设备文件的访问一般在/dev目录,如 /dev/fb*。

内核中的frambuffer在: drivers/video/fbmem.c (fb: frame buffer)

(1) 创建字符设备"fb", FB_MAJOR=29,主设备号为29。
(2)创建类,但并没有创建设备节点,因为需要注册了LCD驱动后,才会有设备节点;

2.1 fb_open函数如下:

(1) registered_fb[fbidx] 这个数组也是fb_info结构体,其中fbidx等于次设备号id,显然这个数组就是保存我们各个lcd驱动的信息;

2.2 fb_read函数如下:

从.open和.read函数中可以发现,都依赖于fb_info帧缓冲信息结构体,它从registered_fb[fbidx]数组中得到,这个数组保存我们各个lcd驱动的信息。由此可见,fbmem.c提供的都是些抽象出来的东西,最终都得依赖registered_fb这个数组。

这个register_framebuffer()除了注册fb_info,还创建了设备节点。

以s3c2410fb.c为例,分析驱动的实现。

既然是总线设备驱动模型,那我们关心的是它的probe函数。

看到这里驱动的写法也大致清晰:



附:
LCD的显示过程与时序:
      1.显示从屏幕左上角第一行的第一个点开始,一个点一个点地在LCD上显示,点与点之间的时间间隔为VCLK(像素时钟信号);当显示到屏幕的最右边就结束这一行(Line),这一行的显示对应时序图上的HSYNC(水平同步信号)
      2. 接下来显示指针又回到屏幕的左边从第二行开始显示,显示指针针在从第一行的右边回到第二行的左边是需要一定的时间的,我们称之为行切换。
      3. 以此类推,显示指针就这样一行一行的显示至矩形的右下角才把一幅图像(帧:frame)显示完成,这一帧的显示时间在时序图上表示为VSYNC(垂直同步信号)。

参考:
https://sites.google.com/a/hongdy.org/www/linux/kernel/lcddriver

⑦ 求教怎么学习linux内核驱动

1.首先要了解为什么要学习内核?下图已表明,如果要从事驱动开发或系统研究,就要学习内核。

2.内核的知识就像下面的绳结一样,一环扣一环,我们要解开它们,就必须要先找到线头也就是内核中的函数接口。初学阶段,我们一般不深入的研究内核代码,会使用内核的接口函数就不错了。

3.下面提供了如何学习这些内核函数的方法,就像解绳子一样

4.学习内核的四步法则,思维导图的设计尤为重要,这也是能否学习好内核的关键

5.语言基础也需要扎实,所以需要把C语言巩固巩固

热点内容
家用电脑改成服务器并让外网访问 发布:2025-02-01 15:30:23 浏览:354
javac工资 发布:2025-02-01 15:24:28 浏览:22
如何删除服务器登录账号 发布:2025-02-01 15:21:05 浏览:498
瑞萨编程器 发布:2025-02-01 15:19:18 浏览:85
上海ntp服务器搭建 发布:2025-02-01 15:03:38 浏览:991
c游戏编程基础 发布:2025-02-01 15:00:17 浏览:993
routejs怎么动态配置 发布:2025-02-01 14:59:07 浏览:502
家用电脑安装服务器内存 发布:2025-02-01 14:38:50 浏览:257
增量调制编译码实验报告 发布:2025-02-01 14:30:30 浏览:787
不良人2无敌伤害脚本 发布:2025-02-01 14:23:04 浏览:398