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

来源:互联网 发布:知乎 新ipad和ipadpro 编辑:程序博客网 时间:2024/06/07 17:27

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

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

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

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

本文属于第四部分。

      在spi设备驱动层提供了两种数据传输方式。一种是半双工方式,write方法提供了半双工用户向内核写访问,read方法提供了半双工用户向内核读访问。另一种就是全双工方式,ioctl调用将同时完成数据的传送与发送。

6、用户层调用SPI驱动API分析

一、read、write函数分析

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

 在用户空间执行open打开设备文件以后,就可以执行write系统调用,该系统调用将会执行我们提供的write方法。

static ssize_t spidev_write(struct file *filp,const char __user *buf,                                ssize_t count,loff_t *f_pos){    struct spidev_data *spidev;    ssize_t     status = 0;    unsigned long   missing;        if(coount > bufsiz)               //每次写入的最大长度        return -EMSGSIZE;            spidev = filp->private_data;      //将文件的私有数据付给spidev        mutex_lock(&spidev->buf_lock);    //上锁    missing = copy_from_user(spidev->buffer,buf,count);    if(missing == 0)                  //将数据从用户层传进来到spidev->buffer里面        status = spidev_sync_write(spidev,count);    else                              //调用spidev_sync_write函数        status = -EFAULT;    mutex_unlock(&spidev->buf_lock);  //解锁        return status;}    
         6.2、spidev_sync_write函数,该函数位于/kernel3.0/drivers/spi/spidev.c。
static ssize_t spidev_write(struct file *filp,const char __user *buf,                                size_t count,loff_t *f_pos){    struct spi_transfer t = {        .tx_buf  = spidev->buffer,        .len     = len,    };    struct spi_message m;        spi_message_init(&m);                    spi_message_add_tail(&t,&m);                  return spidev_sync(spidev,&m);     //调用spidev_sync函数}//初始化spi_message里面的transfer链表头,初始化成双向循环链表static inline void spi_message_init(struct spi_message *m){    memset(m,0,sizeof *m);    INIT_LIST_HEAD(&m->transfers);       }//将spi_transfer里面的链表头加入到spi_message的transfer链表后面spi_message_add_tail(struct spi_transfer *t,struct spi_message *m){    list_add_tail(&t->transfer_list,&m->transfers);}             
v

在这里,创建了transfer和message。spi_transfer包含了要发送数据的信息。然后初始化了message中的transfer链表头,并将spi_transfer添加到了transfer链表中。也就是以spi_message的transfers为链表头的链表中,包含了transfer,而transfer正好包含了需要发送的数据。由此可见message其实是对transfer的封装。

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

static ssize_t spidev_sync(struct spidev_data *spidev,struct spi_message *message){    DECLARE_COMPLETION_ONSTACK(done);    int status;        message->complete = spidev_complete;    //定义complete方法    message->context  = &done;              //complete方法的参数        spin_lock_irq(&spidev->spi_lock);    if(spidev->spi == NULL)        status = -ESHUTDOWN;    else        status = spi_async(spidev->spi,message);  //异步,用complete来完成同步            if(status == 0)    {        wait_for_completion(&done);        //在bitbang_work中调用complete方法来唤醒        status = message->status;        if(status == 0)            status = message->actual_length;    }            return status;   }                  

在这里,初始化了completion,这个将实现write系统调用的同步。在后面我们将会看到如何实现的。随后调用了spi_async,从名字上可以看出该函数是异步的,也就是说该函数返回后,数据并没有被发送出去。因此使用了wait_for_completion来等待数据的发送完成,达到同步的目的。

         6.4、spi_async函数,该函数位于/kernel3.0/drivers/spi/spi.c

int spi_async(struct spi_device *spi,struct spi_message *message){    struct spi_master *master = spi->master;    int ret;    unsigned long flags;        spin_lock_irqsave(&master->bus_lock_spinlock,flag);    if(master->bus_lock_flag)        ret = -EBUSY;    else        ret = __sp_async(spi,message);    spin_unlock_irqrestore(&master->bus_lock_spinlock,flag);        return ret;}static int __spi_async(struct spi_device *spi struct spi_message *message){    struct spi_master *master = spi->master;        if((master->flags & SPI_MASTER_HALF_DUPLEX) || (spi->mode & SPI_3WIRE))    {        struct spi_transfer *xfer;        unsigned flags = master->flags;                list_for_each_entry(xfer,&message->transfers,transfer_list)        {//遍历message的transfer链表里面的spi_transfer节点            if(xfer->rx_buf && xfer->tx_buf)                return -EINVAL;            if((flags & SPI_MASTER_NO_TX) && xfer->tx_buf)                return -EINVAL;            if((flags & SPI_MASTER_NO_RX) && xfer->rx_buf)                return -EINVAL;        }    }    message->spi = spi;    message->status = -EINPROGRESS;//调用s3c64xx_spi_transfer函数,因为我们在s3c64xx_spi_probe函数里面对master->transfer进行了赋值    return master->transfer(spi,message);   }     

该函数最终调用SPI控制器驱动里面的transfer函数。

        6.5、s3c64xx_spi_transfer函数,该函数位于/kerenel3.0/drivers/spi/spi_s3c64xx.c
static int s3c64xx_spi_transfer(struct spi_device *spi,struct spi_message *msg){    struct s3c64xx_spi_driver_data *sdd;    unsigned long flags;        sdd = spi_master_get_devdata(spi->master);        spin_lock_irqsave(&sdd->lock,flags);    if(sdd->state & SUSPND)    {        spin_unlock_irqrestore(&sdd->lock,flags);        return -ESHUTDOWN;    }        msg->status = -EINPROGRESS;    msg->actual_length = 0;        list_add_tail(&msg->queue,&sdd->queue);   //将message添加到sdd的queue链表中        queue_work(sdd->workqueue,&sdd->work);    //提交工作到工作队列        spin_unlock_irqrestore(&sdd->lock,flags);        return 0;   } 
在该函数中的最后只是将spi_message添加到sdd->queue,那么添加到工作对了的message是怎么被继续传输的呢,之前在s3c64xx_spi_probe函数中,
有这样一句INIT_WORK(&sdd->work, s3c64xx_spi_work),该函数是循环检查sdd工作队列是否为空,不为空就申请DMA进行数据传输,可看下面函数。
        6.6、s3c64xx_spi_work函数,该函数位于/kerenel3.0/drivers/spi/spi_s3c64xx.c
static void s3c64xx_spi_work(struct work_struct *work){    struct s3c64xx_spi_driver_data *sdd = container_of(work,                    struct s3c64xx_spi_driver_data,work);    unsigned long flags;        //申请DMA通道    while(!acquire_dma(sdd))        msleep(10);            spin_lock_irqsave(&sdd->lock,flags);    while(!list_empty(&sdd->queue) && !(sdd->state & SUSPND))    {        struct spi_message *msg;                //获取spi_message        msg = container_of(sdd->queue.next,struct spi_message,queue);        //已获取spi_messge,然后将该节点其从工作队列中删除        list_del_init(&msg->queue);        //将sdd的状态置为忙        sdd->state |= SPIBUSY;                spin_unlock_irqrestore(&sdd->lock, flags);        handle_msg(sdd,msg);   //处理消息        spin_lock_irqsave(&sdd->lock,flags);        //还原sdd状态,等下一组数据        sdd->state &= ~SPIBUSY;    }    spin_unlock_irqrestore(&sdd->lock,flags);       //释放DMA通道     s3c2410_dma_free(sdd->tx_dmach, &s3c64xx_spi_dma_client);s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client);}                     
该函数调用handle_msg函数,继续调用 wait_for_xfer函数最终完成数据的传输,再返回spi_sync函数返回后,调用了wait_for_completion等待数据的发送完成。到此,可以看出completion的使用是用来完成同步I/O的。
       因为spi用到的是全双工模式,read、write函数都是半双工模式,所以就不再分析read函数。下面分析ioctl函数

二、ioctl函数分析

下面将分析一下SPI子系统是如何使用ioctl系统调用来实现全双工读写。

在使用ioctl时,用户空间要使用一个数据结构来封装需要传输的数据,该结构为spi_ioc_transfe。而在write系统调用时,只是简单的从用户空间复制数据过来。该结构中的很多字段将被复制到spi_transfer结构中相应的字段。也就是说一个spi_ioc_transfer表示一个spi_transfer,用户空间可以定义多个spi_ioc_transfe,最后以数组形式传递给ioctl。

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

static long spidev_ioctl(struct file *filp,unsigned int cmd,unsigned long arg){    int     err = 0;    int     retval  = 0;    struct spidev_data *spidev;    struct spi_device  *spi;    u32     tmp;    unsigned n_ioc;    struct spi_ioc_transfer *ioc;        //检查类型和命令号    if(_IOC_TYPE(cmd) != SPI_IOC_MAGIC)        return -ENOTTY;           //对用户空间的指针进行检查,分成读写两部分检查,IOC_DIR来自于用户,access_ok来自内核       if(_IOC_DIR(cmd) & _IOC_READ)        err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd));    if(err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)        err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd));    if(err)        return -EFAULT;            spidev = filp->private_data;          //将文件的私有数据给spidev    spin_lock_irq(&spidev->spi_lock);    spi = spi_dev_get(spidev->spi);       //获取spi_device        spin_unlock_irq(&spidev->spi_lock);        if(spi == NULL)        return -ESHUTDOWN;            mutex_lock(&spidev->buf_lock);        switch(cmd)    {        //读请求        case SPI_IOC_RD_MODE:            retval = __put_user(spi->mode & SPI_MODE_MASK,(__u8 __user *)arg);            break;        case SPI_IOC_RD_LSB_FIRST:            retval = __put_user((spi->mode & SPI_LSB_FIRST)?1:0,(__u8 __user *)arg);            break;        case SPI_IOC_RD_BITS_PER_WORD:            retval = __put_user(spi->bits_per_word,(__u8 __user *)arg);            break;        case SPI_IOC_RD_MAX_SPEED_HZ:            retval = __put_user(spi->max_speed_hz,(__u32 __user *)arg);            break;                    //写请求        case SPI_IOC_WR_MODE:            retval = __get_user(tmp,(u8 __user *)arg);            if(retval == 0)            {                u8 save = spi->mode;    //保存原先的值                if(temp & ~SPI_MODE_MASK)                {                    retval == 0;                    break;             //模式错误,则跳出switch                }                                tmp |= spi->mode & ~SPI_MODE_MASK;                spi->mode = (u8)tmp;                retval = spi_setup(spi); //先调用spi_setup函数,然后调用s3c64xx_spi_setup                if(retval < 0)                    spi->mode = save;    //调用不成功则恢复参数                else                    dev_dbg(&spi->dev,"spi mode %02x\n",tmp);            }            break;        case SPI_IOC_WR_LSB_FIRST:            retval = __get_user(tmp,(__u8 __user *)arg);            if(retval == 0)            {                u8 save = spi->mode;                if(tmp)    //参数为正数,设置为LSB                    spi->mode |= SPI_LSB_FIRST;                else       //参数为0,则设置为非LSB                    spi->mode &= ~SPI_LSB_FIRST;                retval = spi_setup(spi); //先调用spi_setup函数,然后调用s3c64xx_spi_setup                if(retval < 0)                    spi->mode = save;    //调用不成功则恢复参数                else                    dev_dbg(&spi->dev,"%csb first\n",tmp?'l':'m');            }            break;        case SPI_IOC_WR_MAX_SPEED_HZ:            retval = __get_user(tmp,(__u32 __user *)arg);            if(retval == 0)            {                u32 save =  spi->max_speed_hz;                spi->max_speed_hz =tmp;                retval = spi_setup(spi);  //先调用spi_setup函数,然后调用s3c64xx_spi_setup                if(retval < 0)                    spi->max_speed_hz = save;                else                        dev_dbg(&spi->dev,"%d Hz (max)\n",tmp);            }            break;                    default:        //分段或者全双工IO要求(全双工,接收发送数据)            if(_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) || _IOC_DIR(cmd) != _IOC_WRITE)            {                retval = -ENOTTY;                break;            }                        tmp = _IOC_SIZE(cmd);  //获取参数的大小,参数为spi_ioc_transfer            if((tmp % sizeof(struct spi_ioc_transfer)) != 0)            {                      //检查tmp是否为spi_ioc_transfer的整数倍                retval = -EINVAL;                break;            }            n_ioc = tmp / sizeof(struct spi_ioc_transfer);            if(n_ioc == 0)        //计算传进来的数据共有几个spi_ioc_transfer                break;                            ioc = kmalloc(tmp,GFP_KERNEL);            if(!ioc)            {                retval = -ENOMEM;                break;            }            //从用户空间拷贝spi_ioc_transfer数组,不对用户空间指针进行检查            if(__copy_from_user(ioc,(void __user *)arg,tmp))            {                kfree(ioc);                retval = -EFAULT;                break;            }                        retval = spidev_message(spidev,ioc,n_ioc);  //传递给spi_message函数执行            kfree(ioc);            break;    }    mutex_unlock(&spidev->buf_lock);    spi_dev_put(spi);   //减少引用计数    return retval;}    
在函数中,首先对cmd进行了一些列的检查。随后使用switch语句来判读cmd,并执行相应的功能。cmd的第一部分为读请求,分别从寄存器读取4个参数。第二部分为写请求,分别用于修改4个参数并写入寄存器。剩余的第三部分就是全双工读写请求,这是会先计算共有多少个spi_ioc_transfer,然后分配空间,从用户空间将spi_ioc_transfer数组拷贝过来,然后将该数组和数组个数作为参数调用spidev_message。
        6.8、spidev_message函数,该函数位于/kernel3.0/drivers/spi/spidev.c
static int spidev_message(struct spidev_data *spidev,                struct spi_ioc_transfer *u_xfers,unsigned n_xfers){    struct spi_message msg;    struct spi_transfer *k_xfers;    struct spi_transter *k_tmp;    struct spi_ioc_transfer *u_tmp;    unsigned    n,total;    u8  *buf;    int status = -EFAULT;        spi_message_init(&msg);  //初始化message    k_xfers = kmalloc(n_xfers,sizeof(*k_tmp),GFP_KERNEL); //分配内存    if(k_xfers == NULL)        return -ENOMEM;            buf = spidev->buffer;    //所有的spi_transfer共享该buffer    total = 0;    //遍历spi_ioc_transfer数组,拷贝相应的参数至spi_transfer数组    for(n = n_xfers,k_tmp = k_xfers,u_tmp = u_xfers; n; n--,k_tmp++,u_tmp++)    {        k_tmp->len = u_tmp->len;        total += k_tmp->len;                if(total > bufsiz)  //缓冲区长度为4096字节        {            status = -EMSGSIZE;            goto  done;         }                if(u_tmp->rx_buf)  //需要接收数据        {            k_tmp->rx_buf = buf;            if(!access_ok(VERIFY_WRITE,(u8 __user *)(uintptr_t) u_tmp->rx_buf,u_tmp->len))                goto done;        }                if(u_tmp->tx_buf)  //需要发送数据        {            k_tmp->tx_buf = buf;            if(copy_form_user(buf,(const u8 __user *)(uintptr_t u_tmp->tx_buf,u_tmp->len)))                goto done;        }        buf += k_tmp->len;  //修改buf指针,指向下一个transfer的缓冲区首地址                k_tmp->cs_change = !!u_tmp->cs_change;        k_tmp->bits_per_word = u_tmp->bits_per_word;        k_tmp->delay_usecs = u_tmp->delay_usecs;        k_tmp->speed_hz = u_tmp->speed_hz;#ifdef VERBOSE        dev_dbg(&spidev->spi->dev,        "  xfer len %zd %s%s%s%dbits %u usec %uHz\n",            u_tmp->len,            u_tmp->rx_buf ? "rx " : "",            u_tmp->tx_buf ? "tx " : "",            u_tmp->cs_change ? "cs " : "",            u_tmp->bits_per_word ? : spidev->spi->bits_per_word,            u_tmp->delay_usecs,            u_tmp->speed_hz ? : spidev->spi->max_speed_hz);#endif            spi_message_add_tail(k_tmp, &msg);    }    status = spidev_sync(spidev, &msg);    if (status < 0)        goto done;    /* copy any rx data out of bounce buffer */    buf = spidev->buffer;    for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++)     {        if (u_tmp->rx_buf)         {   //从buf缓冲区复制数据到用户空间            if (__copy_to_user((u8 __user *)(uintptr_t) u_tmp->rx_buf, buf,u_tmp->len))             {                status = -EFAULT;                goto done;            }        }        buf += u_tmp->len;}    status = total;done:    kfree(k_xfers);    return status;}      

 首先,根据spi_ioc_transfer的个数,分配了同样个数的spi_transfer,把spi_ioc_transfer中的信息复制给spi_transfer,然后将spi_transfer添加到spi_message的链表中。接着执行了spidev_sync,这个东西似乎似曾相识,这个函数就 6.3  小结的函数。之后的过程就和前面的write、read一样了。其实,这个函数的作用就是把所需要完成的数据传输任务转换成spi_transfer,然后添加到message的连表中。从spidev_sync返回以后,数据传输完毕,将读取到的数据,复制到用户空间。至此,整个ioctl系统调用的过程就结束了。

        事实上,全速工io和半双工io的执行过程基本一样,只不过ioctl需要一个专用的结构体来封装传输的任务,接着将该任务转换成对应的spi_transfer,最后交spidev_sync。

1 0
原创粉丝点击