嵌入式Linux驱动——SPI子系统解读(三)

   第一部分,将对SPI子系统整体进行描述,同时给出SPI的相关数据结构,最后描述SPI总线的注册。

   第二部分,该文将对SPI的主控制器(master)驱动进行描述。

   第三部分,即本篇文章,该文将对SPI设备驱动,也称protocol 驱动,进行讲解。

   第四部分,通过SPI设备驱动留给用户层的API,我们将从上到下描述数据是如何通过SPI的protocol 驱动,由bitbang中转,最后由master驱动将数据传输出去。 

本文属于第三部分。

5. SPI设备驱动

    在主控制器驱动中,spi_device已经注册了,在设备驱动中,首先要做的就是注册spi_driver,并提供用户层相应的API。

         5.1 SPI设备驱动的注册

         SPI设备驱动的定义、注册、以及卸载,所在位置/kernel3.0/driver/spi/spidev.c

static const struct file_operations spidev_fops = {
    .owner = THIS_MODULE,
    .open  = spidev_open,                   
    .write = spidev_write,
    .read  = spidev_read,
    .release = spidev_release,
    .llseek  = no_llseek,
    .unlocked_ioctl = spidev_ioctl,
    .compat_ioctl   = spidev_compat_ioctl,
};

static struct spi_driver spidev_spi_driver = {
    .driver = {
        .name  = "spidev",               //spi驱动和设备匹配的标识
        .owner = THIS_MODULE,
    },
    .probe  = spidev_probe,
    .remove = __devexit_p(spidev_remove),
};

static int __init spidev_init(void)
{
    int status;
    
    BUILD_BUG_ON(N_SPI_MINORS > 256);   //检查次设备号
    status = register_chrdev(SPIDEV_MAJOR,"spi",&spidev_fops);
    if(status < 0)                      //注册字符设备(可以在硬件的/proc/devices目录下查看)
        return status;
    
    spidev_class = class_create(THIS_MODULE,"spidev");
    if(IS_ERR(spidev_class))            //创建spidev类(可以在/sys/bus/class下查看)
    {
        unregister_chrdev(SPIDEV_MAJOR,spidev_spi_driver.driver.name);
        return PTR_ERR(spidev_class);
    }
    
    status = spi_register_driver(&spidev_spi_driver);
    if(status < 0)                      //spi设备驱动的注册,深入追踪可以发现其调用了spidev_probe函数
    {
        class_destroy(spidev_class);
        unregister_chrdev(SPIDEV_MAJOR,spidev_spi_driver.driver.name);
    }
    return status;
};
module_init(spidev_init);

static void __exit spidev_exit(void)
{
    spi_unregister_driver(&spidev_spi_driver);
    class_destroy(spidev_class);
    unregister_chrdev(SPIDEV_MAJOR,spidev_spi_driver.driver.name);
};   
module_exit(spidev_exit); 

该函数中,创建了一个字符设备以提供API给用户层,同时创建了一个spidev类,最后注册spi_driver到内核中。

在这里我们看到了SPI设备驱动是如何提供API给用户层的,那就是通过再熟悉不过的字符设备。通过字符设备,给用户层提供了5个API:open,release,write,read和ioctl。本文在后面将介绍open和close,剩余3个将在本系列的第四篇文章中介绍。

         1、spi_register_driver函数, 该函数位于/kernel3.0/drivers/spi/spidev.c。

int spi_register_driver(struct spi_driver *sdrv)
{
    sdrv->driver.bus = &spi_bus_type;
    if(sdrv->probe)              //driver下的函数赋值
        sdrv->driver.probe = spi_drv_probe;
    if(sdrv->remove)          
        sdrv->driver.remove = spi_drv_remove;
    if(sdrv->shutdown)
        sdrv->driver.shutdown = spi_drv_shutdown;
    return driver_register(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(spi_register_driver);        
         2、driver_register函数,该函数位于/kernel3.0/drivers/base/driver.c。

int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;
    
    BUG_ON(!drv->bus->p);
    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);
    other = driver_find(drv->name,drv->bus);
    if(other)
    {
        put_driver(other);
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
    }
    
    ret = bus_add_driver(drv);
    if(ret)
        return ret;
    ret = driver_add_groups(drv,drv->groups);
    if(ret)
        bus_remove_driver(drv);
    return ret;
};
EXPORT_SYMBOL_GPL(driver_register);  

         3、bus_add_driver函数,该函数位于/kernel3.0/drivers/base/bus.c。

int bus_add_driver(struct device_driver *drv)
{
    struct bus_type *bus;
    struct driver_private *priv;
    int error = 0;
    
    bus = bus_get(drv->bus);  //获取驱动对应的bus总线
    if(!bus)
        return -EINVAL;
    
    pr_debug("bus: '%s':add driver %s\n",bus->name,drv->name);
    
    priv = kalloc(sizeof(*priv),GFP_KERNEL);
    if(!priv)
    {
        error = -ENOMEM;
        goto out_put_bus;
    }
    klist_init(&priv->klist_devices,NULL,NULL);
    priv->driver = drv;
    drv->p = priv;
    priv->kobj.kset = bus->p->drivers_kset;
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
    if (error)
    	goto out_unregister;

    if (drv->bus->p->drivers_autoprobe)
    {
    	error = driver_attach(drv);
    	if (error)
    		goto out_unregister;
    }
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent);
    if (error) {
	   	printk(KERN_ERR "%s: uevent attr (%s) failed\n",
    		__func__, drv->name);
    }
    error = driver_add_attrs(bus, drv);
    if (error) {
    	/* How the hell do we get out of this pickle? Give up */
    	printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
    		__func__, drv->name);
    }

    if (!drv->suppress_bind_attrs) {
    	error = add_bind_files(drv);
    	if (error) {
    		/* Ditto */
    		printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
    			__func__, drv->name);
    	}
    }

    kobject_uevent(&priv->kobj, KOBJ_ADD);
    return 0;

out_unregister:
    kobject_put(&priv->kobj);
    kfree(drv->p);
    drv->p = NULL;
out_put_bus:
    bus_put(bus);
	return error;
}
         4、driver_attech函数,该函数位于/kernel3.0/drivers/base/dd.c。
int driver_attach(struct device_driver *dev)
{
    return bus_for_each_dev(dev->bus,NULL,drv,__driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);

/*往下追踪bus_for_each_dev函数,该函数位于/kernel3.0/drivers/base/bus.c*/
int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
    struct klist_iter i;
    struct device *dev;
    int error = 0;

    if (!bus)
    	return -EINVAL;

    klist_iter_init_node(&bus->p->klist_devices, &i,
    		     (start ? &start->p->knode_bus : NULL));
    while ((dev = next_device(&i)) && !error)
    	error = fn(dev, data);  //通过上一个函数,可以发现fn就是传进来的__driver_attach函数,
    klist_iter_exit(&i);        //fn(dev,data) 也就是__driver_attach(dev,data)
    return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

/*往下追踪__driver_attach函数,该函数位于/kernel3.0/drivers/base/dd.c*/
static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;

    /*
     * Lock device and try to bind to it. We drop the error
     * here and always return 0, because we need to keep trying
     * to bind to devices and some drivers will return an error
     * simply if it didn't support the device.
     *
     * driver_probe_device() will spit a warning if there
     * is an error.
     */
    //在这里进行了驱动和设备的匹配
    if (!driver_match_device(drv, dev))    //匹配(重点)
    	return 0;

    if (dev->parent)	/* Needed for USB */
    	device_lock(dev->parent);
    device_lock(dev);
    if (!dev->driver)
    	driver_probe_device(drv, dev);    //probe(重点)
    device_unlock(dev);
    if (dev->parent)
    	device_unlock(dev->parent);

    return 0;
}

/*往下追踪driver_match_device函数,看看该函数是如何匹配的,*/
/*该函数位于/kernel3.0/drivers/base/base.h                 */
static inline int driver_match_device(struct device_driver *drv,
				      struct device *dev)
{   //dri->bus->match = spi_match_device(该函数位于/kernel3.0/drivers/spi/spi.c里面)
    //到此为止spi驱动和设备进行了匹配
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}

