Linux驱动开发入门

设备驱动分类
字符设备:可一个一个字节读取的设备,一般要实现open close read write ioctl等操作,
内核为字符设备对应一个文件如"/dev/consloe",对字符设备的操作通过操作设备文件实现,不可随机读写
块设备 : 类似字符设备,可以容纳文件系统,存储大量信息,每次传输一个或多个块。也可像字符设备一样
每次读取一个字节,可随机读写
网络设备 : 负责主机之间数据交换,实现套接字接口

insmod 将模块加入正在运行的内核中
rmmod 将未使用的模块从内核中删除, 不可删除正在使用的模块。
静态加载:内核启动时加载
动态加载:内核运行时加载

模块基本框架

#include 
#include 
#include 

int __init xxx_init(void)
{ /* 模块加载时初始化 */
   retrun 0;
}
void __exit xxx_exit(void)
{ /*  模块卸载时销毁工作 */
}
module_init(xxx_init);
module_exit(xxx_exit);

"linux/string.h" 
"linux/slab_def.h" 内存分配函数实现 
内核没有C库,没有内存保护机制,内核栈小(需固定常驻内存空间32位机8kb, 64位机16kb),重视可移植性

ARM (Advanced RISC Manchines)

###使用busybox构建根文件系统,make menuconfig配置(需要的时候细看)###

升级内核: 
1,下载内核源码解压,make menuconfig 
2, make / make modules / make modules_install / make install / reboot 
3, 使用uname - r 查看内核版本
                                                                                                                             
驱动模块的组成 
1,头文件
#include  // 加载模块时需要的符号和函数定义
#include    // 包含模块加载函数和模块释放函数的宏定义
2,模块参数(optional) 驱动加载时,需要传递给驱动模块的参数 
3,4,模块加载和卸载函数(必须) 
5,模块许可声明(必须) MODULE_LICENSE("xxx");
"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MPL/GPL", "Proprietary"

编译模块 
ifeq($(KERNELRELEASE), ) 
   KERNELDIR ? = / linuxdir / 
   PWD : = $(shell pwd) 
modules : $(MAKE) - C $(KERNELDIR) M = $(PWD) modules 
modules_install : $(MAKE) - C $(KERNELDIR) M = $(PWD) modules_install 
clean : rm - rf *.o *~core.depend.*.cmd *.ko *.mod.c.tmp_versions 
else 
   obj - m : = hello.o 
endif

modutils 工具操作模块 
1,insmod 加载模块, insmod hello.ko,
或可在 / var / log / messages 文件中demsg | tail 查看加载的打印信息,
传参 insmod modules.ko param1 = value1 param2 = value2 
2,rmmod 卸载模块
3,modprobe 
4,lsmod 列出模块
5,modinfo 查询模块的相关信息
模块加载后查看信息
/ proc / modules 
cat modules| grep xxx
包含模块名,使用的内存,引用计数,分隔符,活跃状态,加载到内核中的地址
/ proc / devices 如果是设备模块会有变化
/ sys / module / 增加模块的基本信息
/ sys / module / 增加一个模块的目录

定义模块参数 
static long a = 1;
static int b = 1;
module_param(a, long, S_IRUGO); //参数名,参数类型,参数读写权限
module_param(b, int, S_IRUGO);
类型:byte, short, ushort, int, uint, long, ulong, bool, charp(字符指针类型) 
file module.ko 查看模块文件格式 
ELF header; 描述文件基本属性,如文件版本,目标机器型号,程序入口地址
.text
.data;存放初始化的数据
.Section Table;描述文件包含的段的信息,如段名,长度,偏移,读写权限即其他
.symtab ;符号表,映射函数到真实内存地址的数据结构,模块加载阶段系统赋予真实的内存地址
       
将模块添加到内核中 
1,将去驱动程序文件放到linux源码目录中 
2,在目录的Kconfig文件中添加新驱动程序对应的编译选项 
3,在目录的Makefile文件中添加新驱动程序的编译语句

一个字符设备或块设备都有一个主设备号和此设备号,统称为设备号。 
主设备号:表示一个特定的驱动程序
次设备号:表示使用该驱动的各设备

typedef u_long dev_t; //32位计算机,高12位为主设备号,低20位位次设备号
使用宏获取主次设备号 
linux / include / kdev_t.h 
register_chrdev_region() 静态分配设备号,容易造成冲突;
使用alloc_chrdev_region() 动态分配设备号;
fs / char_dev.c 
unregister_chrdev_region() 
读取 / proc / devices 获取设备的设备号

