`
totoxian
  • 浏览: 1023937 次
  • 性别: Icon_minigender_2
  • 来自: 西安
文章分类
社区版块
存档分类
最新评论

linux驱动杂谈1

 
阅读更多

前段时间看了linux驱动框架,现在有个具体的scsi驱动分层结构,自然在理解了整个驱动大框架以后,这个曾经的庞然大物就变得很简单了,首先:
static int __init init_sd(void)
{
int majors = 0, i;
SCSI_LOG_HLQUEUE(3, printk("init_sd: sd driver entry point/n"));
for (i = 0; i if (register_blkdev(sd_major(i), "sd") == 0)
majors++;
if (!majors)
return -ENODEV;
class_register(&sd_disk_class);
return scsi_register_driver(&sd_template.gendrv);-->下面的函数
}
int scsi_register_driver(struct device_driver *drv)
{
drv->bus = &scsi_bus_type;
return driver_register(drv);
}
这样在这个函数的最后就进入到整个驱动框架了,在整个scsi总线上枚举所有设备,然后调用probe函数,看一下probe函数。注意是sd_template.gendrv提供的,先看一下sd_template结构。
static struct scsi_driver sd_template = {
.owner = THIS_MODULE,
.gendrv = {
.name = "sd",
.probe = sd_probe,
.remove = sd_remove,
.shutdown = sd_shutdown,
},
.rescan = sd_rescan,
.init_command = sd_init_command,
.issue_flush = sd_issue_flush,
};
接下来可以看sd_probe了。
static int sd_probe(struct device *dev)
{
struct scsi_device *sdp = to_scsi_device(dev);
struct scsi_disk *sdkp;
struct gendisk *gd;
u32 index;
int error;
error = -ENODEV;
if (sdp->type != TYPE_DISK && sdp->type != TYPE_MOD && sdp->type != TYPE_RBC)
goto out;
SCSI_LOG_HLQUEUE(3, sdev_printk(KERN_INFO, sdp,
"sd_attach/n"));
error = -ENOMEM;
sdkp = kzalloc(sizeof(*sdkp), GFP_KERNEL);
if (!sdkp)
goto out;
gd = alloc_disk(16);
if (!gd)
goto out_free;
if (!idr_pre_get(&sd_index_idr, GFP_KERNEL))
goto out_put;
spin_lock(&sd_index_lock);
error = idr_get_new(&sd_index_idr, NULL, &index);
spin_unlock(&sd_index_lock);
if (index >= SD_MAX_DISKS)
error = -EBUSY;
if (error)
goto out_put;
class_device_initialize(&sdkp->cdev);
sdkp->cdev.dev = &sdp->sdev_gendev;
sdkp->cdev.class = &sd_disk_class;
strncpy(sdkp->cdev.class_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE);
if (class_device_add(&sdkp->cdev))
goto out_put;
get_device(&sdp->sdev_gendev);
sdkp->device = sdp;
sdkp->driver = &sd_template;
sdkp->disk = gd;
sdkp->index = index;
sdkp->openers = 0;
if (!sdp->timeout) {
if (sdp->type != TYPE_MOD)
sdp->timeout = SD_TIMEOUT;
else
sdp->timeout = SD_MOD_TIMEOUT;
}
gd->major = sd_major((index & 0xf0) >> 4);
gd->first_minor = ((index & 0xf) gd->minors = 16;
gd->fops = &sd_fops;
if (index sprintf(gd->disk_name, "sd%c", 'a' + index % 26);
} else if (index sprintf(gd->disk_name, "sd%c%c",
'a' + index / 26 - 1,'a' + index % 26);
} else {
const unsigned int m1 = (index / 26 - 1) / 26 - 1;
const unsigned int m2 = (index / 26 - 1) % 26;
const unsigned int m3 = index % 26;
sprintf(gd->disk_name, "sd%c%c%c",
'a' + m1, 'a' + m2, 'a' + m3);
}
gd->private_data = &sdkp->driver;
gd->queue = sdkp->device->request_queue;
sd_revalidate_disk(gd);
gd->driverfs_dev = &sdp->sdev_gendev;
gd->flags = GENHD_FL_DRIVERFS;
if (sdp->removable)
gd->flags |= GENHD_FL_REMOVABLE;
dev_set_drvdata(dev, sdkp);
add_disk(gd);
return 0;

}
众所周知,linux下的usb存储设备是模拟成scsi设备的,最主要的就是当usb存储驱动的probe被调用的时候就要向scsi层报告。下面看看代码怎么实现的。
static int storage_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct Scsi_Host *host;
struct us_data *us;
int result;
struct task_struct *th;
......
host = scsi_host_alloc(&usb_stor_host_template, sizeof(*us));
......
us = host_to_us(host);
......
/* Associate the us_data structure with the USB device */
result = associate_dev(us, intf);
if (result)
goto BadDevice;
......
get_device_info(us, id);
/* Get the transport, protocol, and pipe settings */
result = get_transport(us);
......
result = get_protocol(us);
......
result = get_pipes(us);
.......
result = usb_stor_acquire_resources(us);
......
下面这一句就是向scsi层报告的实现,一会分析这个函数
result = scsi_add_host(host, &intf->dev);
th = kthread_create(usb_stor_scan_thread, us, "usb-stor-scan");
......
scsi_host_get(us_to_host(us));
atomic_inc(&total_threads);
wake_up_process(th);
return 0;
......
}
这个函数里主要初始化了这个usb存储器,另外一点很重要的就是注册了scsi,下面就是scsi_add_host
int scsi_add_host(struct Scsi_Host *shost, struct device *dev)
{
struct scsi_host_template *sht = shost->hostt;
int error = -EINVAL;
if (!shost->can_queue) {

if (!shost->shost_gendev.parent)
shost->shost_gendev.parent = dev ? dev : &platform_bus;
error = device_add(&shost->shost_gendev);//重点在这里
if (error)
goto out;
scsi_host_set_state(shost, SHOST_RUNNING);
get_device(shost->shost_gendev.parent);
error = class_device_add(&shost->shost_classdev);
if (error)
goto out_del_gendev;
get_device(&shost->shost_gendev);
if (shost->transportt->host_size &&
(shost->shost_data = kmalloc(shost->transportt->host_size,
GFP_KERNEL)) == NULL)
goto out_del_classdev;
if (shost->transportt->create_work_queue) {

}
error = scsi_sysfs_add_host(shost);

scsi_proc_host_add(shost);
return error;

}
device_add这个调用应该很熟悉了,就是将通用设备加入到总线数据结构,调用bus_add_device,然后枚举该总线上的所有驱动程序并且调用合适的匹配成功的驱动程序的probe函数。
这样就把各个驱动程序联系起来了,Linux就是靠这种层次化的驱动把很多层次堆叠在一起并且巧妙管理的
serio_driver_probe-->serio_connect_driver(struct serio *serio, struct serio_driver *drv)-->drv->connect(serio, drv)-->input_register_device(atkbd->dev)-->|list_add_tail(&dev->node, &input_dev_list);
|list_for_each_entry(handler, &input_handler_list, node) if (!handler->blacklist || !input_match_device(handler->blacklist, dev)) if ((id = input_match_device(handler->id_table, dev))) if ((handle = handler->connect(handler, dev, id))) input_link_handle(handle);
以上是从驱动的角度来分析的,当驱动加载的时候,驱动的probe函数被回调,然后一步一步就到了handler->connect,那么如果是设备被插入呢?下面分析一下:
mousedev_init-->|input_register_handler(&mousedev_handler)-->|list_add_tail(&handler->node, &input_handler_list)
|list_for_each_entry(dev, &input_dev_list, node)
if (!handler->blacklist|| !input_match_device(handler->blacklist, dev))
if ((id = input_match_device(handler->id_table, dev)))
if ((handle = handler->connect(handler, dev, id)))
input_link_handle(handle);
|class_device_create
这样同样也可以到connect,分析来看,input子系统有两个链表:input_handler_list和input_dev_list,如果是初始化了handler,比如input_register_handler(&mousedev_handler),那么就要在input_dev_list上进行枚举,相对的,如果是初始化了input_dev,那么就要在input_handler_list上进行枚举。
我开始分析这个输入子系统的时候发现不像曾经分析usb和pci那样有device_attach和driver_attach这么对称的结构,其实那是更底层实现的功能,到了输入子系统的时候就已经过了那一层了,可以从上面的代码看出,输入子系统是从驱动的probe回调函数开始的,可以从probe函数看出来
static int serio_driver_probe(struct device *dev)
{
struct serio *serio = to_serio_port(dev);
struct serio_driver *drv = to_serio_driver(dev->driver);
return serio_connect_driver(serio, drv);
}
在这里将统一设备层的device和device_driver都进行了转化,转化成了相应的具体的类型,但是设备的加入,驱动的加入和设备的探测均是在统一的设备层次进行的,在统一层初始化好设备以后,具体说就是device结构怎么和input_dev联系起来呢?这就得看probe函数调用的connect函数了,
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
{
int retval;
mutex_lock(&serio->drv_mutex);
retval = drv->connect(serio, drv);
mutex_unlock(&serio->drv_mutex);
return retval;
}
进一步看一个具体的connect函数
static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
{
struct atkbd *atkbd;
struct input_dev *dev;
int err = -ENOMEM;
atkbd = kzalloc(sizeof(struct atkbd), GFP_KERNEL);
dev = input_allocate_device();
if (!atkbd || !dev)
goto fail;
atkbd->dev = dev;
ps2_init(&atkbd->ps2dev, serio);
INIT_WORK(&atkbd->event_work, atkbd_event_work, atkbd);
mutex_init(&atkbd->event_mutex);

atkbd->softraw = atkbd_softraw;
atkbd->softrepeat = atkbd_softrepeat;
atkbd->scroll = atkbd_scroll;
if (atkbd->softrepeat)
atkbd->softraw = 1;
serio_set_drvdata(serio, atkbd);
err = serio_open(serio, drv);
if (err)
goto fail;
if (atkbd->write) {
if (atkbd_probe(atkbd)) {

}
atkbd->set = atkbd_select_set(atkbd, atkbd_set, atkbd_extra);
atkbd_activate(atkbd);

atkbd_set_keycode_table(atkbd);
atkbd_set_device_attrs(atkbd);
…//创建sysfs文件
atkbd_enable(atkbd);
input_register_device(atkbd->dev);
return 0;
fail: serio_set_drvdata(serio, NULL);
input_free_device(dev);
kfree(atkbd);
return err;
}
这个函数不是那么厂,但是可以解决疑问,有必要看看struct serio,这个结构里有个serio_driver域和device域一个是和设备联系的驱动,另一个是和统一设备层联系的统一设备结构,serio_set_drvdata(serio, atkbd)调用将serio和atkbd控制结构联系起来,函数实现很简单:
static inline void serio_set_drvdata(struct serio *serio, void *data)
{
dev_set_drvdata(&serio->dev, data);
}
dev_set_drvdata (struct device *dev, void *data)
{
dev->driver_data = data;
}
绕了几圈,原来如此,实际上,如果主板对热插拔支持比较好,那么每当一个输入设备插入的时候,则会根据物理接口的电子信号和设备id产生一个serio结构并插入链表,然后等待总线上的驱动一一来认领。
好了,绕过了统一设备层就到了输入子系统,同样也有和统一设备层一样的认领体系,一个加入后就让所有相关链表的相关结构认领,然后调用connect函数来初始化handler,这一切从input_register_device(atkbd->dev)
开始。
在输入子系统初始化的时候首先启动一个内核线程,然后循环处理事件,下面就分析一下:
serio_init-->kthread_run(serio_thread, NULL, "kseriod")-->循环调用serio_handle_event()-->|serio_get_event()
|......
|case SERIO_REGISTER_DRIVER:
serio_drv = event->object;
driver_register(&serio_drv->driver);
break;
这里仅仅给出处理SERIO_REGISTER_DRIVER事件的例子,处理的过程就很熟悉了,就是driver_register,这往后就再熟悉不过了,那么这个SERIO_REGISTER_DRIVER事件是怎么加入系统的呢?看看这个函数吧:
void __serio_register_driver(struct serio_driver *drv, struct module *owner)
{
drv->driver.bus = &serio_bus;
serio_queue_event(drv, owner, SERIO_REGISTER_DRIVER);
}
然后看看
static void serio_queue_event(void *object, struct module *owner, enum serio_event_type event_type)
{
unsigned long flags;
struct serio_event *event;
spin_lock_irqsave(&serio_event_lock, flags);
list_for_each_entry_reverse(event, &serio_event_list, node) {
if (event->object == object) {
if (event->type == event_type)
goto out;
break;
}
}
if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) {
if (!try_module_get(owner)) {

}
event->type = event_type;
event->object = object;
event->owner = owner;
list_add_tail(&event->node, &serio_event_list);
wake_up(&serio_wait);

}
这个函数本质上就是把事件入队,然后那个线程依次取出事件并且处理之,再进一步,这个register函数是谁调用的呢?往下看:
static int __init sermouse_init(void)
{
serio_register_driver(&sermouse_drv);
return 0;
}
原来是鼠标驱动初始化调用的。
一路下来有3个init函数,一个是sermouse_init,一个是mousedev_init,一个是serio_init,其中最后一个是最基本的,它创建一个线程来循环处理事件,第二个是为了初始化输入子系统层次,第一个是注册驱动。这里的2条链表让我想起了bus_type的2条链表
输入子系统基于内核线程处理事件,和usb子系统类似,在usb子系统也是通过hub_init来创建内核线程的,然后监视设备插入拔除事件,这里的输入子系统在比较高的层次同样实现了这种架构,毕竟输入设备可以是串口的,可以是并口的,也可以是usb的,这就要求在较高的层次实现,将底层的不一致屏蔽。这种分层的思想实在太好了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics