上节我们分析设备驱动模型中的device,主要是drivers/base/core.c,可以说是代码量最大的一个文件。本节要分析的驱动driver,就要相对简单很多。原因也很简单,对于driver,我们能定义的公共部分实在不多,能再sysfs中表达的也很少。本节的分析将围绕
drivers/base/driver.c,但头文件仍然是include/linux/device.h和drivers/base/base.h。
先让我们来看看driver的结构。
[cpp]view plaincopyprint?
1.struct device_driver {
2.const char *name;
3.struct bus_type *bus;
4.
5.struct module *owner;
6.const char *mod_name; /* used for built-in modules */
7.
8.bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
9.
10.int (*probe) (struct device *dev);
11.int (*remove) (struct device *dev);
12.void (*shutdown) (struct device *dev);
13.int (*suspend) (struct device *dev, pm_message_t state);
14.int (*resume) (struct device *dev);
15.const struct attribute_group **groups;
16.
17.const struct dev_pm_ops *pm;
18.
19.struct driver_private *p;
20.};
struct device_driver就是模型定义的通用驱动结构。name是驱动名称,但这个name也只是在静态定义的初始名称,实际使用的名称还是由kobject中保管的。bus执行驱动所在的总线,owner是驱动所在的模块,还有一个所在模块名称mod_name,suppress_bind_attrs 定义是否允许驱动通过sysfs决定挂载还是卸载设备。下面是一系列函数指针,probe是在驱动刚与设备挂接时调用的,remove是在设备卸载时调用的,shutdown是在设备关闭时调用的(说实话我现在还不知道remove和shutdown的区别),suspend是设备休眠时调用的,resume是设备恢复时调用的。group是属性集合,pm是电源管理的函数集合,p是指向driver_private的指针。
[cpp]view plaincopyprint?
1.struct driver_private {
2.struct kobject kobj;
3.struct klist klist_devices;
4.struct klist_node knode_bus;
5.struct module_kobject *mkobj;
6.struct device_driver *driver;
7.};
8.#define to_driver(obj) container_of(obj, struct driver_private, kobj)
与device类似,device_driver把与其它组件联系的大部分结构变量移到struct
driver_private中来。首先是kobj,在sysfs中代表driver目录本身。klist_devices是驱动下的设备链表,knode_bus是要挂载在总线的驱动链表上的节点。mkobj是driver与相关module的联系,之前在device_driver结构中已经有指向module的指针,但这还不够,在/sys下你能发现一个module目录,所以驱动所属的模块在sysfs中也有显示,具体留到代码中再看。driver指针自然是从driver_private指回struct device_driver的。
[cpp]view plaincopyprint?
1.struct driver_attribute {
2.struct attribute attr;
3. ssize_t (*show)(struct device_driver *driver, char *buf);
4. ssize_t (*store)(struct device_driver *driver, const char *buf,
5.size_t count);
6.};
7.
8.#define DRIVER_ATTR(_name, _mode, _show, _store) \
9.struct driver_attribute driver_attr_##_name = \
10. __ATTR(_name, _mode, _show, _store)
除了以上两个结构,还有struct driver_attribute。driver_attribute是driver对struct attribute 的封装,添加了两个特用于device_driver的读写函数。这种封装看似简单重复,工作量很小,但在使用时却会造成巨大的便利。
好,结构介绍完毕,下面看driver.c中的实现。
[cpp]view plaincopyprint?
1.static struct device *next_device(struct klist_iter *i)
2.{
3.struct klist_node *n = klist_next(i);
4.struct device *dev = NULL;
5.struct device_private *dev_prv;
6.
7.if (n) {
8. dev_prv = to_device_private_driver(n);
9. dev = dev_prv->device;
10. }
11.return dev;
12.}
13.
14.int driver_for_each_device(struct device_driver *drv, struct device *start,
15.void *data, int (*fn)(struct device *, void *))
16.{
17.struct klist_iter i;
18.struct device *dev;
19.int error = 0;
20.
21.if (!drv)
22.return -EINVAL;
23.
24. klist_iter_init_node(&drv->p->klist_devices, &i,
25. start ? &start->p->knode_driver : NULL);
26.while ((dev = next_device(&i)) && !error)
27. error = fn(dev, data);
28. klist_iter_exit(&i);
29.return error;
30.}
31.struct device *driver_find_device(struct device_driver *drv,
32.struct device *start, void *data,
33.int (*match)(struct device *dev, void *data))
34.{
35.struct klist_iter i;
36.struct device *dev;
37.
38.if (!drv)
39.return NULL;
40.
41. klist_iter_init_node(&drv->p->klist_devices, &i,
42. (start ? &start->p->knode_driver : NULL));
43.while ((dev = next_device(&i)))
44.if (match(dev, data) && get_device(dev))
45.break;
46. klist_iter_exit(&i);
47.return dev;
48.}
driver_for_each_device()是对drv的设备链表中的每个设备调用一次指定函数。
driver_find_device()是在drv的设备链表中寻找一个设备,寻找使用指定的匹配函数。
这两个函数都不陌生,在之前分析device的core.c中已经见到与它们很类似的函数,只不过那里是遍历设备的子设备链表,这里是遍历驱动的设备链表。next_device()同样是辅助用的内部函数。
[cpp]view plaincopyprint?
1.int driver_create_file(struct device_driver *drv,
2.struct driver_attribute *attr)
3.{
4.int error;
5.if (drv)
6. error = sysfs_create_file(&drv->p->kobj, &attr->attr);
7.else
8. error = -EINVAL;
9.return error;
10.}
11.
12.void driver_remove_file(struct device_driver *drv,
13.struct driver_attribute *attr)
14.{
15.if (drv)
16. sysfs_remove_file(&drv->p->kobj, &attr->attr);
17.}
driver_create_file()创建drv下的属性文件,调用sysfs_create_file()实现。
driver_remove_file()删除drv下的属性文件,调用sysfs_remove_file()实现。
[cpp]view plaincopyprint?
1.static int driver_add_groups(struct device_driver *drv,
2.const struct attribute_group **groups)
3.{
4.int error = 0;
5.int i;
6.
7.if (groups) {
8.for (i = 0; groups[i]; i++) {
9. error = sysfs_create_group(&drv->p->kobj, groups[i]);
10.if (error) {
11.while (--i >= 0)
12. sysfs_remove_group(&drv->p->kobj,
13. groups[i]);
14.break;
15. }
16. }
17. }
18.return error;
19.}
20.
21.static void driver_remove_groups(struct device_driver *drv,
22.const struct attribute_group **groups)
23.{
24.int i;
25.
26.if (groups)
27.for (i = 0; groups[i]; i++)
28. sysfs_remove_group(&drv->p->kobj, groups[i]);
29.}
driver_add_groups()在drv目录下添加属性集合,调用sysfs_create_groups()实现。driver_remove_groups()在drv目录下删除属性集合,调用sysfs_remove_groups()实现。发现两点问题:第一,是不是觉得driver_add_groups()不太合适,最好改为
driver_create_groups()才搭调。但不只是driver用driver_add_groups(),device也使用device_add_groups(),不知一处这样做。第二,有没有发现driver_create_file()是外部函数,driver_add_groups()就是内部函数,也就是说driver只对外提供添加属性的接口,却不提供添加属性集合的接口。理由吗?在struct device_driver()已经专门定义了一个groups变量来添加属性集合,后面就不易再重复提供接口,而且创建属性集合需要的操作远比创建属性费时。在device中也是这样做的。
另外,driver中只提供管理属性文件的方法,却不提供管理二进制属性文件的方法,这是因为驱动本身没有这种需求,只有部分设备才要求二进制文件表示。
[cpp]view plaincopyprint?
1.struct device_driver *get_driver(struct device_driver *drv)
2.{
3.if (drv) {
4.struct driver_private *priv;
5.struct kobject *kobj;
6.
7. kobj = kobject_get(&drv->p->kobj);
8. priv = to_driver(kobj);
9.return priv->driver;
10. }
11.return NULL;
12.}
13.
14.void put_driver(struct device_driver *drv)
15.{
16. kobject_put(&drv->p->kobj);
17.}
get_driver()增加drv的引用计数,put_driver()减少drv的引用计数。这都是通过drv->p->kobj 来做的。
[cpp]view plaincopyprint?
1.struct device_driver *driver_find(const char *name, struct bus_type *bus)
2.{
3.struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
4.struct driver_private *priv;
5.
6.if (k) {
7. priv = to_driver(k);
8.return priv->driver;
9. }
10.return NULL;
11.}
driver_find()从bus的驱动链表中寻找特定名称的driver。
[cpp]view plaincopyprint?
1./**
2. * driver_register - register driver with bus
3. * @drv: driver to register
4. *
5. * We pass off most of the work to the bus_add_driver() call,
6. * since most of the things we have to do deal with the bus
7. * structures.
8. */
9.int driver_register(struct device_driver *drv)
10.{
11.int ret;
12.struct device_driver *other;
13.
14. BUG_ON(!drv->bus->p);
15.
16.if ((drv->bus->probe && drv->probe) ||
17. (drv->bus->remove && drv->remove) ||
18. (drv->bus->shutdown && drv->shutdown))
19. printk(KERN_WARNING "Driver '%s' needs updating - please use "
20."bus_type methods\n", drv->name);
21.
22. other = driver_find(drv->name, drv->bus);
23.if (other) {
24. put_driver(other);
25. printk(KERN_ERR "Error: Driver '%s' is already registered, "
26."aborting...\n", drv->name);
27.return -EBUSY;
28. }
29.
30. ret = bus_add_driver(drv);
31.if (ret)
32.return ret;
33. ret = driver_add_groups(drv, drv->groups);
34.if (ret)
35. bus_remove_driver(drv);
36.return ret;
37.}
driver_register()将drv注册到系统中。它真是做得难以预料地简单,所有的工作几乎完全是由bus_add_driver()代为完成的。但你要注意,在调用driver_register()前,drv->bus一定要预先设置。device可以不绑定bus,但driver一定要绑定到bus上。
[cpp]view plaincopyprint?
1.void driver_unregister(struct device_driver *drv)
2.{
3.if (!drv || !drv->p) {
4. WARN(1, "Unexpected driver unregister!\n");
5.return;
6. }
7. driver_remove_groups(drv, drv->groups);
8. bus_remove_driver(drv);
9.}
driver_unregister()将drv从系统中撤销。大部分工作是调用bus_remove_driver()完成的。可以看出bus_add_driver()与bus_remove_driver()相对。driver和bus的联系如此紧密,以至于driver的注册和撤销工作都可以由bus代劳了。我们需要更进一步的分析。
经过调查,我们发现很有一部分driver的代码被移动到了bus.c中。我们本节是以driver 为主,所以接下来会尽量在不惊动bus的情况下,分析存在于bus.c中的driver代码。
[cpp]view plaincopyprint?
1.static ssize_t drv_attr_show(struct kobject *kobj, struct attribute *attr,
2.char *buf)
3.{
4.struct driver_attribute *drv_attr = to_drv_attr(attr);
5.struct driver_private *drv_priv = to_driver(kobj);
6. ssize_t ret = -EIO;
7.
8.if (drv_attr->show)
9. ret = drv_attr->show(drv_priv->driver, buf);
10.return ret;
11.}
12.
13.static ssize_t drv_attr_store(struct kobject *kobj, struct attribute *attr,
15.{
16.struct driver_attribute *drv_attr = to_drv_attr(attr);
17.struct driver_private *drv_priv = to_driver(kobj);
18. ssize_t ret = -EIO;
19.
20.if (drv_attr->store)
21. ret = drv_attr->store(drv_priv->driver, buf, count);
22.return ret;
23.}
24.
25.static struct sysfs_ops driver_sysfs_ops = {
26. .show = drv_attr_show,
27. .store = drv_attr_store,
28.};
看到这里,你终于觉得driver开始正常了,它还要定义sysfs读写时操作的函数。
[cpp]view plaincopyprint?
1.static void driver_release(struct kobject *kobj)
2.{
3.struct driver_private *drv_priv = to_driver(kobj);
4.
5. pr_debug("driver: '%s': %s\n", kobject_name(kobj), __func__);
6. kfree(drv_priv);
7.}
8.
9.static struct kobj_type driver_ktype = {
10. .sysfs_ops = &driver_sysfs_ops,
11. .release = driver_release,
12.};
与device的释放函数device_release不同,driver_release没有提供外界代码运行的机会,只是简单地释放drv_priv函数。
[cpp]view plaincopyprint?
1./* Manually detach a device from its associated driver. */
2.static ssize_t driver_unbind(struct device_driver *drv,
4.{
5.struct bus_type *bus = bus_get(drv->bus);
6.struct device *dev;
7.int err = -ENODEV;
8.
9. dev = bus_find_device_by_name(bus, NULL, buf);
10.if (dev && dev->driver == drv) {
11.if (dev->parent) /* Needed for USB */
12. down(&dev->parent->sem);
13. device_release_driver(dev);
14.if (dev->parent)
15. up(&dev->parent->sem);
16. err = count;
17. }
18. put_device(dev);
19. bus_put(bus);
20.return err;
21.}
22.static DRIVER_ATTR(unbind, S_IWUSR, NULL, driver_unbind);
23.
24./*
25. * Manually attach a device to a driver.
26. * Note: the driver must want to bind to the device,
27. * it is not possible to override the driver's id table.
28. */
29.static ssize_t driver_bind(struct device_driver *drv,
30.const char *buf, size_t count)
31.{
32.struct bus_type *bus = bus_get(drv->bus);
33.struct device *dev;
34.int err = -ENODEV;
35.
36. dev = bus_find_device_by_name(bus, NULL, buf);
37.if (dev && dev->driver == NULL && driver_match_device(drv, dev)) {
38.if (dev->parent) /* Needed for USB */
39. down(&dev->parent->sem);
40. down(&dev->sem);
41. err = driver_probe_device(drv, dev);
42. up(&dev->sem);
43.if (dev->parent)
44. up(&dev->parent->sem);
45.
46.if (err > 0) {
47./* success */
48. err = count;
49. } else if (err == 0) {
50./* driver didn't accept device */
51. err = -ENODEV;
52. }
53. }
54. put_device(dev);
55. bus_put(bus);
56.return err;
57.}
58.static DRIVER_ATTR(bind, S_IWUSR, NULL, driver_bind);
上面描述了driver下两个只写的属性文件,unbind和bind。应该是提供用户空间命令是否将设备与驱动挂接的接口。
[cpp]view plaincopyprint?
1.static int driver_add_attrs(struct bus_type *bus, struct device_driver *drv)
2.{
3.int error = 0;
4.int i;
5.
6.if (bus->drv_attrs) {
7.for (i = 0; attr_name(bus->drv_attrs[i]); i++) {
8. error = driver_create_file(drv, &bus->drv_attrs[i]);
9.if (error)
10.goto err;
11. }
12. }
13.done:
14.return error;
15.err:
16.while (--i >= 0)
17. driver_remove_file(drv, &bus->drv_attrs[i]);
18.goto done;
19.}
20.
21.static void driver_remove_attrs(struct bus_type *bus,
22.struct device_driver *drv)
23.{
24.int i;
25.
26.if (bus->drv_attrs) {
27.for (i = 0; attr_name(bus->drv_attrs[i]); i++)
28. driver_remove_file(drv, &bus->drv_attrs[i]);
29. }
30.}
driver_add_attrs()向drv目录下添加属性,只是这些属性都是在bus中定义的drv_attrs[]。driver_remove_attrs()从drv目录中删除相应的bus->drv_attrs[]。
[cpp]view plaincopyprint?
1.static int __must_check add_bind_files(struct device_driver *drv)
2.{
3.int ret;
4.
5. ret = driver_create_file(drv, &driver_attr_unbind);
6.if (ret == 0) {
7. ret = driver_create_file(drv, &driver_attr_bind);
8.if (ret)
9. driver_remove_file(drv, &driver_attr_unbind);
10. }
11.return ret;
12.}
13.
14.static void remove_bind_files(struct device_driver *drv)
15.{
16. driver_remove_file(drv, &driver_attr_bind);
17. driver_remove_file(drv, &driver_attr_unbind);
18.}
add_bind_files()在drv目录下增加bind和unbind属性。
remove_bind_files()从drv目录下删除bind和unbind属性。
[cpp]view plaincopyprint?
1.static ssize_t driver_uevent_store(struct device_driver *drv,
2.const char *buf, size_t count)
3.{
4.enum kobject_action action;
5.
6.if (kobject_action_type(buf, count, &action) == 0)
7. kobject_uevent(&drv->p->kobj, action);
8.return count;
9.}
10.static DRIVER_ATTR(uevent, S_IWUSR, NULL, driver_uevent_store);
这是drv目录下地uevent属性文件,提供了从drv发送uevent的方法。
[cpp]view plaincopyprint?
1./**
2. * bus_add_driver - Add a driver to the bus.
3. * @drv: driver.
4. */
5.int bus_add_driver(struct device_driver *drv)
6.{
7.struct bus_type *bus;
8.struct driver_private *priv;
9.int error = 0;
10.
11. bus = bus_get(drv->bus);
12.if (!bus)
13.return -EINVAL;
14.
15. pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
16.
17. priv = kzalloc(sizeof(*priv), GFP_KERNEL);
18.if (!priv) {
19. error = -ENOMEM;
20.goto out_put_bus;
21. }
22. klist_init(&priv->klist_devices, NULL, NULL);
23. priv->driver = drv;
24. drv->p = priv;
25. priv->kobj.kset = bus->p->drivers_kset;
26. error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
27."%s", drv->name);
28.if (error)
29.goto out_unregister;
30.
31.if (drv->bus->p->drivers_autoprobe) {
32. error = driver_attach(drv);
33.if (error)
34.goto out_unregister;
35. }
36. klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
37. module_add_driver(drv->owner, drv);
38.
39. error = driver_create_file(drv, &driver_attr_uevent);
40.if (error) {
41. printk(KERN_ERR "%s: uevent attr (%s) failed\n",
42. __func__, drv->name);
43. }
44. error = driver_add_attrs(bus, drv);
45.if (error) {
46./* How the hell do we get out of this pickle? Give up */
47. printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
48. __func__, drv->name);
49. }
50.
51.if (!drv->suppress_bind_attrs) {
52. error = add_bind_files(drv);
53.if (error) {
54./* Ditto */
55. printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
56. __func__, drv->name);
57. }
58. }
59.
60. kobject_uevent(&priv->kobj, KOBJ_ADD);
61.return 0;
62.
63.out_unregister:
64. kfree(drv->p);
65. drv->p = NULL;
66. kobject_put(&priv->kobj);
67.out_put_bus:
68. bus_put(bus);
69.return error;
70.}
bus_add_driver()看似是把drv与bus联系起来,其实是完成driver加入系统的大部分操作。首先调用bus_get(drv->bus)增加对bus的引用。
分配并初始化drv->p,即driver_private结构。
调用kobject_init_and_add()将drv加入sysfs,之前只是设置了priv->obj.kset为
bus->p->drivers_kset,所以drv目录会出现在bus目录的drivers子目录中。如果总线允许自动probe,就会调用driver_attach()将驱动和总线上的设备进行匹配,这个过程先略过。然后调用klist_add_tail()将drv挂入总线的驱动链表。
调用module_add_driver()创建driver相关的模块在sysfs中的表示。后面专门描述。
调用driver_create_file()在drv目录下创建uevent属性文件。
调用driver_add_attrs()在drv目录下添加bus->driver_attrs[]中定义的属性。
如果drv->suppress_bind_attrs为零,即允许用户空间决定驱动何时链接和卸载设备,则调用add_bind_files()添加bind和unbind属性文件。
调用kobject_uevent()向用户空间发布KOBJ_ADD消息。
从bus_add_driver()的处理过程来看,driver只在bus的drivers目录下出现,没什么软链接,需要的属性也不多。
[cpp]view plaincopyprint?
1./**
2. * bus_remove_driver - delete driver from bus's knowledge.
3. * @drv: driver.
4. *
5. * Detach the driver from the devices it controls, and remove
6. * it from its bus's list of drivers. Finally, we drop the reference
7. * to the bus we took in bus_add_driver().
8. */
9.void bus_remove_driver(struct device_driver *drv)
10.{
11.if (!drv->bus)
12.return;
13.
14.if (!drv->suppress_bind_attrs)
15. remove_bind_files(drv);
16. driver_remove_attrs(drv->bus, drv);
17. driver_remove_file(drv, &driver_attr_uevent);
18. klist_remove(&drv->p->knode_bus);
19. pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);
20. driver_detach(drv);
21. module_remove_driver(drv);
22. kobject_put(&drv->p->kobj);
23. bus_put(drv->bus);
24.}
bus_remove_driver()将drv从系统中撤销,与bus_add_driver()相对应。
driver真正精彩的地方在于probe函数,对设备的操作,对用户空间提供的接口,可惜这些都是特定的。这里只能将driver与bus联系起来,并在以后与device联系起来。
不过不必失望,下面我们分析下drivers/base/module.c,它显示了与驱动有关的module,在sysfs中的表现情况。
首先介绍使用到的结构。应该说module.c的代码实现很简单,但使用到的结构不简单。[cpp]view plaincopyprint?
1.struct module_attribute {
2.struct attribute attr;
3. ssize_t (*show)(struct module_attribute *, struct module *, char *);
4. ssize_t (*store)(struct module_attribute *, struct module *,
5.const char *, size_t count);
6.void (*setup)(struct module *, const char *);
7.int (*test)(struct module *);
8.void (*free)(struct module *);
9.};
10.
11.struct param_attribute
12.{
13.struct module_attribute mattr;
14.struct kernel_param *param;
15.};
16.
17.struct module_param_attrs
18.{
19. unsigned int num;
20.struct attribute_group grp;
21.struct param_attribute attrs[0];
22.};
23.
24.struct module_kobject
25.{
26.struct kobject kobj;
27.struct module *mod;
28.struct kobject *drivers_dir;
29.struct module_param_attrs *mp;
30.};
可以看到module_attribute结构除了包含struct attribute,还多增加了好几条函数指针。而这只是最简单的,struct param_attribute除了包含module_attribute,还有一个指向kernel_param的指针param。这个kernel_param就太复杂了,是外界向module提供参数用的窗口,这里忽略。后面还有struct module_param_attrs和struct module_kobject。[cpp]view plaincopyprint?
1.static char *make_driver_name(struct device_driver *drv)
2.{
3.char *driver_name;
4.
5. driver_name = kmalloc(strlen(drv->name) + strlen(drv->bus->name) + 2,
6. GFP_KERNEL);
7.if (!driver_name)
8.return NULL;
9.
10. sprintf(driver_name, "%s:%s", drv->bus->name, drv->name);
11.return driver_name;
12.}
make_driver_name()将drv的名字和drv->bus的名字合起来,不过这是一个内部函数,具体使用还要看后面。
[cpp]view plaincopyprint?
1.static void module_create_drivers_dir(struct module_kobject *mk)
2.{
3.if (!mk || mk->drivers_dir)
4.return;
5.
6. mk->drivers_dir = kobject_create_and_add("drivers", &mk->kobj);
7.}
module_create_drivers_dir()在mk所在的目录下创建一个drivers的目录。不过因为是使用kobject_create_and_add(),所以这个kobject使用默认的dynamic_kobj_ktype。
[cpp]view plaincopyprint?
1.void module_add_driver(struct module *mod, struct device_driver *drv)
2.{
3.char *driver_name;
4.int no_warn;
5.struct module_kobject *mk = NULL;
6.
7.if (!drv)
8.return;
9.
10.if (mod)
11. mk = &mod->mkobj;
12.else if (drv->mod_name) {
13.struct kobject *mkobj;
14.
15./* Lookup built-in module entry in /sys/modules */
16. mkobj = kset_find_obj(module_kset, drv->mod_name);
17.if (mkobj) {
18. mk = container_of(mkobj, struct module_kobject, kobj);
19./* remember our module structure */
20. drv->p->mkobj = mk;
21./* kset_find_obj took a reference */
22. kobject_put(mkobj);
23. }
24. }
25.
26.if (!mk)
27.return;
28.
29./* Don't check return codes; these calls are idempotent */
30. no_warn = sysfs_create_link(&drv->p->kobj, &mk->kobj, "module");
31. driver_name = make_driver_name(drv);
32.if (driver_name) {
33. module_create_drivers_dir(mk);
34. no_warn = sysfs_create_link(mk->drivers_dir, &drv->p->kobj,
35. driver_name);
36. kfree(driver_name);
37. }
38.}
module_add_drivers()在module下添加与driver的联系。
开始调用kset_find_obj()从module_kset下寻找drv所属的module对应的kobj。说明每个module在加载时都会在/sys/module中创建一个kobject目录。这里找到后只是将其赋给drv->p->kmobj,并调用kobject_put()释放找到时加上的引用计数。至于为什么driver不保留对module的引用计数,或许是不需要,或许是已经存在了。
接下来调用sysfs_create_link()在驱动目录中添加指向module目录的软链接,名称就是module。
调用module_create_drivers_dir()在module目录下建立drivers子目录。
调用sysfs_create_link()在drivers子目录下建立指向驱动目录的软链接,名称使用
make_driver_name()的返回结果。
[cpp]view plaincopyprint?
1.void module_remove_driver(struct device_driver *drv)
2.{
3.struct module_kobject *mk = NULL;
4.char *driver_name;
5.
6.if (!drv)
7.return;
8.
9. sysfs_remove_link(&drv->p->kobj, "module");
10.
11.if (drv->owner)
12. mk = &drv->owner->mkobj;
13.else if (drv->p->mkobj)
14. mk = drv->p->mkobj;
15.if (mk && mk->drivers_dir) {
16. driver_name = make_driver_name(drv);
17.if (driver_name) {
18. sysfs_remove_link(mk->drivers_dir, driver_name);
19. kfree(driver_name);
20. }
21. }
22.}
module_remove_driver()消除driver与相应module之间的软链接关系。
对于module,应该是另一个议题了,这里只是简单涉及,下节我们将涉及到总线bus,并深入分析device和driver的关系。
Linux设备驱动程序设计实例2007-03-03 23:09 Linux系统中,设备驱动程序是操作系统内核的重要组成部分,在与硬件设备之间 建立了标准的抽象接口。通过这个接口,用户可以像处理普通文件一样,对硬件设 备进行打开(open)、关闭(close)、读写(read/write)等操作。通过分析和设计设 备驱动程序,可以深入理解Linux系统和进行系统开发。本文通过一个简单的例子 来说明设备驱动程序的设计。 1、程序清单 //MyDev.c 2000年2月7日编写 #ifndef __KERNEL__ #define __KERNEL__//按内核模块编译 #endif #ifndef MODULE #define MODULE//设备驱动程序模块编译 #endif #define DEVICE_NAME "MyDev" #define OPENSPK 1 #define CLOSESPK 2 //必要的头文件 #include
第31卷 第2期 2008年4月 电子器件 Ch in es e Jo u rnal Of Electro n Devi ces Vol.31 No.2Apr.2008 Design and Implementation of Audio Driver for Embedded Linux System YU Yue,YA O G uo -liang * (N ational A S I C S ystem Eng ine ering Center ,S outhe ast Unive rsity ,N anj ing 210096,China) Abstract:This paper intro duces the fundam ental principle and architecture of the audio system w hich con -sists of the CODEC UCB1400and the 805puls,and describes the design of audio dev ice dr iv er based on Audio Codec .97for Embedded Linux System.The paper focuses o n the implementatio n of the DM A trans -port and ioctl interface.T he audio dr iv e is running w ell in actual Embedded Linux system equipments.Key words:805plus;embedded Linux;Audio A C .97driver;DM A;ioctl interface EEACC :1130B 嵌入式Linux 系统中音频驱动的设计与实现 虞 跃,姚国良 * (东南大学国家专用集成电路系统工程中心,南京210096) 收稿日期:2007-07-09 作者简介:虞 跃(1982-),男,东南大学电子工程系国家专用集成电路工程技术研究中心硕士研究生,研究方向为嵌入式系统设计; 姚国良(1979-),男,东南大学电子工程系博士研究生,yuyueo@https://www.doczj.com/doc/0e18593785.html,. 摘 要:介绍了由805puls 处理器和U CB1400编解码芯片构成的音频系统体系结构及工作原理,接着阐述了嵌入式Linux 操作系统下基于A C .97协议标准的音频设备驱动程序的设计与实现。其中着重讲述了采用循环缓冲区进行音频数据的DM A 传输流程以及ioctl 接口的实现。此设计方案已在嵌入式L inux 系统中得到使用,运行效果良好。 关键词:805plus;嵌入式L inux ;AC .97音频驱动;DM A;ioctl 接口中图分类号:TP391 文献标识码:A 文章编号:1005-9490(2008)02-0709-03 嵌入式音频系统广泛应用于GPS 自动导航、PDA,3G 手机等移动信息终端,具备播放、录音功能的音频系统的应用使得移动信息终端上视听娱乐IP 电话、音频录制等成为可能,并推动了移动信息终端设备的发展。 在软件上,嵌入式操作系统的新兴力量Linux 的开源性,内核可定制等优点吸引了许多的开发者与开发商。它是个和U nix 相似、以核心为基础的、完全内存保护、多任务多进程的操作系统。支持广泛的计算机硬件,包括X86,A lpha,Sparc,M IPS,PPC,ARM ,NEC,MOT OROLA 等现有的大部分芯片[1]。 本文针对805puls 微处理器选用Philips 公司的编解码芯片(CODEC)U CB1400,构建了基于Au -dio Codec .97(AC .97)标准的音频系统。并介绍了该音频系统在Linux 操作系统2.4.19内核下驱动 程序的实现技术。 1 音频系统构架 1.1 微处理器805plus 805plus 是东南大学ASIC 系统工程技术研究中心和北京大学微处理器研究开发中心共同设计和开发的32bit 嵌入式微处理器,是采用H ar vard 结构的RISC 处理器。内部采用五级流水线结构,兼容16bit 和32bit 的指令系统805plus 嵌入式微处理器集成了存储接口EMI,时钟和功耗管理PM C,中断控制器INTC,通用定时器T IM ER,脉宽调制器PWM,实时时钟RT C,通用串口UA RT,LCD 控制器LCDC,AC .97控制器,同步外设接口SPI 。1.2 AC .97协议标准[2] AC'97协议标准是一套关于A C'97数字音频处理(AC'97Digital Controller)、AC '97数字串口(AC
实现如下的功能: --字符设备驱动程序的结构及驱动程序需要实现的系统调用 --可以使用cat命令或者自编的readtest命令读出"设备"里的内容 --以8139网卡为例,演示了I/O端口和I/O内存的使用 本文中的大部分内容在Linux Device Driver这本书中都可以找到, 这本书是Linux驱动开发者的唯一圣经。 ================================================== ===== 先来看看整个驱动程序的入口,是char8139_init(这个函数 如果不指定MODULE_LICENSE("GPL", 在模块插入内核的 时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的 "GPL"属性。 module_init(char8139_init; module_exit(char8139_exit; MODULE_LICENSE("GPL"; MODULE_AUTHOR("ypixunil"; MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC"; 接着往下看char8139_init( static int __init char8139_init(void {
int result; PDBG("hello. init.\n"; /* register our char device */ result=register_chrdev(char8139_major, "char8139", &char8139_fops; if(result<0 { PDBG("Cannot allocate major device number!\n"; return result; } /* register_chrdev( will assign a major device number and return if it called * with "major" parameter set to 0 */ if(char8139_major == 0 char8139_major=result; /* allocate some kernel memory we need */ buffer=(unsigned char*(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL; if(!buffer { PDBG("Cannot allocate memory!\n"; result= -ENOMEM;
linux驱动开发的经典书籍 结构、操作系统、体系结构、编译原理、计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高 从安装使用=>linux常用命令=>linux系统编程=>内核开发阅读内核源码 其中学习linux常用命令时就要学会自己编译内核,优化系统,调整参数 安装和常用命令书太多了,找本稍微详细点的就ok,其间需要学会正则表达式 系统编程推荐《高级unix环境编程》,黑话叫APUE 还有《unix网络编程》 这时候大概还需要看资料理解elf文件格式,连接器和加载器,cmu的一本教材中文名为《深入理解计算机系统》比较好 内核开发阅读内核源码阶段,从写驱动入手逐渐深入linux内核开发 参考书如下《linux device drivers》,黑话叫ldd 《linux kernel development》,黑话叫lkd 《understading the linux kernel》,黑话叫utlk 《linux源码情景分析》 这四本书为搞内核的必读书籍 最后,第三阶段和第四阶段最重动手,空言无益,光看书也不罩,不动手那些东西理解不了 学习linux/unix编程方法的建议 建议学习路径: 首先先学学编辑器,vim, emacs什么的都行。 然后学make file文件,只要知道一点就行,这样就可以准备编程序了。 然后看看《C程序设计语言》K&R,这样呢,基本上就可以进行一般的编程了,顺便找本数据结构的书来看。 如果想学习UNIX/LINUX的编程,《APUE》绝对经典的教材,加深一下功底,学习《UNP》的第二卷。这样基本上系统方面的就可以掌握了。 然后再看Douglus E. Comer的《用TCP/IP进行网际互连》第一卷,学习一下网络的知识,再看《UNP》的第一卷,不仅学习网络编程,而且对系统编程的一些常用的技巧就很熟悉了,如果继续网络编程,建议看《TCP/IP进行网际互连》的第三卷,里面有很多关于应用
CAN总线在嵌入式Linux下驱动程序的实现 时间:2009-11-05 09:41:22 来源:微计算机信息作者:黄捷峰蔡启仲郭毅锋田小刚 1 引言 基于嵌入式系统设计的工业控制装置,在工业控制现场受到各种干扰,如电磁、粉尘、天气等对系统的正常运行造成很大的影响。在工业控制现场各个设备之间要经常交换、传输数据,需要一种抗干扰性强、稳定、传输速率快的现场总线进行通信。文章采用CAN总线,基于嵌入式系统32位的S3C44B0X微处理器,通过其SPI接口,MCP2510 CAN控制器扩展CAN总线;将嵌入式操作系统嵌入到S3C44B0X微处理器中,能实现多任务、友好图形用户界面;针对S3C44B0X微处理器没有内存管理单元MMU,采用uClinux嵌入式操作系统。这样在嵌入式系统中扩展CAN设备关键技术就是CAN设备在嵌入式操作系统下驱动程序的实现。文章重点解决了CAN总线在嵌入式操作系统下驱动程序实现的问题。对于用户来说,CAN设备在嵌入式操作系统驱动的实现为用户屏蔽了硬件的细节,用户不用关心硬件就可以编出自己的用户程序。实验结果表明驱动程序的正确性,能提高整个系统的抗干扰能力,稳定性好,最大传输速率达到1Mb/s;硬件的错误检定特性也增强了CAN的抗电磁干扰能力。 2 系统硬件设计 系统采用S3C44B0X微处理器,需要扩展CAN控制器。常用的CAN控制器有SJA1000和MCP2510,这两种芯片都支持CAN2.0B标准。SJA1000采用的总线是地址线和数据线复用的方式,但是嵌入式处理器外部总线大多是地址线和数据线分开的结构,这样每次对SJA1000操作时需要先后写入地址和数据2次数据,而且SJA1000使用5V逻辑电平。所以应用MCP2510控制器进行扩展,收发器采用82C250。MCP2510控制器特点:1.支持标准格式和扩展格式的CAN数据帧结构(CAN2.0B);2.0~8字节的有效数据长度,支持远程帧;3.最大1Mb/s的可编程波特率;4.2个支持过滤器的接受缓冲区,3个发送缓冲区; 5.SPI高速串行总线,最大5MHz; 6.3~5.5V宽电压范围供电。MCP2510工作电压为3.3V,能够直接与S3C44B0X微处理器I/O口相连。为了进一步提高系统抗干扰性,可在CAN控制器和收发器之间加一个光隔6N137。其结构原理框图如图1: 图1.S3C44B0X扩展CAN结构框图图2.字符设备注册表 3 CAN设备驱动程序的设计 Linux把设备看成特殊的文件进行管理,添加一种设备,首先要注册该设备,增加它的驱动。设备驱动程序是操作系统内核与设备硬件之间的接口,并为应用程序屏蔽了硬件细节。在linux中用户进程不能直接对物理设备进行操作,必须通过系统调用向内核提出请求,
Linux设备驱动程序学习(18)-USB 驱动程序(三) (2009-07-14 11:45) 分类:Linux设备驱动程序 USB urb (USB request block) 内核使用2.6.29.4 USB 设备驱动代码通过urb和所有的 USB 设备通讯。urb用 struct urb 结构描述(include/linux/usb.h )。 urb以一种异步的方式同一个特定USB设备的特定端点发送或接受数据。一个USB 设备驱动可根据驱动的需要,分配多个 urb 给一个端点或重用单个 urb 给多个不同的端点。设备中的每个端点都处理一个 urb 队列, 所以多个 urb 可在队列清空之前被发送到相同的端点。 一个 urb 的典型生命循环如下: (1)被创建; (2)被分配给一个特定 USB 设备的特定端点; (3)被提交给 USB 核心; (4)被 USB 核心提交给特定设备的特定 USB 主机控制器驱动; (5)被 USB 主机控制器驱动处理, 并传送到设备; (6)以上操作完成后,USB主机控制器驱动通知 USB 设备驱动。 urb 也可被提交它的驱动在任何时间取消;如果设备被移除,urb 可以被USB 核心取消。urb 被动态创建并包含一个内部引用计数,使它们可以在最后一个用户释放它们时被自动释放。 struct urb
struct list_head urb_list;/* list head for use by the urb's * current owner */ struct list_head anchor_list;/* the URB may be anchored */ struct usb_anchor *anchor; struct usb_device *dev;/* 指向这个 urb 要发送的目标 struct usb_device 的指针,这个变量必须在这个 urb 被发送到 USB 核心之前被USB 驱动初始化.*/ struct usb_host_endpoint *ep;/* (internal) pointer to endpoint */ unsigned int pipe;/* 这个 urb 所要发送到的特定struct usb_device 的端点消息,这个变量必须在这个 urb 被发送到 USB 核心之前被 USB 驱动初始化.必须由下面的函数生成*/ int status;/*当 urb开始由 USB 核心处理或处理结束, 这个变量被设置为 urb 的当前状态. USB 驱动可安全访问这个变量的唯一时间是在 urb 结束处理例程函数中. 这个限制是为防止竞态. 对于等时 urb, 在这个变量中成功值(0)只表示这个 urb 是否已被去链. 为获得等时 urb 的详细状态, 应当检查 iso_frame_desc 变量. */ unsigned int transfer_flags;/* 传输设置*/ void*transfer_buffer;/* 指向用于发送数据到设备(OUT urb)或者从设备接收数据(IN urb)的缓冲区指针。为了主机控制器驱动正确访问这个缓冲, 它必须使用 kmalloc 调用来创建, 不是在堆栈或者静态内存中。对控制端点, 这个缓冲区用于数据中转*/ dma_addr_t transfer_dma;/* 用于以 DMA 方式传送数据到 USB 设备的缓冲区*/ int transfer_buffer_length;/* transfer_buffer 或者 transfer_dma 变量指向的缓冲区大小。如果这是 0, 传送缓冲没有被 USB 核心所使用。对于一个 OUT 端点, 如果这个端点大小比这个变量指定的值小, 对这个USB 设备的传输将被分成更小的块,以正确地传送数据。这种大的传送以连续的 USB 帧进行。在一个 urb 中提交一个大块数据, 并且使 USB 主机控制器去划分为更小的块, 比以连续地顺序发送小缓冲的速度快得多*/
Linux2.6设备驱动常用的接口函数(一) ----字符设备 刚开始,学习linux驱动,觉得linux驱动很难,有字符设备,块设备,网络设备,针对每一种设备其接口函数,驱动的架构都不一样。这么多函数,要每一个的熟悉,那可多难啦!可后来发现linux驱动有很多规律可循,驱动的基本框架都差不多,再就是一些通用的模块。 基本的架构里包括:加载,卸载,常用的读写,打开,关闭,这是那种那基本的咯。利用这些基本的功能,当然无法实现一个系统。比方说:当多个执行单元对资源进行访问时,会引发竞态;当执行单元获取不到资源时,它是阻塞还是非阻塞?当突然间来了中断,该怎么办?还有内存管理,异步通知。而linux 针对这些问题提供了一系列的接口函数和模板框架。这样,在实际驱动设计中,根据具体的要求,选择不同的模块来实现其功能需求。 觉得能熟练理解,运用这些函数,是写号linux设备驱动的第一步。因为是设备驱动,是与最底层的设备打交道,就必须要熟悉底层设备的一些特性,例如字符设备,块设备等。系统提供的接口函数,功能模块就像是工具,能够根据不同的底层设备的的一些特性,选择不同的工具,方能在linux驱动中游刃有余。 最后就是调试,这可是最头疼的事。在调试过程中,总会遇到这样,那样的问题。怎样能更快,更好的发现并解决这些问题,就是一个人的道行咯!我个人觉得: 发现问题比解决问题更难! 时好时坏的东西,最纠结! 看得见的错误比看不见的错误好解决! 一:Fops结构体中函数: ①ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型). ②ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数 ③loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述). ④int (*open) (struct inode *, struct file *);
linux驱动程序的编写 一、实验目的 1.掌握linux驱动程序的编写方法 2.掌握驱动程序动态模块的调试方法 3.掌握驱动程序填加到内核的方法 二、实验内容 1. 学习linux驱动程序的编写流程 2. 学习驱动程序动态模块的调试方法 3. 学习驱动程序填加到内核的流程 三、实验设备 PentiumII以上的PC机,LINUX操作系统,EL-ARM860实验箱 四、linux的驱动程序的编写 嵌入式应用对成本和实时性比较敏感,而对linux的应用主要体现在对硬件的驱动程序的编写和上层应用程序的开发上。 嵌入式linux驱动程序的基本结构和标准Linux的结构基本一致,也支持模块化模式,所以,大部分驱动程序编成模块化形式,而且,要求可以在不同的体系结构上安装。linux是可以支持模块化模式的,但由于嵌入式应用是针对具体的应用,所以,一般不采用该模式,而是把驱动程序直接编译进内核之中。但是这种模式是调试驱动模块的极佳方法。 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。同时,设备驱动程序是内核的一部分,它完成以下的功能:对设备初始化和释放;把数据从内核传送到硬件和从硬件读取数据;读取应用程序传送给设备文件的数据和回送应用程序请求的数据;检测和处理设备出现的错误。在linux操作系统下有字符设备和块设备,网络设备三类主要的设备文件类型。 字符设备和块设备的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般就紧接着发生了;块设备利用一块系统内存作为缓冲区,当用户进程对设备请求满足用户要求时,就返回请求的数据。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。 1 字符设备驱动结构 Linux字符设备驱动的关键数据结构是cdev和file_operations结构体。
Linux设备驱动模型之platform总线深入浅出 在Linux2.6以后的设备驱动模型中,需关心总线,设备和驱动这三种实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。 对于依附在USB、PCI、I2C、SPI等物理总线来这些都不是问题。但是在嵌入式系统里面,在Soc系统中集成的独立外设控制器,挂接在Soc内存空间的外设等却不依附在此类总线。基于这一背景,Linux发明了一种总线,称为platform。相对于USB、PCI、I2C、SPI等物理总线来说,platform总线是一种虚拟、抽象出来的总线,实际中并不存在这样的总线。 platform总线相关代码:driver\base\platform.c 文件相关结构体定义:include\linux\platform_device.h 文件中 platform总线管理下最重要的两个结构体是platform_device和platform_driver 分别表示设备和驱动在Linux中的定义如下一:platform_driver //include\linux\platform_device.h 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 3 4 5 6 7 8
本人此刻还不是什么驱动工程师,连入门都谈不上,但我坚信在未来的3-5年我肯定能成为我想像中的人,因为我马上就要进入这一行工作了。写下这个日志来记录我是怎么最后成为我想像中的人才的,呵呵。 《Linux驱动工程师》这个东西是我在大二的时候看到有一篇讲如何学习嵌入式的,点击这里下载PDF,里面讲到嵌入式分为四层:硬件,驱动,系统,应用程序;还说linux驱动最难然后工资也最高就冲着他这句话我就决定我大学毕业的时候要去做这个linux驱动工程师,随后我就先后买了51单片机,ARM7,ARM9还有一大堆的视频教程准备来进行学习。我还跟我旁边那个哈工大哥们说:“我们学校像我这样的人很少,你们学校呢?”他说:“太少了,不过我们学校都是做这种板子卖的人比较多!”。行,你们牛!即使是买了这些东西,从大二到现在都快毕业了但感觉还是没有入门。回想一下我都学过什么啊:1:自己在ARM9上写bootloader(主要锻炼了三方面的知识:C语言应该写了有近万行的代码,ARM9的外设的基本操作方法如UART,LCD,TOUCH,SD,USB,ETHERNET...,makefile);2:移植和学习linux驱动。下面我说一下我学习Linux驱动的一个思路这也是我在面试的时候自我介绍中最重要的部分;1:硬件知识学习Linux驱动首先得了解这个驱动对应的硬件的一些基本原理和操作方法比如LCD你得了解它的场同步,行同步,像素时钟,一个像素的表示模式,还有就是这个LCD是怎么把图像显示在屏幕上的。如果是USB,SD卡就得了解相关协议。可以通过spec(协议)、datasheet来了解,这就是传说中的Linux驱动开发三件宝之二,还有一个就是linux相关源码。2:了解linux驱动框架linux下的每一类驱动差不多都是一个比较完善的子系统,比如FLASH的驱动它就属于MTD子系统从上到下分为四层:设备节点层,设备层,原始设备层,最下面的与具体硬件相关的硬件驱动层,通常要我们自己来实现就是最下面这个与具体硬件相关那部分代码。3:了解这个驱动的数据流。这个过程与第二个过程紧密相关,如果了解了驱动的框架差不多这个过程也算了解了。比如flash.在/dev/目录下有对应flash的字符设备文件和块设备文件,用户对这些文件进行读、写、ioctl操作,其间通过层层的函数调用最终将调用到最下面的硬件驱动层对硬件进行操作。了解这个过程我相信在调试驱动的时候是很有帮助。3:分析与硬件相关通常需要我们实现的那部分源代码。4:三板子上将驱动调试出来。每次调试都会出问题,但我买的板子提供的资料比较全调试过程中遇到的问题都比较浅显,即使是浅显的问题也要把它记录下来。(这个是我上次在华为面试的时候,那个人问我你调试驱动遇到过什么问题吗?你是如何解决的。当时我学习还没有到调试驱动这一步,所以那次面试也惨败收场)。 好像说了这么多,还没有进入正题《工作的选择》。在年前去了龙芯,实习2.8K,转正3.5k,环境还是不错,经理很好,头儿也很帅都是中科院的硕士。不过去了两周我就没去了身边的人都不太理解,我也一度有过后悔的时候,从龙芯出来应该是1月6号,也就是从那个时候开始我就没有再找工作,转而学习linux驱动。一直到上周日。上周日的晚上我就开始投简历一开始要找linux驱动,在智联里面输入linux驱动出来500来个职位,点开一看没有一个自己符合要求的,差不多都要3-5年经验本科,有时候好不容易有个实习的关键字在里面,一看要求硕士,严重打击了我的信心,哎不管了随便投,最后又投了一下嵌入式关键字的职位。最后就瞎申请,看看职位要求差不多就申请。周一来了,这周一共来了6个面试,创下了我求职以来的历史新高。周一下午面了一家感觉还不错不过到现在也没有给我一个通知,估计当时我要了4500把他给要跑了,这家是做测量的不是Linux驱动,差不多是把ARM当单片机用。周二上午一家也是要招linux驱动面了估计不到二分钟,他
Linux设备驱动程序学习(20)-内存映射和DMA-基本概念 (2011-09-25 15:47) 标签: 虚拟内存设备驱动程序Linux技术分类:Linux设备驱动程序 这部分主要研究 Linux 内存管理的基础知识, 重点在于对设备驱动有用的技术. 因为许多驱动编程需要一些对于虚拟内存(VM)子系统原理的理解。 而这些知识主要分为三个部分: 1、 mmap系统调用的实现原理:它允许设备内存直接映射到一个用户进程地址 空间. 这样做对一些设备来说可显著地提高性能. 2、与mmap的功能相反的应用原理:内核态代码如何跨过边界直接存取用户空间的内存页. 虽然较少驱动需要这个能力. 但是了解如何映射用户空间内存到内 核(使用 get_user_pages)会有用. 3、直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取. 但所有这些技术需要理解 Linux 内存管理的基本原理, 因此我将先学习VM子 系统的基本原理. 一、Linux的内存管理 这里重点是 Linux 内存管理实现的主要特点,而不是描述操作系统的内存管理理论。Linux虚拟内存管理非常的复杂,要写可以写一本书:《深入理解Linux 虚拟内存管理》。学习驱动无须如此深入, 但是对它的工作原理的基本了解是必要的. 解了必要的背景知识后,才可以学习内核管理内存的数据结构. Linux是一个虚拟内存系统(但是在没有MMU的CPU中跑的ucLinux除外), 意味着在内核启动了MMU 之后所有使用的地址不直接对应于硬件使用的物理地址,这些地址(称之为虚拟地址)都经过了MMU转换为物理地址之后再从CPU的内存总线中发出,读取/写入数据. 这样 VM 就引入了一个间接层, 它是许多操作成为可能: 1、系统中运行的程序可以分配远多于物理内存的内存空间,即便单个进程都可拥有一个大于系统的物理内存的虚拟地址空间. 2、虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射程序的内存到设备内存.等等~~~ 1、地址类型 Linux 系统处理几种类型的地址, 每个有它自己的含义: 用户虚拟地址:User virtual addresses,用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底层的硬件结构, 并且每个进程有它自己 的虚拟地址空间.
参考: 韦东山视频第10课第一节内核启动流程分析之编译体验 第11课第三节构建根文件系统之busybox 第11课第四节构建根文件系统之构建根文件系统韦东山书籍《嵌入式linux应用开发完全手册》 其他《linux设备驱动程序》第三版 平台: JZ2440、mini2440或TQ2440 交叉网线和miniUSB PC机(windows系统和Vmware下的ubuntu12.04) 一、交叉编译环境的选型 具体的安装交叉编译工具,网上很多资料都有,我的那篇《arm-linux- gcc交叉环境相关知识》也有介绍,这里我只是想提示大家:构建跟文件系统中所用到的lib库一定要是本系统Ubuntu中的交叉编译环境arm-linux- gcc中的。即如果电脑ubuntu中的交叉编译环境为arm-linux-
二、主机、开发板和虚拟机要三者互通 w IP v2.0》一文中有详细的操作步骤,不再赘述。 linux 2.6.22.6_jz2440.patch组合而来,具体操作: 1. 解压缩内核和其补丁包 tar xjvf linux-2.6.22.6.tar.bz2 # 解压内核 tar xjvf linux-2.6.22.6_jz2440.tar.bz2 # 解压补丁
cd linux_2.6.22.6 patch –p1 < ../linux-2.6.22.6_jz2440.patch 3. 配置 在内核目录下执行make 2410_defconfig生成配置菜单,至于怎么配置,《嵌入式linux应用开发完全手册》有详细介绍。 4. 生成uImage make uImage 四、移植busybox 在我们的根文件系统中的/bin和/sbin目录下有各种命令的应用程序,而这些程序在嵌入式系统中都是通过busybox来构建的,每一个命令实际上都是一个指向bu sybox的链接,busybox通过传入的参数来决定进行何种命令操作。 1)配置busybox 解压busybox-1.7.0,然后进入该目录,使用make menuconfig进行配置。这里我们这配置两项 一是在编译选项选择动态库编译,当然你也可以选择静态,不过那样构建的根文件系统会比动态编译的的大。 ->Busybox Settings ->Build Options
嵌入式linux android驱动工程师面试题总汇 1.嵌入式系统中断服务子程序(ISR)收藏中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。 2.C语言中对位的操作,比如对a的第三位清0,第四位置1.本来应该会的,一犯晕写反了,以后注意! #define BIT3 (1<<3) #define BIT4 (1<<4) a &= ~BIT3;a |= BIT4; 3.考到volatile含义并举例: 理解出错,举了很具体的例子,连程序都搬上去了,有些理解不深的没举出来…… volatile表示这个变量会被意想不到的改变,每次用他的时候都会小心的重新读取一遍,不适用寄存器保存的副本。 volatile表示直接存取原始地址 例: 并行设备的硬件寄存器(状态寄存器) 在多线程运行的时候共享变量也要时时更新 一个中断服务子程序中访问到的的非自动变量(不太清楚,正在查找资料ing……) 4.要求设置一绝对地址为0x67a9的整型变量的值为0xaa66
当时我的写法: #define AA *(volatile unsigned long *)0xaa66AA = 0x67a9; 答案: int *ptr =(int *)0xaa66; *ptr = 0x67a9; 我感觉自己写的应该不算错吧(自我感觉,还请达人指正),我写的适合裸机下用,当做寄存器用,而答案就是适合在操作系统下的写法。 1. linux内核里面,内存申请有哪几个函数,各自的区别? 2. IRQ和FIQ有什么区别,在CPU里面是是怎么做的? 3. int *a; char *b; a 和b本身是什么类型? a、b里面本身存放的只是一个地址,难道是这两个地址有不同么? 4.xx的上半部分和下半部分的问题: 讲下分成上半部分和下半部分的原因,为何要分?讲下如何实现? 5.内核函数mmap的实现原理,机制? 6.驱动里面为什么要有并发、互斥的控制?如何实现?讲个例子? 7. spinlock自旋锁是如何实现的? 8.任务调度的机制? 【二、本人碰到】
第一章Linux设备驱动程序简介 Linux Kernel 系统架构图 一、驱动程序的特点 ?是应用和硬件设备之间的一个软件层。 ?这个软件层一般在内核中实现 ?设备驱动程序的作用在于提供机制,而不是提供策略,编写访问硬件的内核代码时不要给用户强加任何策略 o机制:驱动程序能实现什么功能。 o策略:用户如何使用这些功能。 二、设备驱动分类和内核模块 ?设备驱动类型。Linux 系统将设备驱动分成三种类型 o字符设备 o块设备 o网络设备 ?内核模块:内核模块是内核提供的一种可以动态加载功能单元来扩展内核功能的机制,类似于软件中的插件机制。这种功能单元叫内核模块。 ?通常为每个驱动创建一个不同的模块,而不在一个模块中实现多个设备驱动,从而实现良好的伸缩性和扩展性。 三、字符设备 ?字符设备是个能够象字节流<比如文件)一样访问的设备,由字符设备驱动程序来实现这种特性。通过/dev下的字符设备文件来访问。字符设备驱动程序通常至少需要实现 open、close、read 和 write 等系统调用 所对应的对该硬件进行操作的功能函数。 ?应用程序调用system call<系统调用),例如:read、write,将会导致操作系统执行上层功能组件的代码,这些代码会处理内核的一些内部 事务,为操作硬件做好准备,然后就会调用驱动程序中实现的对硬件进 行物理操作的函数,从而完成对硬件的驱动,然后返回操作系统上层功 能组件的代码,做好内核内部的善后事务,最后返回应用程序。 ?由于应用程序必须使用/dev目录下的设备文件<参见open调用的第1个参数),所以该设备文件必须事先创建。谁创建设备文件呢? ?大多数字符设备是个只能顺序访问的数据通道,不能前后移动访问指针,这点和文件不同。比如串口驱动,只能顺序的读写设备。然而,也 存在和数据区或者文件特性类似的字符设备,访问它们时可前后移动访
对一个具有四个按键的按键驱动的分析 源代码: /*Headers-------------------------------------------------*/ #include
文库资料?2017 Guangzhou ZHIYUAN Electronics Stock Co., Ltd. 如何实现Linux 设备驱动模型 设备驱动模型,对系统的所有设备和驱动进行了抽象,形成了复杂的设备树型结构,采用面向对象的方法,抽象出了device 设备、driver 驱动、bus 总线和class 类等概念,所有已经注册的设备和驱动都挂在总线上,总线来完成设备和驱动之间的匹配。总线、设备、驱动以及类之间的关系错综复杂,在Linux 内核中通过kobject 、kset 和subsys 来进行管理,驱动编写可以忽略这些管理机制的具体实现。 设备驱动模型的内部结构还在不停的发生改变,如device 、driver 、bus 等数据结构在不同版本都有差异,但是基于设备驱动模型编程的结构基本还是统一的。 Linux 设备驱动模型是Linux 驱动编程的高级内容,这一节只对device 、driver 等这些基本概念作介绍,便于阅读和理解内核中的代码。实际上,具体驱动也不会孤立的使用这些概念,这些概念都融合在更高层的驱动子系统中。对于大多数读者可以忽略这一节内容。 1.1.1 设备 在Linux 设备驱动模型中,底层用device 结构来描述所管理的设备。device 结构在文件