cdev 结构,描述字符设备,是所有字符设备的抽象 每个字符设备在/dev下都有一个设备文件,
打开设备文件,系统产生一个inode节点。通过inode的i_cdev字段找到cdev 结构体,
通过cdev的ops指针,就可以找到设备的操作函数。 file_operations结构体存储操作指针

内核使用inode结构表示文件 用户空间和内核空间的数据交换采用专用函数 
copy_to_user(),
copy_from_user(), 
put_user(), 
get_user()

atomic_t 原子变量类型 原子整形操作,原子位操作

自旋锁 struct spinlock_t;
是一种忙等待,锁住的时候一直循环检测条件是否满足,短时间轻量级的锁机制
不可递归使用
spinlock_t lock = SPIN_LOCK_UNLOCK;
spin_lock_init(&lock); //动态初始化锁
spin_lock(&lock);
spin_unlock(&lock);

内核信号量
没有获得信号量的函数可能睡眠,故只有能够睡眠的进程才可以使用信号量,中断处理程序不可使用;
用在进程对被保护资源的占用时间比进程上下文切换长很多(睡眠需要进程切换上下文)
struct semaphore
{
   spinlock_t lock;            //保护count
   unsigned int count;         //0 信号量被使用,无等待进程,<0 至少有一个进程等待,>0 空闲;
   struct list_head wait_list; //睡眠等待进程队列
};
sema_init();
down();               //获取信号量,不可被唤醒
down_interruptible(); //可被唤醒
up();                 // 释放信号量

完成量
一个线程发送一个信号通知另一个线程开始完成某个任务
struct completion
{
   unsigned int done;      //0 将拥有完成量的线程置于等待状态, >0等待完成量的函数可以立刻执行
   wait_queue_head_t wait; //等待队列链表,存放正在睡眠的进程连边
};
init_completion();
wait_for_completion(); //等待,不会被信号中断的等待
complete();            //唤醒完成量
complete_all();

等待队列
struct wait_queue_head
{
   spinlock_t lock;
   struct list_head task_list;
}

定义系统调用
asmlinkage int sys_xxx(...){}
编译进内核并启动,会增加新的系统调用__NR_xxx;
调用方法 syscall(__NR_xxx, ...);

{
DEFINE_WAIT(wait); // 初始化一个wait_queue_head
prepare_to_wait(p_wait_list, &wait, TASK_INTERRUPTIBLE);//当前进程进入阻塞队列
schedule();//重新调度

finish_wait(p_wait_list, &wait);//进程被唤醒从阻塞队列退出
}
wake_up(p_wait_list);//唤醒等待队列的进程

中断分类
硬中断:随机性,突发性,外部事件触发硬件产生,可屏蔽
软中断:确定性,由程序执行中断指令产生的。处理器执行到错误的指令代码;由软件产生中断
外部中断:由硬件触发,可屏蔽
内部中断:硬件出错或运算出错引起的,不可屏蔽
同步中断:中断来了不会打断当前执行的指令,一般由程序错误引起
异步中断:硬件产生,随时的,此中断处理程序和内核是异步执行的,不会影响,如接收网络数据

申请/释放中断线
request_irq();
free_irq();
ioremap();//将物理端口地址转换为内核地址

static int __init button_init(void){
   int ret;
   set_irq_type(K1_IRQ, IRQ_TYPE_EDGE_FALLING);
   ret = request_irq(K1_IRQ, isr_button, SA_INTERRUPT, DEVICE_NAME, NULL);
   if (ret){
       printk("K1_IRQ: could not register interrupt\n");
       return ret;
   }
   printk(DEVICE_NAME "initialized\n");
   return 0;
}
static irqreturn_t isr_button(int irq, void* dev_id, struct pt_regs*regs){
   unsigned long GPGDAT;
   GPGDAT = (unsigned long)ioremap(0x56000064, 4);
   if (irq == K1_IRQ){
       if ((*(volatile unsigned long*)GPGDAT) & 1 == 0){
           printk("K1 is pressed\n");
       }
   }
   return 0;
}
static void __exit button_exit(void){
   free_irq(K1_IRQ, NULL);
   printk(DEVICE_NAME"exit\n");
}

