platform: JZ2440
Kernel: linux-2.6.22.6
==========================================================
1st_drv:
编写第一个驱动:点亮LED灯。
注册字符设备,应用程序一直在查询读取到的命令。
总结知识点:
如何注册字符设备:
1. 主设备号
2. file_operations结构体分配
3. 注册字符设备:register_chrdev(major, "name", &file_operations) // 建立主设备号和file_operation结构体之间的关系
4. 修饰符: module_init, module_exit
5. MODULE_LICENSE("GPL");
应用层写操作函数,如何进入到内核驱动层去写呢?
驱动操作函数: copy_from_user(dest, src, size);
相关操作命令:
insmod
rmmod
lsmod
cat /proc/devices
可以利用man +命令的方式,查看命令所需要的头文件,方法以及返回值。
比如:$man open
查看manual。
2nd_drv:
读取按键的状态值。
应用程序一直在查询按键管脚状态值,CPU一直被占用。
总结知识点:
如何注册字符设备:
1. 主设备号
2. file_operations结构体分配
3. 注册字符设备:register_chrdev(major, "name", &file_operations) // 建立主设备号和file_operation结构体之间的关系
4. 修饰符: module_init, module_exit
5. MODULE_LICENSE("GPL");
应用层读操作函数,如何进入到内核驱动层去读呢?
驱动操作函数: copy_to_user(dest, src, size);
相关操作命令:
./seconddrvtest & // “&”可以让应用程序在后台运行
top // 命令可以查看进程使用CPU的情况
kill -9 PID // 可以杀死进程号为PID的进程
3rd_drv:
利用中断方式,注册按键中断到内核。
这样解放了CPU一直查询按键的任务。
但没有按键按下的时候,读取按键的任务进入休眠态;只有当按键按下,触发中断时候,则进程成休眠态返回,并读取相应的按键值。
注意:没有按键按下时,进程一直休眠不返回。
知识点总结:
1. 中断注册:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
中断释放:
void free_irq(unsigned int irq, void *dev_id)
2. 休眠队列:
2.1 声明定义休眠队列:
static DECLARE_WAIT_QUEUE_HEAD(buttonQueue);
2.2 唤醒队列,在中断里面调用
wake_up_interruptible(&buttonQueue);
2.3 等待队列唤醒,读取按键,ev_press为状态值,当ev_press = 1,唤醒队列:buttonQueue
在读取函数中调用,如果没有按键按下,则进入休眠;如果按下则唤醒进程,读取按键值
wait_event_interruptible(buttonQueue, ev_press);
3. file_operations函数:
static const struct file_operations third_fops = {
.owner = THIS_MODULE,
.open = third_drv_open, /* 应用程序打开时调用 */
.read = third_drv_read, // 在这个函数中,读取按键值
.release = third_drv_close, /* 应用程序退出时调用 */
};
相关操作命令:
cat /proc/interrupt //用来查看在内核中注册的中断情况
4th_drv:
POLL机制,可以设置休眠的超时时间。当到达超时时间并且仍然没有按键数据可以读取,还在休眠的应用程序会超时返回。
防止该进程永不返回。
知识点总结:
1. 驱动POLL,在file_operations结构体中添加poll函数:
static const struct file_operations forth_fops = {
....
.poll = forth_drv_poll,
};
unsigned int forth_drv_poll(struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &buttonQueue, wait); // 不会立即休眠
if (ev_press) // 如果有按键按下,则触发事件POLLIN
mask |= POLLIN | POLLRDNORM;
return mask;
}
2. 应用程序POLL:
struct pollfd fds[1];
fds[0].fd = fd; /* 对应的文件句柄 */
fds[0].events = POLLIN; /* 触发的事件 */
// 设置超时时间:5000ms
ret = poll(fds, 1, 5000);
if (ret == 0)
{
/* 超时打印 */
printf("time out\n");
}
else
{
/* 在未超时时间内读取键值,如果有按键按下,则返回;如果没有按键按下,则进入休眠态 */
read(fd, &keyVal, 1);
printf("cnt: %d, keyVal: 0x%x\n", cnt++, keyVal);
}
5th_drv:
驱动:异步通知机制
如果有按键按下,则驱动会发出一个按键按下的信号给应用程序,通知它来读取。
相当于:驱动实现"软件"中断的方式,当有按键按下时,打印按键信息。
知识点总结:
1. 驱动aysnc,在file_operations结构体中添加fasync函数
// 定义声明
static struct fasync_struct *button_async;
// 在按键中断处理函数中
static irqreturn_t key_isr(int irq, void *dev_id)
{
...
kill_fasync (&button_async, SIGIO, POLL_IN); //如果进入按键中断,则发送信号 SIGIO 给应用层程序
}
static const struct file_operations fifth_fops = {
...
.fasync = fifth_drv_fasync,
};
/* 这个函数什么时候被调用呢?下面有解释 */
static int fifth_drv_fasync (int fd, struct file *filp, int on)
{
printk("driver: fifth_drv_fasync\n");
return fasync_helper (fd, filp, on, &button_async);
}
2. 应用程序设置启动async功能:
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
main{
...
signal(SIGIO, my_signal_fun); // 设置当驱动层有信号传来(表示有按键按下),调用哪个函数来处理
fcntl(fd, F_SETOWN, getpid()); // 设置fd属于哪个进程pid:绑定文件句柄fd和进程PID
Oflags = fcntl(fd, F_GETFL); // 获取该fd的标志参数
fcntl(fd, F_SETFL, Oflags | FASYNC); // 设置并启动fd的FASYNC功能: 这句话设置完成将调用驱动程序的函数:fifth_drv_fasync
}
6th_drv:
定义互斥锁机制,保证应用程序不会同时被调用。
知识点:共享资源的使用。比如对同一个缓冲区进行读写操作,这会导致数据混乱。
例子:互斥锁机制,阻塞/非阻塞模式
知识点总结:
1. 驱动程序:互斥锁机制
// 定义互斥锁
static DECLARE_MUTEX(button_lock);
// 获取信号量
// 打开文件:阻塞和非阻塞方式访问
int sixth_drv_open(struct inode *inode, struct file *file)
{
if (file->f_flags & O_NONBLOCK)
{
if (down_trylock(&button_lock))
{
//如果为非阻塞方式访问,则获取不到资源立刻返回
return -EBUSY;
}
}
else
{
/* 阻塞方式 获取信号量:
* 如果该信号量已经被占用, 则该进程一直在这里等待不返回,直到该信号量被释放
*/
down(&button_lock);
}
...
// 初始化操作....
}
// 读函数
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
// 非阻塞方式读取,则如果获取不到资源,立刻返回
if (file->f_flags & O_NONBLOCK)
{
if (!ev_press)
{
printk("sixth_drv_read: O_NONBLOCK return\n");
return -EAGAIN;
}
}
else // 阻塞方式访问,如果获取不到资源,进入休眠态等待
{
/* 等待按键按下事件发生 */
wait_event_interruptible(buttonQueue, ev_press);
}
// 释放信号量
int sixth_drv_close(struct inode *inode, struct file *file)
up(&button_lock); /* 释放信号量*/
2. 应用程序
fd = open("/dev/sixthDrv", O_RDWR | O_NONBLOCK); // 以非阻塞的方式访问文件
fd = open("/dev/sixthDrv", O_RDWR ); // 以阻塞的方式访问文件
7th_drv:
定义一个定时器模块,用来消除按键抖动。10ms的按键采样频率.
知识点总结:
1. 驱动模块
// 定义timer链表
static struct timer_list buttons_timer;
//定时器的初始化
int seventh_drv_init(void)
{
...
init_timer(&buttons_timer); // 初始化定时器
buttons_timer.function = buttons_timer_function; // 设置定时器超时处理函数
buttons_timer.expires = 0; // 设置定时器超时时间
add_timer(&buttons_timer); // 注册定时器模块
}
// 在按键中断里,用10ms的频率处理按键抖动情况:
// 一次按键中断发生,就延长10ms后处理,直到在10ms里检测不到中断,表示一次有效按键产生
static irqreturn_t key_isr(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + (HZ / 100)); /* 10ms按键去抖动*/
return IRQ_RETVAL(IRQ_HANDLED);
}
// 在定时器超时中断函数中,读取有效的按键值
static void buttons_timer_function(unsigned long data)
{
...
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&buttonQueue); /* 唤醒休眠的进程 */
}
2. 应用程序:
3. 测试
先输入两次:
./seventhdrvtest &
./seventhdrvtest &
再输入
top
看到打印信息:
Mem: 6652K used, 54532K free, 0K shrd, 0K buff, 1996K cached
CPU: 0% usr 0% sys 0% nice 99% idle 0% io 0% irq 0% softirq
Load average: 0.56 0.15 0.05
PID PPID USER STAT VSZ %MEM %CPU COMMAND
779 771 0 R 3096 5% 0% top
771 1 0 S 3096 5% 0% -sh
1 0 0 S 3092 5% 0% init
777 771 0 S 1312 2% 0% ./seventhdrvtest (sleep模式,当没有按键按下的时候)
778 771 0 D 1308 2% 0% ./seventhdrvtest (获取不到资源,进入僵死状态,除非上一个应用程序退出)
8th_drv:
学习输入子系统,重点啊。linux驱动大多数都是用输入子系统。
知识点总结:
驱动分层结构 - 两部分:input_handler, input_device
0. 核心层:\drivers\input\input.c
在该文件中定义了:驱动和设备向核心层注册/卸载等函数。
1. 软件部分:驱动部分,linux内核已经做好了,这部分不需要修改,只需要知道怎么工作。
在内核启动时,就会注册输入子系统的驱动部分,调用核心层(input.c)的: input_register_handler
注册input_handler结构体,具体有:
a. 文件: \drivers\char\keyboard.c
input_register_handler(&kbd_handler); // kbd, 没有生成对应的设备文件
b. 文件: \drivers\input\evdev.c
input_register_handler(&evdev_handler); // 注册设备:/dev/event0...
c. 文件: \drivers\input\mousedev.c
input_register_handler(&mousedev_handler); // 注册设备:/dev/mouse0...
d. 文件:\drivers\input\tsdev.c
input_register_handler(&tsdev_handler); // 注册设备: /dev/ts0
注意:设备文件只有当input_handler和input_device匹配的时候,才创建。
2. 设备部分,这部分是需要修改的,根据自己的硬件来进行修改。
驱动编写:
2.1 分配一个input_device结构体
2.2 设置该结构体
2.3 注册:input_register_device(input_device)
3. 注册input_device程序流程分析:
input_register_device(input_device)
// 将该input_device放入链表input_dev_list
list_add_tail(&dev->node, &input_dev_list);
// 遍历input_handler_list链表,寻找与之匹配的input_handler
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
// 找到匹配的input_hanlder,返回id
id = input_match_device(handler->id_table, dev);
// input_handler里面有个id_table,用来和input_device设置的标志位进行比较,
// 看看是否匹配;如果匹配则返回id
for (; id->flags || id->driver_info; id++) // 比较id->flags
MATCH_BIT(evbit, EV_MAX); // 比较相关的标志位
// 如果找到了匹配的input_handler,则调用input_handler里面的connect函数,来进行input_device和input_handler的绑定
handler->connect(handler, dev, id);
举个例子, 对于evdev_handler来说,就是: evdev_connect
evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
// 分配结构体:struct evdev,里面有个handle结构体
struct evdev *evdev;
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
// 设置,注意handle结构体,一边连着input_handler,一边连着input_device
evdev->handle.dev = dev;
evdev->handle.handler = handler;
sprintf(evdev->name, "event%d", minor);
// 设置主次设备号
devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor)
// 创建设备文件/dev/event0...,主设备号为:INPUT_MAJOR = 23,次设备号为:EVDEV_MINOR_BASE + 0 = 64
cdev = class_device_create(&input_class, &dev->cdev, devt, dev->cdev.dev, evdev->name);
// 注册handle结构体
input_register_handle(&evdev->handle);
// 把handle同时挂在input_handler和input_device上面
list_add_tail(&handle->d_node, &handle->dev->h_list);
list_add_tail(&handle->h_node, &handler->h_list);
总结一下:
a. 内核启动后,就会自动注册各种input_handler,并且将他们挂接到同一个链表input_handler_list上面
b. 程序员需要编写与硬件相关的驱动程序,调用register_input_device向内核注册一个input_device结构体
c. 一旦向上注册,则会遍历input_handler链表,找到与input_device相匹配的一个或者多个input_handler结构体
d. 当找到匹配的input_handler后,就会调用input_hanlder的connect函数
e. 在input_handler->connect函数中,分配并设置input_handler里的成员,然后创建与input_device对应的设备驱动文件
f. 最后注册input_handle结构体,这个结构体将input_handler和input_device绑定在一起
4. 输入子系统设备驱动文件的open, read, write函数
见文档:[1st_input子系统.TXT]
驱动编写:
1. 分配input_device结构体
static struct input_dev *button_dev;
button_dev = input_allocate_device();
2. 设置
button_dev->name = "button_input";
/* 设置产生的事件 */
set_bit(EV_KEY, button_dev->evbit); // 按键事件
set_bit(EV_REP, button_dev->evbit); // 重复按键事件
/* 设置产生哪些按键事件*/
set_bit(KEY_L, button_dev->keybit);
set_bit(KEY_S, button_dev->keybit);
set_bit(KEY_ENTER, button_dev->keybit);
set_bit(KEY_LEFTSHIFT, button_dev->keybit);
3. 注册input_device
input_register_device(button_dev);
注册之后,如果找到匹配的input_handler,则会在/dev目录下创建相应的设备文件
4. 上报事件,同步事件
当检测到有效的按键中断后,则读取按键值,并且上报事件给内核:
input_event(button_dev, EV_KEY, ptPinDesc->keyCode, 0);
input_sync(button_dev);
测试:
9th_drv:
分离结构: bus-driver-device驱动模型
linux内核驱动有一些是依赖于真正的总线,比如SPI, IIC, USB等。
但是有一些设备是没有真正总线的,linux内核将这些设备抽象出来,定义了一个虚拟的总线,
叫做:platform bus - 平台总线
分离:
platform_bus连接platform_device和platform_driver
核心层:\drivers\base\platform.c,里面定义了注册/卸载设备和驱动的程序等。
知识点总结:
1. 几个重要的数据结构:
1.1 struct platform_driver:
可以看出:platform_driver是struct device_driver的继承,核心的是struct device_driver;
只是将一些方法
比如:probe, remove, shutdown, ...等等
进行了重载。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
1.2 struct device_driver
可以看到在struct device_driver的结构体中也定义了方法:probe, remove, shutdown... 等,
而在struct platform_driver也定义了这些方法,这就可以方便地进行方法的重载。
在该结构体中,还包含一个重要的结构体:struct bus_type
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
1.3 struct bus_type
看看在bus_type结构体中也定义probe, remove, shutdown, ...等等。
这说明bus_type才是父结构体,其他struct device_driver和struct platform_driver是在这个结构体上的继承。
在bus_type结构体中,注意有个match函数,用来匹配dev和drv。
struct bus_type {
const char * name;
struct module * owner;
struct kset subsys;
struct kset drivers;
struct kset devices;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs;
struct device_attribute * dev_attrs;
struct driver_attribute * drv_attrs;
struct bus_attribute drivers_autoprobe_attr;
struct bus_attribute drivers_probe_attr;
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
int (*probe)(struct device * dev);
int (*remove)(struct device * dev);
void (*shutdown)(struct device * dev);
int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);
unsigned int drivers_autoprobe:1;
};
1.4 struct platform_device
这个结构体包含struct device dev,以及资源定义:struct resource。
这个结构体继承自:struct device dev
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
1.5 struct device
里面含有对应的driver结构体:struct device_driver
struct device {
......
char bus_id[BUS_ID_SIZE]; // bus总线名称
struct bus_type * bus; // bus_type结构体, 里面有个match函数, 用来drv和dev匹配
struct device_driver *driver; // 匹配的struct device_driver
void *platform_data; // 私有数据
void (*release)(struct device * dev);
};
1.6 platform_bus_type
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
2. 程序流程分析
2.1 具体流程分析
从注册platform_driver开始分析:
int platform_driver_register(struct platform_driver *drv)
// 设置struct bus_type类型为platform_bus_type
drv->driver.bus = &platform_bus_type;
// 方法的重载,比如 drv->driver.probe = drv->probe = platform_drv->probe.
// 如果子模块定义了probe函数,则父模块重载为子模块定义的probe函数
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
// 注册父模块:struct device_driver = drv->driver
总结:
设置bus type: drv->driver.bus = &platform_bus_type;
重载方法为: drv->driver->probe = platform_drv->probe;
drv->driver->remove = platform_drv->remove;
drv->driver->shutdown = platform_drv->shutdown;
....
driver_register(&drv->driver);
//这里drv->bus设置为: platform_bus_type;
//下面这个if不成立
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown)) {
printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
}
bus_add_driver(drv); // struct device_driver drv;
struct bus_type * bus = get_bus(drv->bus); // bus = &platform_bus_type;
if (drv->bus->drivers_autoprobe) // 内核启动的时候,注册过总线: bus_register(&platform_bus_type);
// 在该函数中,设置:bus->drivers_autoprobe = 1;
driver_attach(drv);
// 遍历struct device链表上的每个device, 调用__driver_attach进行匹配
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
if (!dev->driver) // 如果该device没有匹配驱动,则看看是否与现在在注册的驱动能否匹配(很合理吧!!!)
driver_probe_device(drv, dev);
// dev->is_registered = 1表示该设备device挂接到BUS总线上
if (! device_is_registered(dev))
return -ENODEV;
// 前面分析知道: drv->bus = &platform_bus_type
// 所以: drv->bus->match = platform_bus_type.match = platform_match
// 如果匹配不成功,则返回,这里仍然设置: dev->is_registered = 1, 并将该driver挂接到BUS的driver链表中
// 这样的话,当有新的设备device注册进内核时,仍然会调用该驱动看是否匹配新加进来的设备(这样做很合理)
if (drv->bus->match && !drv->bus->match(dev, drv))
// 替换可知,最后执行函数: platform_match
platform_match(struct device * dev, struct device_driver * drv)
// 找到包含struct device的struct platform_device结构体
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
// 比较 platform_device 和 platform_driver 的name, 如果相等,则表示匹配上了
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
// 如果匹配上了,则调用下面的函数,进行device的probe
really_probe(dev, drv)
// 将drv挂接到dev上,表示该设备的驱动找到了
// 以后注册驱动跟它无关,前面有提到只有没有匹配上驱动的设备才能遍历
dev->driver = drv;
// 调用真正的probe函数
if (dev->bus->probe) {
// 设备dev层应该另外分析: dev->bus = &platform_bus_type,没有定义probe函数
ret = dev->bus->probe(dev);
} else if (drv->probe) {
// 这里drv->probe = platform_drv->probe,用前面在struct platform结构体中重载过的方法: platform_drv->probe
ret = drv->probe(dev);
}
driver_bound(struct device *dev)
// 猜测是: 将device绑定到该驱动下,一个驱动下面可以绑定很多设备(很合理吧)
// 猜想: 设备端注册会这样做吗?一个设备下面绑定很多驱动(貌似不可以吧)
klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
if (ret >= 0)
// 将drv挂接到bus下的: bus->klist_drivers
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
else
// 只有当出现错误的时候,ret < 0,
dev->is_registered = 0;
---------------------------------------------------------------
从注册 platform_device 开始分析:
platform_device_register(struct platform_device * pdev)
platform_device_add(pdev);
pdev->dev.bus = &platform_bus_type; // 设置 platform_device -> struct device -> bus_type = platform_bus_type;
device_add(&pdev->dev); // pdev->dev 为 struct device 结构体;
bus_add_device(struct device * dev)
bus_attach_device(struct device * dev)
struct bus_type *bus = dev->bus; // bus = &platform_bus_type;
if (bus) {
dev->is_registered = 1; // 为什么设置为1,看下面就知道了
if (bus->drivers_autoprobe) // bus->drivers_autoprobe 在哪里设置的呢?上面的分析同样有这一句话
ret = device_attach(dev);
if (dev->driver)
// 如果该dev已经指定了driver,则直接绑定就好
ret = device_bind_driver(dev);
else
// 如果没有指定驱动,则遍历bus下面的driver链表: bus->klist_drivers
// 并且调用函数: __device_attach(struct device_driver * drv, void * data)
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
__device_attach(struct device_driver * drv, void * data)
struct device * dev = data;
return driver_probe_device(drv, dev); // 这里开始与上面的分析一样
if (!device_is_registered(dev)) // 这里才看明白,为什么上面设置 dev->is_registered = 1
return -ENODEV; // dev->is_registered = 1 表示BUS总线上注册了一个有效的设备
// 同样比较drv和dev的名字name是否一致,如果一致,则匹配,调用真正的probe函数
if (drv->bus->match && !drv->bus->match(dev, drv)) // platform_match
goto done;
.... 与前面一样分析
if (ret >= 0)
// 当device_attach(dev)绑定成功,则将dev挂接到bus的dev链表中
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
else
dev->is_registered = 0; // 看这里,才知道只有当device_attach(dev)成功,则置位,表示成功注册一个有效设备
// 如果没有找到相应的驱动程序,则设置该设备没有注册成功
// 为什么要这么做: 猜想: 这样的话,如果有新的驱动加载进来,这个设备还是有权利可以匹配驱动啊
}
2.2 问题:
问题1:bus->drivers_autoprobe 在哪里设置的呢?并且设置为真?
回答1:
内核启动,会运行函数:\drivers\base\platform.c
int __init platform_bus_init(void)
device_register(&platform_bus);
device_add(dev);
bus_register(&platform_bus_type);
bus->drivers_autoprobe = 1; // 在这里设置为1, 可以测试一下
问题2: 总线是什么时候注册的呢?有什么总线呢?
回答2:linux启动的时候,就注册了很多总线,比如SPI,IIC,Platform等总线
2.3 platform总线驱动设备流程总结:
注意:下面讨论的是,当字符设备利用platform总线的方式进行注册驱动;当然块设备也有可能利用platform总线的方式。
在字符设备驱动中,需要操作函数来open, read, write设备文件,那么问题是:
a. 设备文件什么时候创建的?
b. file_operations在哪里定义的?
在内核中,找个例子,就可以看到流程:
drivers\char\watchdog\advantechwdt.c
static int __init advwdt_init(void) // 内核启动会初始化的函数
platform_driver_register(&advwdt_driver); // 注册平台驱动
// 如果有匹配的平台设备注册进内核,就会调用platform_driver下定义的probe函数,也就是:advwdt_driver.probe
advwdt_probe(struct platform_device *dev)
misc_register(&advwdt_miscdev); // 混杂设备注册,在advwdt_miscdev定义了操作函数
dev = MKDEV(MISC_MAJOR, misc->minor);
// 创建设备节点/dev/misc->name
misc->this_device = device_create(misc_class, misc->parent, dev, "%s", misc->name);
所以:当设备和驱动匹配,在调用platform_driver的probe函数期间,创建设备节点,并且绑定操作函数,类似地提供file_operations结构体:
里面提供对设备文件的操作函数,比如:open, read, write等。
3. 驱动的编写
一个led的点灯驱动程序,利用平台总线的方式编写驱动:
分为两个部分:
led_drv: 这是内核做好的,不需要改变,主要是提供probe函数,创建设备节点以及提供设备文件的操作函数;
led_dev: 这是需要程序员根据自己的硬件资源进行修改的部分。
在 led_dev 中定义资源:
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050, // 寄存器的起始地址
.end = 0x56000050 + 8 - 1, // 寄存器的结束地址
.flags = IORESOURCE_MEM, // 内存资源
},
[1] = {
.start = 5, // 定义中断的管脚
.end = 5,
.flags = IORESOURCE_IRQ, // 中断资源
}
};
在 led_drv 中定义probe函数,进行设备资源的获取,并创建设备节点:
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根据platform_device的资源进行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start;
/* 注册字符设备驱动程序 */
printk("led_probe, found led\n");
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
return 0;
}
总结:
驱动部分不需要修改,仅仅只是需要修改device部分,device不一样,你所定义的资源就不一样。
但是驱动可以保持一致,这就是内核分离结构的魅力。
10th_drv:
lcd驱动程序,这个在linux内核有个专业的名字:framebuffer驱动程序
注意:又是分层结构,核心层:\drivers\video\fbmem.c,这里面定义了注册和卸载framebuffer的函数,
驱动层可以调用核心层 fbmem.c 中的相关函数,进行framebuffer的注册,设置,卸载,或者获取硬件(LCD显示屏)的参数(比如分辨率,坐标值)等操作。
既然有核心层,那肯定分为两部分,
一部分为软件层,内核定义好的,不需要程序员去修改,固定不变的;
一部分为硬件相关层,程序员需要根据自己的硬件进行修改,自己构造framebuffer中需要的结构体,设置,然后向内核注册LCD设备。
知识点总结:
1. 核心层fbmem.c分析:
1.1 fb设备的入口与出口函数
fb设备: 也是字符设备,
初始化入口函数:
static int __init fbmem_init(void)
// 注册字符设备,主设备号固定为: FB_MAJOR = 29;
register_chrdev(FB_MAJOR,"fb",&fb_fops)
// 创建fb的一个类,注意这里还没有创建设备文件,
// 猜想:应该是等到向内核注册一个framebuffer设备并成功以后,才会创建与该设备对应的设备文件,例如/dev/fbxx
fb_class = class_create(THIS_MODULE, "graphics");
卸载出口函数:
static void __exit fbmem_exit(void)
// 卸载类设备
class_destroy(fb_class);
// 注销fb字符设备
unregister_chrdev(FB_MAJOR, "fb");
1.2 framebuffer的注册
int register_framebuffer(struct fb_info *fb_info)
// 记录fb注册的个数
num_registered_fb++
// 找到空闲的registered_fb,后面用来存储新注册的framebuffer结构体
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
// 记录fb_info处于的位置
fb_info->node = i;
// 这里注册class下面的device,创建设备文件/dev/fbxxx,class前面内核启动的时候已经创建好了
fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), "fb%d", i);
// ....设置函数
// 把fb_info注册进内核,放入到registered_fb[]数组中;
registered_fb[i] = fb_info;
1.3 framebuffer的卸载
int unregister_framebuffer(struct fb_info *fb_info)
i = fb_info->node; // 找到fb_info节点的位置
registered_fb[i]=NULL; // 注销数组
num_registered_fb--; // fb_info计数减一
device_destroy(fb_class, MKDEV(FB_MAJOR, i)); // 注销设备节点
1.4 fb操作函数分析
问题:framebuffer如何打开,读写设备文件呢?
回答:在入口函数中,注册字符设备: register_chrdev(FB_MAJOR,"fb",&fb_fops) 的时候,对应了一个file_operations结构体fb_fops
由前面的学习知道,在这个fb_fops中定义了该设备文件的各种操作函数,比如open, read, write, poll等。
当然,也可以由用户自己定义设备的操作函数,然后注册进内核。这相当于方法的重载。见下面更详细的说明。
----------------------------------------|
app: open, read, write | 应用层打开或者读写framebuffer文件:open("/dev/fb0...", O_RDWR)
----------------------------------------|
|
kernel: fb_open, fb_read, fb_write | fbmem.c
|
----------------------------------------|
driver: LCD硬件 |
----------------------------------------|
1.4.1 fb_open:
static int fb_open(struct inode *inode, struct file *file)
// 获取次设备号
int fbidx = iminor(inode);
struct fb_info *info;
// 获得在数组中保存的fb_info结构体
info = registered_fb[fbidx]
// 如果新注册的fb_info中的fbops定义了fb_open函数,则调用该open函数。
// 看看,这里又是如果重载了该open的方法,则调用重载的方法进行操作 [在内核里,面向对象的思想编程很多啊]
// 也就是说: 在framebuffer核心层fbmem.c文件中定义了一套fb的操作方法,比如open, read, write;
// 但是用户也可以在自己定义的fb_info结构体中构造自己的另一套fb的操作方法(也就是方法重载),然后注册进内核,以后的操作就会用用户定义的重载方法
if (info->fbops->fb_open)
res = info->fbops->fb_open(info,1);
1.4.2 fb_read
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
// 获得fb_info结构体
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
// 又是该死的方法重载:如果用户自己定义了fb_read方法,则用用户定义的
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
// 下面的是不是很熟悉啊.....
copy_to_user(buf, buffer, c)
1.4.3 fb_write
static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info->fbops->fb_write)
return info->fbops->fb_write(info, buf, count, ppos);
copy_from_user(src, buf, c)
和fb_read一样,没有好分析的....
1.5 重要的数据结构体
struct fb_info {
int node; // 节点的位置
int flags;
struct fb_var_screeninfo var; /* Current var 可变参数*/
struct fb_fix_screeninfo fix; /* Current fix 固定参数*/
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */
struct fb_ops *fbops; // 类似于 file_operations结构体,定义了fb的操作函数
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
char __iomem *screen_base; /* Virtual address 虚拟地址*/
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 虚拟地址的大小*/
void *pseudo_palette; /* Fake palette of 16 colors 调色板 */
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
};
// 可变参数
struct fb_var_screeninfo {
__u32 xres; /* visible resolution 屏幕x轴的分辨率*/
__u32 yres; /* 屏幕y轴的分辨率*/
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what 每一个像素点的位数: 如果是565,则每一个pixel是16bits */
__u32 grayscale; /* != 0 Graylevels instead of colors */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
};
// 固定参数
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem 显存的起始地址,这是物理地址*/
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */
};
2. 流程总结:
2.1 内核启动后,会初始化注册一个字符设备:主设备号为:FB_MAJOR,对应的fops为fb_fops:register_chrdev(FB_MAJOR,"fb",&fb_fops)
在fb_fops中,定义了操作函数,比如open, read, write等函数;
这是内核定义好的软件部分,不需要程序员去修改的。
2.2 当程序员自己注册framebuffer设备到内核中,首先的定义一个fb_info结构体,并设置它包含的参数:
- 可变参数
- 固定参数
- 该设备文件的操作函数的结构体:fb_fops
- ....
然后,调用核心层的register_framebuffer(struct fb_info *fb_info)向内核注册;
在该函数中,创建设备对应的设备文件,比如/dev/fb0.
2.3 对于这个设备文件/dev/fb0,主设备号为: FB_MAJOR, 次设备号为分配的: minor = 0
其中主设备号对应的fops为内核自己注册定义好的:fb_fops;
而用户注册的fops可以通过次设备作为下标,找到对应的数组元素:fb_info[minor], 该数组元素中就定义了该设备的fops。
如果它重载了内核自己定义的fb_fops中的操作方法,则调用重载后的方法。
驱动编写:
1. 分配一个fb_info结构体: framebuffer_alloc
2. 设置:就是对fb_info结构体的填充
3. 注册: register_framebuffer
4. 硬件相关的操作
具体实现:
// LCD初始化函数:
static int lcd_init(void)
{
/* 1. 分配一个fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);
/* 2. 设置 */
/* 2.1 设置固定的参数 */
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = 240*320*16/8;
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */
s3c_lcd->fix.line_length = 240*2;
/* 2.2 设置可变的参数 */
s3c_lcd->var.xres = 240;
s3c_lcd->var.yres = 320;
s3c_lcd->var.xres_virtual = 240;
s3c_lcd->var.yres_virtual = 320;
s3c_lcd->var.bits_per_pixel = 16;
/* RGB:565 */
s3c_lcd->var.red.offset = 11;
s3c_lcd->var.red.length = 5;
s3c_lcd->var.green.offset = 5;
s3c_lcd->var.green.length = 6;
s3c_lcd->var.blue.offset = 0;
s3c_lcd->var.blue.length = 5;
s3c_lcd->var.activate = FB_ACTIVATE_NOW;
/* 2.3 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 其他的设置 */
s3c_lcd->pseudo_palette = pseudo_palette;
//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 240*324*16/8;
/* 3. 硬件相关的操作 */
/* 3.1 配置GPIO用于LCD */
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
*gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
*gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */
*gpbcon &= ~(3); /* GPB0设置为输出引脚 */
*gpbcon |= 1;
*gpbdat &= ~1; /* 输出低电平 */
*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
* 10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
* CLKVAL = 4
* bit[6:5]: 0b11, TFT LCD
* bit[4:1]: 0b1100, 16 bpp for TFT
* bit[0] : 0 = Disable the video output and the LCD control signal.
*/
lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);
/* 垂直方向的时间参数
* bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 T0-T2-T1=4
* VBPD=3
* bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
* bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
* LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
* bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
*/
lcd_regs->lcdcon2 = (3<<24) | (319<<14) | (1<<6) | (0<<0);
/* 水平方向的时间参数
* bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 T6-T7-T8=17
* HBPD=16
* bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
* bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
* LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
*/
lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);
/* 水平方向的同步信号
* bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
*/
lcd_regs->lcdcon4 = 4;
/* 信号的极性
* bit[11]: 1=565 format
* bit[10]: 0 = The video data is fetched at VCLK falling edge
* bit[9] : 1 = HSYNC信号要反转,即低电平有效
* bit[8] : 1 = VSYNC信号要反转,即低电平有效
* bit[6] : 0 = VDEN不用反转
* bit[3] : 0 = PWREN输出0
* bit[1] : 0 = BSWP
* bit[0] : 1 = HWSWP 2440手册P413
*/
lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, (dma_addr_t *)&s3c_lcd->fix.smem_start, GFP_KERNEL);
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
lcd_regs->lcdsaddr3 = (240*16/16); /* 一行的长度(单位: 2字节) */
//s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 */
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
/* 4. 注册 */
register_framebuffer(s3c_lcd);
return 0;
}
// LCD卸载函数:
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
*gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}
11th_ts
该篇主要是移植触摸屏的驱动程序:触摸屏也是一个输入子系统结构体,当注册进内核,生成的设备文件就是/dev/event...
可以参考前面的输入子系统讲的知识点。
对于触摸屏,无非就是触摸操作,类似于按键按下,抬起等等。
对于硬件驱动来讲,差别在于它的实现方式与按键不一样而已,对内核来说都一样,都属于输入子系统那一套东西。内核归纳的好啊!!!
移植ts_lib库,就是可以执行应用程序,屏蔽硬件层,直接看到触摸到哪个位置,方便测试。
----
驱动程序自行分析
12th_iic
iic驱动程序的移植. 重点啊.
知识点总结:
1. IIC驱动流程分析:
核心层: \drivers\i2c\i2c-core.c
内核又是分离结构,bus_type为: i2c_bus_type,内核启动后,首先自己会初始化该总线,注册到内核中。
类似于:platform_bus,但是又不同。
1.1 初始化入口函数:
static int __init i2c_init(void)
// 注册总线: i2c_bus_type
bus_register(&i2c_bus_type);
// 注册一个类
class_register(&i2c_adapter_class);
1.2 卸载出口函数
static void __exit i2c_exit(void)
class_unregister(&i2c_adapter_class);
bus_unregister(&i2c_bus_type);
猜想:IIC设备驱动,也是分离为为drv和dev两部分,分别挂接到IIC_bus上的drv和dev链表上。一边是内核定义的不变部分,一边是与硬件相关的,需要
程序员修改的部分。
1.3 流程分析:
1.3.1 重要数据结构
/*
* i2c_adapter is the structure used to identify a physical i2c bus along
* with the access algorithms necessary to access it.
*/
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class;
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* --- administration stuff. */
int (*client_register)(struct i2c_client *);
int (*client_unregister)(struct i2c_client *);
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
struct mutex clist_lock;
int timeout;
int retries;
struct device dev; /* the adapter device */
int nr;
struct list_head clients;
struct list_head list;
char name[48];
struct completion dev_released;
};
struct i2c_driver {
int id;
unsigned int class;
/* Notifies the driver that a new bus has appeared. This routine
* can be used by the driver to test if the bus meets its conditions
* & seek for the presence of the chip(s) it supports. If found, it
* registers the client(s) that are on the bus to the i2c admin. via
* i2c_attach_client. (LEGACY I2C DRIVERS ONLY)
*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/* tells the driver that a client is about to be deleted & gives it
* the chance to remove its private data. Also, if the client struct
* has been dynamically allocated by the driver in the function above,
* it must be freed here. (LEGACY I2C DRIVERS ONLY)
*/
int (*detach_client)(struct i2c_client *);
/* Standard driver model interfaces, for "new style" i2c drivers.
* With the driver model, device enumeration is NEVER done by drivers;
* it's done by infrastructure. (NEW STYLE DRIVERS ONLY)
*/
int (*probe)(struct i2c_client *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);
struct device_driver driver;
struct list_head list;
};
struct i2c_driver里面包含了一个struct device_driver driver结构体,所以说struct i2c_driver是继承struct device_driver driver的结构体。
前面讲到过: platform_driver结构体,同样也是继承自struct device_driver driver。
注意:在struct device_driver driver包含 device 和 driver 挂接的总线类型 bus_type。
1.3.2 IIC设备驱动流程分析
在内核代码中,定义了很多struct i2c_driver 结构体变量,举例说明:\drivers\rtc\rtc-ds1307.c
static struct i2c_driver ds1307_driver = {
.driver = {
.name = "ds1307",
.owner = THIS_MODULE,
},
.attach_adapter = ds1307_attach_adapter,
.detach_client = __devexit_p(ds1307_detach_client),
};
IIC总线类型:
struct bus_type i2c_bus_type = {
.name = "i2c",
.dev_attrs = i2c_dev_attrs,
.match = i2c_device_match,
.uevent = i2c_device_uevent,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
i2c驱动注册流程:\drivers\rtc\rtc-ds1307.c为例子
static int __init ds1307_init(void)
i2c_add_driver(&ds1307_driver);
i2c_register_driver(THIS_MODULE, ds1307_driver);
driver->driver.owner = THIS_MODULE;
driver->driver.bus = &i2c_bus_type; // 设置bus_type = i2c_bus_type;
driver_register(&driver->driver); // 注册struct device_driver,前面platfrom_driver讲到过
问题:i2c_bus_type总线上的device是在哪里注册的?
// 添加i2c_driver.driver到总线 i2c_bus_type 上, 并遍历iic总线上device链表,找到匹配的device进行绑定,但是并不probe设备
bus_add_driver(struct device_driver driver);
struct bus_type * bus = get_bus(drv->bus); // bus = i2c_bus_type
if (drv->bus->drivers_autoprobe) // 这个应该是在IIC BUS注册的时候设置过了,参考platform_bus注册
error = driver_attach(drv); // 进行遍历dev,并且匹配绑定过程
// 从drv的BUS,也即i2c_bus_type的device链表上取出device,一一和driver进行比较
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach(struct device * dev, void * data)
driver_probe_device(drv, dev);
drv->bus->match(dev, drv) // 这里: drv->bus->match = i2c_bus_type.match = i2c_device_match
i2c_device_match(struct device *dev, struct device_driver *drv)
struct i2c_client *client = to_i2c_client(dev);
struct i2c_driver *driver = to_i2c_driver(drv)
// 比较client->driver_name和drv->name是否一致,相同则找到对应的设备
// 注意:这里drv->name在全局变量ds1307_driver中已定义 = "ds1307"
strcmp(client->driver_name, drv->name) == 0;
really_probe(struct device *dev, struct device_driver *drv)
dev->driver = drv; // dev->driver = ds1307_driver.driver
if (dev->bus->probe) { // 注意:这里dev->bus = i2c_bus_type
ret = dev->bus->probe(dev); // = i2c_device_probe
i2c_device_probe(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct i2c_driver *driver = to_i2c_driver(dev->driver); //dev->driver = i2c_driver.driver
问题:driver->probe(client)这个函数不执行,那么问:IIC驱动在哪里probe设备呢?
driver->probe(client); // 这里: driver->probe = ds1307_driver.probe, 并没有定义,为空
}
//如果上面的操作成功, 则将该i2c_driver.driver放入i2c_bus_type的drivers链表中
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
// 将这个i2c_driver挂接到drivers链表上
list_add_tail(&driver->list,&drivers);
/* legacy drivers scan i2c busses directly */
if (driver->attach_adapter) { // 前面定义了这个函数:ds1307_attach_adapter
struct i2c_adapter *adapter;
// 遍历adapters链表,一个一个拿出来,调用i2c_driver的attach_adapter函数
问题:adapters链表上注册了什么呢?有多少adapter挂接到了adapters链表上了?
list_for_each_entry(adapter, &adapters, list) {
driver->attach_adapter(adapter);
// 举例子: ds1307_driver.attach_adapter = ds1307_attach_adapter,
ds1307_attach_adapter(struct i2c_adapter *adapter)
// 终于看到probe函数了,在基类的 dev->bus->probe = i2c_bus_type.probe 和drv->probe = i2c_driver.probe 中,并没有真正的probe设备;
// 在这里才真正probe,继续看.......
i2c_probe(adapter, &addr_data, ds1307_detect);
i2c_probe_address(adapter, address_data->normal_i2c[i], -1, found_proc);
i2c_probe_address(adapter, 0x50, -1, ds1307_detect);
i2c_smbus_xfer(adapter, addr, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
if (adapter->algo->smbus_xfer) // 没有定义,不执行下面的语句
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write, command,size,data);
else
i2c_smbus_xfer_emulated(adapter,addr,flags,read_write, command,size,data);
i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
ret = adap->algo->master_xfer(adap,msgs,num); // 调用adapter->algo中定义的主机传输函数: s3c24xx_i2c_xfer
// 调用回调函数
found_proc(adapter, addr, kind);
ds1307_detect(struct i2c_adapter *adapter, int address, -1)
struct ds1307 *ds1307;
struct i2c_client *client;
ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL)
// 设置client
client = &ds1307->client;
client->addr = address;
client->adapter = adapter;
client->driver = &ds1307_driver;
client->flags = 0;
// 设置消息
ds1307->msg[0].addr = client->addr;
ds1307->msg[0].flags = 0;
ds1307->msg[0].len = 1;
ds1307->msg[0].buf = &ds1307->reg_addr;
ds1307->msg[1].addr = client->addr;
ds1307->msg[1].flags = I2C_M_RD;
ds1307->msg[1].len = sizeof(ds1307->regs);
ds1307->msg[1].buf = ds1307->regs;
// 设置类型
ds1307->type = ds_1307;
// 读check
i2c_transfer(client->adapter, ds1307->msg, 2)
strlcpy(client->name, "ds1307", I2C_NAME_SIZE);
i2c_attach_client(struct i2c_client *client)
struct i2c_adapter *adapter = client->adapter;
// 把新注册的client挂接到该adpter中的clinets链表中去
list_add_tail(&client->list,&adapter->clients);
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type; // client挂接在i2c_bus_type上面
if (client->driver) // client->driver = &ds1307_driver
// client->dev.driver = &client->driver->driver = &ds1307_driver.driver
client->dev.driver = &client->driver->driver;
client->dev.release = i2c_client_dev_release
device_register(&client->dev);
bus_attach_device(struct device * dev)
device_attach(struct device * dev)
if (dev->driver) // 这里dev->driver =client->dev.driver = &client->driver->driver = &ds1307_driver.driver
ret = device_bind_driver(dev); // 由于driver已知,所以直接绑定,注意和platform的区别
adapter->client_register(client)
}
}
问题1:adapters链表上挂接了什么?由谁挂载的?
回答1: 当向内核注册adapter时,就会将该adapter挂接到adapters链表上
i2c_add_adapter(struct i2c_adapter *adapter)
adapter->nr = id; // 获取adapter的id号
i2c_register_adapter(adapter); // 注册adapter
struct i2c_driver *driver;
list_add_tail(&adap->list, &adapters); // 将注册的adapter挂接到adapters链表
adap->dev.parent = &platform_bus;
// 设置adapter->device.
sprintf(adap->dev.bus_id, "i2c-%d", adap->nr); // 设置名字为: i2c-0, i2c-1......等等
adap->dev.release = &i2c_adapter_dev_release; // 设置release函数
adap->dev.class = &i2c_adapter_class; // 设置类
---------------------------------// 设备如何挂接到IIC bus上去呢?
res = device_register(&adap->dev); // 注册adapter下的device
device_add(struct device *dev)
bus_attach_device(struct device * dev)
问题:
struct bus_type *bus = dev->bus; // 这个dev->bus在哪里设置的呢?分析不下去了。
-------------------------------------
/* let legacy drivers scan this bus for matching devices */
list_for_each(item,&drivers) {
// 遍历i2c_driver链表中的每一个driver,调用driver->attach_adapter(adap);
driver = list_entry(item, struct i2c_driver, list);
if (driver->attach_adapter)
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);
//同样:在这里才真正的probe IIC设备
}
问题2: 注册的adapter中的struct device实体的BUS_TYPE挂接在什么上了?
回答2: 当注册 i2c_adapter 的时候, 先probe是否有IIC设备,如果有,则继续执行: 构造i2c_client结构体,
设置它,然后将 i2c_client.device 注册到总线 i2c_bus_type 上。
问题3:IIC总线 i2c_bus_type 上的probe函数:i2c_device_probe,在struct i2c_driver中的diver->probe函数为空,
那怎么probe i2c设备呢?
回答3: 在driver->attach_adapter(adapter)中才真正的probe i2c设备
问题4:struct i2c_driver xxx_driver结构体中的xxx_driver.driver挂接到 i2c_bus_type 的driver链表上了,
那么 i2c_bus_type 上的设备链表是什么时候挂接的?
回答4:在内核中搜索:i2c_bus_type,肯定能找到设备挂接的代码。
问题5: 对于不同的开发板,会有不同的IIC控制器,如果开发板设置为IIC的主设备,那么主设备如何来发现IIC从设备呢?
回答5: 注册的adapter为: s3c24xx_i2c.adapter
\drivers\i2c\busses\i2c-s3c2410.c
初始化注册的adapter为:
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
当注册i2c_driver到内核中,都会遍历adapters链表上的adapter,然后调用 i2c_driver->attach_adapter(adapter) 函数;
而所有的i2c_driver.attach_adapter函数都会调用同一个函数接口:i2c_probe
int i2c_probe(struct i2c_adapter *adapter, struct i2c_client_address_data *address_data, int (*found_proc) (struct i2c_adapter *, int, int))
举例:\drivers\rtc\rtc-ds1307.c
ds1307_attach_adapter(struct i2c_adapter *adapter)
i2c_probe(adapter, &addr_data, ds1307_detect);
i2c_probe_address(adapter, 设备地址, -1, ds1307_detect);
// 分为两步走:
// 1. 先发一个写命令到IIC从设备,查询是否收到ACK信号,一旦ack信号收到,表示该从设备存在
i2c_smbus_xfer(adapter, 设备地址, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
// adapter->algo->smbus_xfer = s3c24xx_i2c.adap->algo->smbus_xfer = NULL
// 所以if里面的语句不执行
if (adapter->algo->smbus_xfer) {
mutex_lock(&adapter->bus_lock);
res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
command,size,data);
mutex_unlock(&adapter->bus_lock);
} else
// 执行这句代码
res = i2c_smbus_xfer_emulated(adapter, addr, flags,read_write,command, size, data);
// 这里 read_write = 0, 表示为"写操作" 参考头文件定义:#define I2C_SMBUS_WRITE 0
i2c_smbus_xfer_emulated(adapter, 设备地址, 0, 0, 0, I2C_SMBUS_QUICK, NULL)
// 构造消息
struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
{ addr, flags | I2C_M_RD, 0, msgbuf1 };
case I2C_SMBUS_QUICK:
msg[0].len = 0;
// 这里为写操作
msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
num = 1;
break;
i2c_transfer(adapter, msg, num);
adap->algo->master_xfer(adap,msgs,num);
s3c24xx_i2c_xfer(adap,msgs,num); // 在msgs里面保存有probe从设备的地址
// 从adapter获得i2c控制器设备,就是从这个设备发出IIC信号的;
// 前面注册adapter的时候,就是将struct s3c24xx_i2c保存在adapter的变量里的
struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
for (retry = 0; retry < adap->retries; retry++) // adap->retries = 2, 显示设置好了
s3c24xx_i2c_doxfer(i2c, msgs, num); // 真正地到底层,发起IIC信号了
s3c24xx_i2c_set_master(i2c); // 设置为主机设备模式
// 设置
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
s3c24xx_i2c_enable_irq(i2c); // 开启iic中断
s3c24xx_i2c_message_start(i2c, msgs); // 启动传输信息
// 进入休眠态,等待传输信息完成
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
// 问题:谁来唤醒i2c->wait呢?
回答:猜想,应该是当IIC bus上的msg传输完成,就会来唤醒该进程
s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret)
wake_up(&i2c->wait); // 唤醒等待队列
// 2. 如果第1步成功,表示IIC从设备存在
// 调用用户定义的detect函数:构造一个client结构体,将client绑定到i2c_bus_type,
err = found_proc(adapter, addr, kind);
ds1307_detect(struct i2c_adapter *adapter, int address, int kind)
ds1307_detect(adapter, 设备地址, -1)
struct ds1307 *ds1307; // 定义一个 ds1307
struct i2c_client *client; // 定义一个 i2c_client
// 分配并设置ds1307, client
ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL)
client = &ds1307->client;
client->addr = 设备地址; // 设备地址
client->adapter = adapter; // 一边是 i2c_adapter
client->driver = &ds1307_driver; // 一边是 i2c_driver
client->flags = 0;
client->dev = (struct dev *)ds1307;
// 设置消息
ds1307->msg[0].addr = client->addr; // client->addr = 设备地址
ds1307->msg[0].flags = 0; // 表示"写IIC从设备操作"
ds1307->msg[0].len = 1; // 表示消息长度为 1
ds1307->msg[0].buf = &ds1307->reg_addr; // 表示写操作的buffer为:ds1307->reg_addr
ds1307->msg[1].addr = client->addr; // client->addr = 设备地址
ds1307->msg[1].flags = I2C_M_RD; // 表示"读IIC从设备操作"
ds1307->msg[1].len = sizeof(ds1307->regs); // 表示消息长度为 sizeof(ds1307->regs)
ds1307->msg[1].buf = ds1307->regs; // 表示写操作的buffer为:ds1307->regs
i2c_transfer(client->adapter, ds1307->msg, 2);
// 设置client名字
strlcpy(client->name, "ds1307", I2C_NAME_SIZE);
// 将client绑定attach到 i2c_bus_type
i2c_attach_client(client)
struct i2c_adapter *adapter = client->adapter;
list_add_tail(&client->list,&adapter->clients);
// client进一步设置
client->usage_count = 0;
// 开始设置 client->dev
client->dev.parent = &client->adapter->dev;
// 注意:看到这里是不是明白了,client下面的device挂接到 i2c_bus_type 上
// 前面一直在找 i2c_bus_type 的device是什么时候挂接的?
client->dev.bus = &i2c_bus_type;
// 进一步设置,很明显:设置client下面的device下的driver = client->dev->driver = ds1307_driver.driver
// 前面讲到:在注册ds1307_driver的时候,是将ds1307_driver.driver挂接到IIC总线 i2c_bus_type
client->dev.driver = &client->driver->driver;
client->dev.release = i2c_client_release;
client->dev.uevent_suppress = 1;
snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id), "%d-%04x", i2c_adapter_id(adapter), client->addr);
// 注册 client->dev
device_register(&client->dev) // 匹配之前注册i2c_driver时构造的 i2c_driver.driver,挂接在 i2c_bus_type 下
// 注册client到内核中,作为字符设备
ds1307->rtc = rtc_device_register(client->name, &client->dev, &ds13xx_rtc_ops, THIS_MODULE);
struct rtc_device *rtc;
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
rtc->id = id;
rtc->ops = ops; // ops = ds13xx_rtc_ops
rtc->owner = owner;
rtc->max_user_freq = 64;
rtc->dev.parent = dev; // rtc->dev.parent = dev = client->dev
rtc->dev.class = rtc_class; // 注册到rtc_class
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); // rtc->name = “ds1307”
snprintf(rtc->dev.bus_id, BUS_ID_SIZE, "rtc%d", id);
rtc_dev_prepare(rtc);
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); // 构造devt(major, minor)
cdev_init(&rtc->char_dev, &rtc_dev_fops); // 初始化字符设备,绑定主设备号和rtc_dev的fops
// 注册rtc下的struct device结构体到内核中
device_register(&rtc->dev);
// 注册rtc下面的字符设备到内核中
cdev_add(&rtc->char_dev, rtc->dev.devt, 1)
// rtc类的操作函数
static const struct rtc_class_ops ds13xx_rtc_ops = {
.read_time = ds1307_get_time,
.set_time = ds1307_set_time,
};
// rtc设备操作函数(向内核注册了字符设备)
static const struct file_operations rtc_dev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = rtc_dev_read,
.poll = rtc_dev_poll,
.ioctl = rtc_dev_ioctl,
.open = rtc_dev_open,
.release = rtc_dev_release,
.fasync = rtc_dev_fasync,
};
1.3.3 IIC设备驱动框架
app: open, read, write
------------------------------------------------
驱动:
drv_open, drv_read, drv_write drivers/i2c/chip, iic设备驱动:知道数据的含义(针对具体的芯片)
----------------------------------------
i2c总线驱动程序 drivers/i2c/buses, iic总线驱动程序:1. 识别IIC从设备; 2. 提供读写函数,知道如何收发数据
------------------------------------------------
硬件: AT24C02, AT24C08
IIC驱动程序理解:
硬件部分:
i2c驱动里面,构造了 i2c_adapter 这个结构体,看源代码知道,adapter就对应特定芯片的一个IIC控制器,对应的是硬件部分。
所以,adapter结构体里面定义了与IIC传输协议相关的函数,比如IIC信号如何传输。
而client则相当于该"adapter(插槽)"下挂接的"IIC从设备",所以client保存有变量"adapter"和i2c_driver,而且内核最终将利用它
来对IIC设备进行数据的读写等操作。
对于S3C2440开发板,内核启动时,会首先注册一个adapter到内核,就是说先注册一个"adapter(插槽)",并将adapter挂接到adapters(IIC驱动相关)链表上
不过内核将它封装成一个平台设备结构体,进行注册,见文件:drivers\i2c\busses\i2c-s3c2410.c
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
在 s3c24xx_i2c.adap.algo = s3c24xx_i2c_algorithm 中就定义了IIC传输函数:s3c24xx_i2c_xfer
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
函数 s3c24xx_i2c_xfer 就是最底层的IIC传输函数了。
软件部分:
软件部分构造了 i2c_driver 结构体,里面定义了 attach_adapter, detach_adapter, detach_client,当注册i2c_driver结构体时,会做如下工作:
1. 把 i2c_driver.driver 挂接到 i2c_bus_type 上,也就相当于把i2c_driver挂接到 i2c_bus_type 上
2. 取出adapters链表中的每个adapter结构体,调用 i2c_driver.attach_adapter 函数,来探测IIC设备是否真的存在。
3. 在i2c_driver.attach_adapter("adapter")函数中,会做如下工作:
3.0 首先会调用统一的函数接口:i2c_probe(adapter, 设备地址, IIC设备真正的probe函数)来探测IIC设备是否真的存在
如果存在,则会往下继续做:
3.1 构造一个 i2c_client 结构体,设置它:
a. 保存IIC设备地址;
b. 一边挂接"刚注册的i2c_driver",一边挂接"探测到iic设备的adapter";
3.2 注册 i2c_client.dev 到总线 i2c_bus_type 上,和前面注册的 i2c_driver.driver 绑定到一起,都挂接在 i2c_bus_type 总线上。
这样的话,i2c_client 和 i2c_driver 都挂接在 i2c_bus_type 总线上。
IIC驱动中相关结构体关系连接图:
-----------------------------------------------------------------------------------------------------------------------------------------
client
. addr = IIC设备地址
. adapter = i2c_adapter --> adapters链表
drivers链表 <---------i2c_driver = . driver . algo
.driver . dev ------------------>| .master_xfer (定义硬件底层IIC传输信号的函数)
|----------->. driver |
| |
|----------> i2c_bus_type <--------|
----------------------------------------------------------------------------------------------------------------------------------------
S3C2440内核IIC设备驱动建立流程:
------------------------------------------------
分析:\drivers\i2c\i2c-dev.c
流程分析:
static int __init i2c_dev_init(void)
// 注册IIC字符设备:主设备号为I2C_MAJOR = 89; 对应的 file_operations = i2cdev_fops;
register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
// 创建一个IIC类:i2c_dev_class
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
-----------------------------------------------------问题:哪里创建设备呢?
// 注册iic driver
i2c_add_driver(&i2cdev_driver);
i2cdev_driver->driver.owner = THIS_MODULE;
i2cdev_driver->driver.bus = &i2c_bus_type;
// 从总线 i2c_bus_type 的device一端取出各个device,和 i2cdev_driver->driver 进行比较是否匹配
driver_register(&i2cdev_driver->driver);
// 调用总线的match函数: i2c_bus_type.match = i2c_device_match
i2c_device_match(struct device *dev, struct device_driver *drv)
struct i2c_client *client = to_i2c_client(dev);
struct i2c_driver *driver = to_i2c_driver(drv);
// 由总线:i2c_bus_type 下的挂接的device找到 i2c_client;
// 比较i2c_client和i2c_driver的名字是否相等
strcmp(client->driver_name, drv->name)
// 如果找到匹配的device,则调用probe函数
really_probe(struct device *dev, struct device_driver *drv)
dev->bus->probe(dev)
i2c_device_probe(struct device *dev)
driver->probe(client)
// 将i2cdev_driver放入drivers链表中
list_add_tail(&i2cdev_driver->list,&drivers);
// 如果定义了i2cdev_driver->attach_adapter函数,则遍历链表adapters下的adapter,
// 取出每个adapter,调用函数:i2cdev_driver->attach_adapter(adapter)
if (i2cdev_driver->attach_adapter) {
struct i2c_adapter *adapter;
list_for_each_entry(adapter, &adapters, list) {
i2cdev_driver->attach_adapter(adapter);
}
}
由前面的分析知道,当注册 i2c_driver 到内核的时候,将遍历adapters链表下的"adapter",然后调用该注册的i2c_driver下的 .attach_adapter 函数,
进行绑定。所以一旦内核有"adapter"存在,将调用函数: i2cdev_driver.attach_adapter = i2cdev_attach_adapter
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
struct i2c_dev *i2c_dev;
i2c_dev = get_free_i2c_dev(adap);
struct i2c_dev *i2c_dev;
i2c_dev = kzalloc(sizeof(*i2c_dev), GFP_KERNEL);
i2c_dev->adap = adap; // 指定adapter
list_add_tail(&i2c_dev->list, &i2c_dev_list); // 将该 i2c_dev 挂接到链表 i2c_dev_list 上面
----------------------------------------------------回答:在这里创建设备
// 在类 i2c_dev_class 下面创建设备,比如“/dev/i2c-0”, “/dev/i2c-1”......
// 主设备号为:I2C_MAJOR = 89,次设备号为:IIC设备的编号,操作函数 file_operations 结构体为:i2cdev_fops;
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), "i2c-%d", adap->nr);
结构体定义:\drivers\i2c\i2c-dev.c
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.id = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
由内核打印信息,追索驱动流程:
----------------------------------------
内核注册流程:
由打印信息:
1. 先注册 i2c_driver: i2cdev_driver --- \drivers\i2c\i2c-dev.c
前面分析过,内核启动初始化会执行函数:i2c_dev_init
i2c_dev_init(void)
i2c_add_driver(&i2cdev_driver);
i2c_register_driver(THIS_MODULE, driver);
driver_register(&driver->driver);
list_add_tail(&driver->list,&drivers); // 这里将 i2cdev_driver 挂接到 drivers 链表上
driver->attach_adapter(adapter);
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = "dev_driver",
},
.id = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
打印信息:
i2c /dev entries driver
i2c_dev_init, i2c_add_driver: i2cdev_driver
-------------------------------------------------
2. 然后注册i2c_adapter: s3c24xx_i2c.adap --- \drivers\i2c\busses\i2c-s3c2410.c
s3c24xx_i2c_probe(struct platform_device *pdev)
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
s3c24xx_i2c_init(i2c); // 初始化IIC控制器
i2c_add_adapter(&i2c->adap);
i2c_register_adapter(adapter);
list_add_tail(&adap->list, &adapters); // 这里将 s3c24xx_i2c.adap 挂接到 adapters 链表上
driver->attach_adapter(adap);
static struct s3c24xx_i2c s3c24xx_i2c = {
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap = {
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,
.retries = 2,
.class = I2C_CLASS_HWMON,
},
};
打印信息:
cnt: 1, i2c_adapter name: s3c2410-i2c
-------------------------------------------
3. 当注册 i2c_adapter: s3c24xx_i2c.adap 的时候,就会遍历drivers链表,把drivers链表中的 i2c_driver 一个一个取出来,然后调用 i2c_driver.attach_adapter(adapt)函数。
因为前面只注册了一个 i2c_driver结构体: i2cdev_driver,所以就将调用函数:i2cdev_driver.attach_adapter(adapter) = i2cdev_attach_adapter(s3c24xx_i2c.adap)
i2cdev_attach_adapter(s3c24xx_i2c.adap)
device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), "i2c-%d", adap->nr);
这个函数前面已经分析了,将会创建设备文件:"/dev/i2c-0"
所以当内核启动后,可以查看是否建立的设备文件:
# ls /dev/i2c* -l
crw-rw---- 1 0 0 89, 0 Jan 1 00:00 /dev/i2c-0
可以看到,主设备号为89,次设备号为0,建立了设备文件: "/dev/i2c-0"
内核打印信息:
cnt: 1, i2cdev_attach_adapter,adap.name:s3c2410-i2c
cnt: 1, i2c_register_driver, i2c_driver->name: dev_driver
--------------------------------------------------------------
如何编写i2c驱动呢?
1. 构造一个i2c_driver结构体
该结构体包括函数:
attach_adapter
detach_client
2. 注册i2c_driver结构体
当向内核注册i2c_driver,将会调用该结构体下面的attach_adapter函数
3. 所以当内核注册i2c_driver,会先探测IIC设备时候存在,如果存在,则调用attach_adapter函数来probe设备,来建立某种联系:
3.1 在attach_adapter函数中,调用统一的函数接口:i2c_probe(adapter, 设备地址, "设备真正的probe函数");
3.2 在"设备真正的probe函数"中,构造一个:struct i2c_client结构体:
该结构体一端连着"adapter",一端连着"i2c_driver",并保存IIC设备地址;最后调用函数 i2c_attach_client 将该client注册到内核的 i2c_bus_type 上。
3.3 运行到这里,表示已经找到IIC设备,并且已经利用client建立了某种联系,就可以创建与IIC设备对应的字符设备文件,这个创建过程和前面讲的建立字符设备驱动一样。
以后IIC设备文件的读写等操作,都是借助于client这个桥梁。
13th_chrdev_another:
讲的是:字符设备注册的另外一种方法。
前面讲的字符设备注册:register_chrdev(0, "hello", &hello_fops);
这将会导致:(major, 0), (major, 1), ..., (major, 255)都对应hello_fops
这一注册,将会导致 minor = 0 - 255 的次设备号都被霸占。
新的方法, 两步走:
1. 找到设备号
主设备号major已知情况:
devid = MKDEV(major, 0);
register_chrdev_region(devid, 2, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
主设备号major由内核自动分配情况:
alloc_chrdev_region(&devid, 0, 2, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
major = MAJOR(devid);
2. 注册字符设备到内核
cdev_init(&hello_cdev, &hello_fops);
memset(cdev, 0, sizeof *cdev); // 初始化
cdev->ops = fops; // 绑定fops到cdev
cdev_add(&hello_cdev, devid, 2); // 真正注册字符设备到内核
14th_sound:
音频设备驱动:重点了.....
知识点总结:
1. 音频驱动流程分析:
举例子:\sound\soc\s3c24xx\s3c2410-uda1341.c
1.1 驱动端
s3c2410_uda1341_init(void)
driver_register(&s3c2410iis_driver);
static struct device_driver s3c2410iis_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type,
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
前面分析很多了,找名字相同的device,调用:drv->bus->match(dev, drv) = s3c2410iis_driver.bus.match = platform_match
platform_match(struct device * dev, struct device_driver * drv)
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
// 比较platform_device的name和driver的name是否一致,
// 如果一致,则调用函数: dev->bus->probe(dev) = 没有定义; 或者:drv->probe(dev) = s3c2410iis_probe(struct device *dev)
strncmp(pdev->name, drv->name, BUS_ID_SIZE)
问题:设备端是如何注册的呢?
回答:继续往下看......
1.2 设备端
定义设备结构体:\arch\arm\plat-s3c24xx\devs.c
struct platform_device s3c_device_iis = {
.name = "s3c2410-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &s3c_device_iis_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
设置并注册设备: \arch\arm\mach-s3c2440\Mach-smdk2440.c
static struct platform_device *smdk2440_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis, // 音频设备
&s3c2440_device_sdi,
};
// 内核启动初始化设备数组
static void __init smdk2440_machine_init(void)
platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices)); // 向内核注册设备
platform_device_register(devs[i]);
platform_device_add(pdev);
// 设置platform_device.dev挂接的总线为:platform_bus_type,和驱动挂接的总线一致
// 为什么要一致?因为待会儿会从挂接的"总线的driver链表"上一个一个取出driver,来和pdev->dev进行匹配
// 只有相同的总线,才有绑定的必要(这是很合理的)
pdev->dev.bus = &platform_bus_type;
device_add(&pdev->dev);
问题:如何匹配绑定呢?
回答:前面讲过了,比较platform_device的name和driver的name是否一致:
因为:s3c_device_iis.name = "s3c2410-iis" = s3c2410iis_driver
所以:两者匹配,调用driver重载的probe函数:s3c2410iis_probe
s3c2410iis_probe(struct device *dev)
// 获取平台设备: pdev = s3c_device_iis
struct platform_device *pdev = to_platform_device(dev);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 使能IIS模块时钟
iis_clock = clk_get(dev, "iis");
clk_enable(iis_clock);
// 配置GPIO管脚为IIS模块
s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB4,1);
......
// 初始化IIS bus
init_s3c2410_iis_bus
// 初始化uda1341模块的控制器寄存器
init_uda1341(void)
/* 设置两个DMA通道:一个用于播放,另一个用于录音 */
output_stream.dma_ch = DMACH_I2S_OUT;
audio_init_dma(&output_stream, "UDA1341 out")
input_stream.dma_ch = DMACH_I2S_IN;
audio_init_dma(&input_stream, "UDA1341 in")
audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
sound_insert_unit(&chains[3], fops, dev, 3, 131, "dsp", S_IWUSR | S_IRUSR, NULL);
struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
sprintf(s->name, "sound/%s", name);
sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
// 注册类sound_class下的设备: "/dev/dsp"
device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6);
audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);
sound_insert_unit(&chains[0], fops, dev, 0, 128, "mixer", S_IRUSR | S_IWUSR, NULL);
struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);
sprintf(s->name, "sound/%s", name);
sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);
// 注册类sound_class下的设备: "/dev/mixer"
device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), s->name+6);
说明:
/dev/dsp: 用于播放/录音
/dev/mixer: 调整音量
static struct file_operations smdk2410_audio_fops = {
llseek: smdk2410_audio_llseek,
write: smdk2410_audio_write,
read: smdk2410_audio_read,
poll: smdk2410_audio_poll,
ioctl: smdk2410_audio_ioctl,
open: smdk2410_audio_open,
release: smdk2410_audio_release
};
static struct file_operations smdk2410_mixer_fops = {
ioctl: smdk2410_mixer_ioctl,
open: smdk2410_mixer_open,
release: smdk2410_mixer_release
};
问题: 类sound_class是在什么时候注册的?
回答: 参考声卡的核心程序:\sound\sound_core.c
static int __init init_soundcore(void)
// 内核启动,初始化注册声卡字符设备,主设备号为:SOUND_MAJOR = 14; 对应的file_operations = soundcore_fops
register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops)
// 创建一个类: sound_class
sound_class = class_create(THIS_MODULE, "sound");
static const struct file_operations soundcore_fops=
{
/* We must have an owner or the module locking fails */
.owner = THIS_MODULE,
.open = soundcore_open,
};
看到soundcore_fops结构体,只定义了:open函数,就知道肯定在open函数中,"移花接木"了。
soundcore_open(struct inode *inode, struct file *file)
int unit = iminor(inode);
const struct file_operations *new_fops = NULL;
s = __look_for_unit(chain, unit);
new_fops = fops_get(s->unit_fops); // 找到真正的设备的open函数
const struct file_operations *old_fops = file->f_op;
file->f_op = new_fops; // 获取真正的open函数
file->f_op->open(inode,file); // 真正的open函数,"移花接木"
2. 应用程序相关操作:
app: open () // 假设主设备号为14
-------------------------------------------
soundcore_open
int unit = iminor(inode);
s = __look_for_unit(chain, unit);
// 从chains数组里得到, 谁来设置这个数组? 肯定是注册相关的字符设备的时候,添加进数组的。
new_fops = fops_get(s->unit_fops);
file->f_op = new_fops;
err = file->f_op->open(inode,file);
录音:
app: read
----------------------
file->f_op->read
播放:
app: write
-------------------------
file->f_op->write
3. 驱动程序的编写:
对于声卡为:uda1341的开发板:
---------------------------
测试:
1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
-> Device Drivers
-> Sound
-> Advanced Linux Sound Architecture
-> Advanced Linux Sound Architecture
-> System on Chip audio support
<*> I2S of the Samsung S3C24XX chips
2. make uImage
使用新内核启动
3. ls -l /dev/dsp /dev/mixer
4. 播放:
在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
cat Windows.wav > /dev/dsp
5. 录音:
cat /dev/dsp > sound.bin
然后对着麦克风说话
ctrl+c退出
cat sound.bin > /dev/dsp // 就可以听到录下的声音
对于声卡为:WM8976的开发板:
---------------------------
怎么写WM8976驱动程序?
1. IIS部分一样,保持不变
2. 控制部分不同,重写
测试WM8976:
1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
-> Device Drivers
-> Sound
-> Advanced Linux Sound Architecture // 兼容OSS
-> Advanced Linux Sound Architecture
-> System on Chip audio support
<*> I2S of the Samsung S3C24XX chips
2. 修改sound/soc/s3c24xx/Makefile
obj-y += s3c2410-uda1341.o
改为:
obj-y += s3c-wm8976.o
3. make uImage
使用新内核启动
4. ls -l /dev/dsp /dev/mixer
5. 播放:
在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
cat Windows.wav > /dev/dsp
6. 录音:
cat /dev/dsp > sound.bin
然后对着麦克风说话
ctrl+c退出
cat sound.bin > /dev/dsp // 就可以听到录下的声音
4. 使用madplay测试声卡:
1. 解压:
tar xzf libid3tag-0.15.1b.tar.gz // 库
tar xzf libmad-0.15.1b.tar.gz // 库
tar xzf madplay-0.15.2b.tar.gz // APP
2. 编译 libid3tag-0.15.1b
mkdir tmp
cd libid3tag-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install
3. 编译 libmad-0.15.1b
cd libmad-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install
4. 编译madplay
cd madplay-0.15.2b/
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp LDFLAGS="-L/work/drivers_and_test/21th_sound/app/tmp/lib" CFLAGS="-I /work/drivers_and_test/21th_sound/app/tmp/include"
make
make install
5. 把tmp/bin/* tmp/lib/*so* 复制到根文件系统:
6. 把一个mp3文件复制到根文件系统
7. madplay --tty-control /1.mp3
播放过程中不断按小键盘的减号("-")会降低音量
不断按小键盘的加号("+")会降低音量
阅读(1388) | 评论(0) | 转发(0) |