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

   第一部分,将对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_transfe r的个数,分配了同样个数的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。

猜你喜欢

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