内核内存动态分配 
1,分配的时候不会清空存储空间的原有数据,分配连续物理内存空间
static inline void* kmalloc(size_t size, gfp_t flags);
kfree();
size 内核先分配一系列大小不同的内存池,选取合大小的内存
flags 控制分配的行为
2,分配虚拟地址连续的内存,物理地址不连续,开销大,用来申请大内存,小内存
使用__get_free_pages();
void *vmalloc(unsigned long size);
void vfree(const void*addr);
3,后备高速缓存slab,固定大小的内存池,用于需要频繁创建和销毁
struct kmem_cache *kmem_cache_create(const char* name, size_t size/*块数目 */, size_t align,
   unsigned long flags, void (*ctor)(void*));
void *kmem_cache_alloc(struct kmem_cache *cache, gfp_t flags);
void kmem_cache_free(struct kmem_cache * cache, void*objp);
void kmem_cache_destroy(struct kmem_cache *cache);
4,页面分配,物理地址和虚拟地址转换
5,将I/O端口映射到I/O内存空间访问
#define request_mem_region(start/*起始IO物理地址*/,n/*字节长度*/,name) \
     __request_region(&iomem_resource,(start),(n),(name),0)
#define release_mem_region(start, n) __release_region(&iomem_resource,(start),(n))
void __iomem *ioremap(unsigned long phys_addr, unsigned long size/*整个IO端口大小*/);
void iounmap(volatile void __iomem *addr);
ioread8();iowrite8();//16 32
ioreadxx_rep();//多次读取
iowritexx_rep();//多次写入
6,I/O端口访问
#define request_region(start/*I/O端口地址*/,n/*端口数*/,name) \
     __request_region(&ioport_resource,(start),(n),(name),0)
struct resource *__request_region(struct resource *parent, resource_size_t start, 
resource_size_t n, const char*name, int flags);
#define release_region(start,n) __release_region(&ioport_resource, (start),(n))
void __release_region(struct resource*parent, resource_size_t start, resource_size_t n);
static inline u8 inb(u16 port);
static inline void outb(u8 v, u16 port);// b w l

设备驱动模型:设备和驱动组成的层次结构
sysfs文件系统,存在于内存中,内核通过此文件系统将信息导出到用户空间中,
是内核对象(kobject)、属性(kobj_type)及他们的相互关系的一种表现机制,可以从sysfs文件系统中读出内核数据,
可将用户空间的数据写入内核。/sys/
设备启动时,设备驱动模型注册kobject对象,并在sysfs文件系统中产生相应目录
drwxr-xr-x   2 root root    0 Jul 17 20:50 block/
drwxr-xr-x  39 root root    0 May 15 08:30 bus/
drwxr-xr-x  60 root root    0 May 15 08:30 class/
drwxr-xr-x   4 root root    0 May 15 08:30 dev/
drwxr-xr-x  42 root root    0 May 15 08:30 devices/
drwxr-xr-x   5 root root    0 May 15 08:30 firmware/
drwxr-xr-x   8 root root    0 May 15 08:30 fs/
drwxr-xr-x   2 root root    0 Jul 17 20:50 hypervisor/
drwxr-xr-x  11 root root    0 May 15 08:30 kernel/
drwxr-xr-x 137 root root    0 May 15 08:30 module/
drwxr-xr-x   2 root root    0 Jul 17 20:50 power/
如block/ 每个子目录对应一个块设备,记录各种属性

每一个目录都与一个kobject对象对应,是组成设备驱动模型的基本结构
最底层目录是一个设备、驱动或其他内容,一个目录包含一些属性,以文件的方式表示ktype
kobject_init();
kobject_get();//增加引用计数,不为0的时候,对象必须存在
kobject_put();//减少引用计数,为0时,系统将释放资源
kobject_set_name();
kobject_rename();
kobject_add();//在sysfs文件系统中创建一个目录

kobject {name,entry/*指向下一个kobject*/,parent/*指向父kobject*/,kset,ktype,sysfs_dirent/*对应的sysfs的
文件目录*/,kref,state_initialized,state_in_sysfs,state_add_uevent_sent,state_remove_uevent_sent}

kobj_type {release,sysfs_ops, attribute}
attribute {name , owner, mode /*mode_t */}
sysfs_ops {show/*读属性 */,store/*写属性 */}

添加非默认属性
sysfs_create_file();
sysfs_remove_file();

kset {list/*kobject链表*/,lock, kobject/*表明本身是一个*/,kset_uevent_ops/*热插拔事件 */}
kobject通过kset组织成层次化的结构。是具有相同类型kobject集合,如驱动程序一样放在/sys/drivers/目录下,
目录drivers是一个kset对象,包含系统中驱动程序对应的目录,驱动程序的目录由kobject表示