/*往下追踪driver_probe_device函数,看该函数是如何调用spi设备驱动的probe函数,*/
/*该函数位于/kernel3.0/drivers/base/dd.c*/
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    if (!device_is_registered(dev))
    	return -ENODEV;

    pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
    	 drv->bus->name, __func__, dev_name(dev), drv->name);

    pm_runtime_get_noresume(dev);
    pm_runtime_barrier(dev);
    ret = really_probe(dev, drv);    //(重点)
    pm_runtime_put_sync(dev);

    return ret;
}

/*往下追踪really_probe函数,在该函数中调用了SPI设备里面的probe函数,*/
/*该函数位于/kernel3.0/drivers/base/dd.c*/
static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = 0;

    atomic_inc(&probe_count);
    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
    	 drv->bus->name, __func__, drv->name, dev_name(dev));
    WARN_ON(!list_empty(&dev->devres_head));

    dev->driver = drv;
    if (driver_sysfs_add(dev)) {
    	printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
    		__func__, dev_name(dev));
    	goto probe_failed;
    }

    if (dev->bus->probe) {          //在这里进行了probe函数的调用
    	ret = dev->bus->probe(dev); 
    	if (ret)
    		goto probe_failed;
    } else if (drv->probe) {
    	ret = drv->probe(dev);
    	if (ret)
    		goto probe_failed;
    }   
    
    driver_bound(dev);
    ret = 1;
    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
    	 drv->bus->name, __func__, dev_name(dev), drv->name);
    goto done;
    
probe_failed:
    devres_release_all(dev);
    driver_sysfs_remove(dev);
    dev->driver = NULL;

    if (ret != -ENODEV && ret != -ENXIO) {
    	/* driver matched but the probe failed */
    	printk(KERN_WARNING
    	       "%s: probe of %s failed with error %d\n",
    	       drv->name, dev_name(dev), ret);
    }
    /*
     * Ignore errors returned by ->probe so that the next driver can try
     * its luck.
     */
    ret = 0;
done:
    atomic_dec(&probe_count);
    wake_up(&probe_waitqueue);
    return ret;
}  
  vi者相等
在该系列函数中,SPI设备驱动注册后,在driver_match_device函数中进行了SPI设备和SPI设备驱动的匹配,最后在really_probe函数中调用了spidev_probe函数。

5.2 SPI驱动函数分析

        1、spidev_probe函数,该函数位于/kernel3.0/drivers/spi/spidev.c。

static int __devinit spidev_probe(struct spi_device *spi)
{
    struct spidev_data *spidev;
    int status;
    unsigned long minor;
    
    /*申请一个spidev_data结构体空间*/
    spidev = kzalloc(sizeof(*spidev),GFP_KERNEL);
    if(!spidev)
        return -ENOMEM;
    /*初始化spidev结构体元素*/
    spidev->spi = spi;                      //保存spi_device
    spin_lock_init(&spidev->spi_lock);      //初始化自旋锁
    mutex_init(&spidev->buf_lock);          //初始化互斥体
    
    INIT_LIST_HEAD(&spidev->device_entry);  //初始化双向循环链表头 
    
    mutex_lock(&device_list_lock);          //上锁
    /*在一个unsigned long类型的变量的位图中找到其第一个为0位的所在的位置即为minor(次设备号)的值*/
    minor = find_first_zero_bit(minors,N_SPI_MINORS);
    if(minor < N_SPI_MINORS)
    {
        struct device *dev;
        spidev->devt = MKDEV(SPIDEV_MAJOR,minor);  //根据主次设备号获取设备号
        
        /*在/dev下面创建spidev设备节点*/
        dev = device_create(spidev_class,&spi->dev,spidev->devt,
            spidev,"spidev%d.%d",spi->master->bus_num,spi->chip_select);
        status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
    }
    else
    {
        dev_dbg(&spi->dev,"no minor number available!\n");
        status = -ENODEV;
    }
    if(status == 0)
    {
        set_bit(minor,minors);              //保存已使用的次设备号
        list_add(&spidev->device_entry,&device_list); //在链表头list后面添加spidev条目
    }
    mutex_unlock(&device_list_lock);        //解锁
    
    if(status == 0)
        spi_set_drvdata(spi,spidev);
    else
        kfree(spidev);   
             
    return status;      
}              
此外,将spidev添加到device_list中,这样做就方便查找该spidev。
        2、spidev_remove函数,该函数位于/kernel3.0/drivers/spi/spidev.c。