一个热插拔事件是从内核空间发送到用户空间的通知,表明系统某些部分的配置已经变化。用户空间收到通知,调用相应的
程序,处理配置的变化。
当驱动程序将kobject注册到设备驱动模型时,内核产生热插拔事件,即kobject_add,kobject_del,热插拔事件产生时
内核根据kobject的kset指针找到所属kset结构体,执行kset中的热插拔函数
任何热插拔程序需要的信息可以通过环境变量来传递
kset_uevent_ops {filter/*决定是否向用户空间发信号 */, name/*获取子系统名字 */, uevent/*在热插拔程序执行前
,向环境变量写入值 */}

kset_init();
kset_register();
kset_unregister();
kset_get();
kset_put();

设备驱动模型三大组件:总线,设备,驱动
此模型中,所有设备通过总线连接,是物理总线的抽象,包含虚拟总线。驱动程序附属在总线上。
bus_type
总线目录下包含设备目录、驱动目录、总线属性,设备和驱动相互绑定
device
设备结构体包含设备的通用信息
device_driver
一个设备对应一个最合适的设备驱动程序,一个设备驱动程序可能适用多个设备,设备驱动模型会自动探测新设备的产生,并
分配最合适的驱动程序

平台设备模型
优势:平台设备模型将设备本身的资源注册进内核,由内核统一管理。提高驱动和资源管理的独立性,可移植性好,安全
平台设备:处理器上集成的额外功能的附加设备
platform_device {name,id,device/*设备 */,num_resources,resource/*申请的资源 */}
资源类型:IORESOURCE_IO, IORESOURCE_MEM,IORESOURCE_IRQ,IORESOURCE_DMA
platform_add_devices();//分配平台设备使用的资源,将资源挂到资源树上;初始化device设备,将设备注册到系统
platform_get_resource();

platform_driver{probe,remove,shutdown,suspend,suspend_late,resume_early,resume,driver/*设备驱动 */}
包含设备驱动和对平台设备的一些操作
内核启动时,会注册设备平台设备驱动程序。适当时候通过匹配将设备和驱动连接起来
platform_match();//找到驱动时触发probe()函数,可在其中申请设备所需资源

platform_driver_register();
platform_driver_unregister();

混杂设备:由于设备号比较紧张,一些设备可以使用同一个主设备,不同的次设备号。主设备通常时10,是特殊的字符设备
miscdevice{minor/*次设备号*/,name,file_operations,list,parent,this_device}
misc_register();
misc_unregister();

IDR机制:整数ID管理机制,实质是将一个整数ID和一个指针关联起来

FrameBuffer
帧缓冲,位于内核空间,通过FrameBuffer能透明的访问不同的显示设备,是硬件设备显示缓存区的抽象。
通过操作FrameBuffer直接对显存进行操作。将其映射到用户地址空间,写操作会立即反应再屏幕上
是标准的字符设备,主设备号是29,次设备号是0~31,对应/dev/fb0~31, 默认/dev/fb指向0号设备。
cp /dev/fb0 myscreen.png // 截屏, 缓冲区是普通的内存设备,可以直接对其进行读写

LCD显示原理,FrameBuffer驱动分配一块内存做显存,对LCD控制器做一些设置。LCD显示器从显存中获取数据。将其显示在
显示器上。
填充fb_info结构,register_framebuffer(fb_info*),将fbinfo注册到内核,实现其fs_ops中的接口
fb.h fbmem.c 

struct fb_var_screeninfo;//描述图形卡的特性,用户设置
struct fb_fix_screeninfo;//定义了图形卡的硬件特性不可改变
struct fb_info;//当前图形卡FrameBuffer设备的独立状态,只对内核可见
struct fb_cmap;//定义帧缓冲设备的颜色表,通过ioctl()的FBIOGETCMAP和FBIOPUTCMAP命令设置colormap
struct fb_ops;//定义操作函数

1,编写初始化函数
初始化LCD控制器,通过写寄存器设置显示模式和显示颜色数,分配LCD显示缓冲。起始地址保存在控制器寄存器中。
初始化一个fb_info结构,将其注册进内核
2,编写fb_ops
实现fb_ops的函数

LCD驱动程序以平台设备的方式实现
实例 s3c2410.h/s3c2410.c
s3c_device_lcd/devs.c

input 子系统
驱动层->核心层(input.c)->处理层->用户空间层
struct input_dev;//物理输入设备的基本数据结构,包含设备相关信息
struct input_handler;//事件处理结构体,定义如何处理事件的逻辑
struct input_handle;//用来创建input_dev和input_handler之间关系

你可能感兴趣的:(Linux驱动开发入门)