static int __devexit spidev_remove(struct spi_device *spi)
{
    struct spidev_data *spidev = spi_get_drvdata(spi);
    
    spin_lock_irq(&spidev->spi_lock);
    spidev->spi = NULL;
    spi_set_drvdata(spi,NULL);
    spin_unlock_irq(&spidev->spi_lock);
    
    mutex_lock(&device_list_lock);                 //上锁
    list_del(&spidev->device_entry);               //将链表里的device_entry删除
    device_destroy(spidev_class,spidev->devt);     //销毁/dev下的设备节点
    clear_bit(MINOR(spidev->devt),minors);         //将位图中申请的次设备号的位置置0
    if(spidev->users == 0)
        kfree(spidev);
    mutex_unlock(&device_list_lock);              //解锁
    
    return 0;       
}
        3、spidev_open函数,该函数位于 /kernel3.0/drivers/spi/spidev.c。

static int spidev_open(struct *inode,struct file *filp)
{
    struct spidev_data *spidev;
    int     status = -ENXIO;
    
    mutex_lock(&device_list_lock);              //上锁(多个程序调用open方法,但他们共享一个buffer,
                                                //        因此对buufer需要进行互斥保护)
    //从list头开始遍历entry,即遍历所有的spidev
    list_for_each_entry(spidev,&device_list,device_entry)  
    {                                          
        if(spidev->devt == inode->i_rdev)       //判断设备号是否相等
        {
            status = 0;                         //找到匹配的spi设备
            break;
        }
    }
    
    if(status == 0)
    {
        if(!spidev->buffer)
        {   //分配buffer缓冲区,默认4KB
            spidev->buffer = kmalloc(bufsiz,GFP_KERNEL);
            if(!spidev->buffer)
            {
                dev_dbg(&spidev->spi->dev,"open/ENOMEX\n");
                status = -ENOMEME;
            }
        }
        if(status == 0)
        {   
            spidev->users++;               //成功open后,用户增加
            filp->private_data = spidev;   //将spidev保存到文件的私有指针里面
            nonseekable_open(inode,filp);  //禁用lseek
        }
    }
    else
    {
        pr_debug("spidev:nothing for minor %d\n",iminor(inode));
    }
    mutex_unlock(&device_list_lock);       //解锁
    return status;
}       

在这里,以device_list为链表头,遍历所有的spidev_data结构,通过设备节点的设备号和spidev_data中保存的设备号进行匹配,来找到属于该设备节点的spi设备。随后,分配了spi设备驱动层所使用的缓冲区,最后增加打开计数。

        4、spidev_release函数,该函数位于/kernel3.0/drivers/spi/spidev.c。

static int spidev_release(struct inode *inode,struct file *filp)
{
    struct spidev_data *spidev;
    int         status = 0;
    
    mutex_lock(&device_list_lock);   //上锁
    spidev = filp->private_data;     //将文件保存的数据给spidev
    filp->private_data = NULL;       //清空文件的私有数据
    
    spidev->users--;                 //用户减一
    if(!spidev->users)               //如果用户为0
    {
        int dofree;
        
        kfree(spidev->buffer);       //释放缓冲区
        spidev->buffer = NULL;
        
        spin_lock_irq(&spidev->spi_lock);
        dofree = (spidev->spi == NULL);  
        spin_unlock_irq(&spidev->spi_lock);
        
        if(dofree)
            kfree(spidev);          //释放spidev,在probe中申请的
    }
    mutex_unlock(&device_list_lock);  //解锁
    
    return status;
}     

 至此,对于protocol驱动层的框架进行了简单的分析,在下一篇将对该驱动层很多未分析的函数进行一一讲解。

猜你喜欢

转载自blog.csdn.net/bingjia103126/article/details/71125625