第一部分,将对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; }
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函数。
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; }
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); }
二、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; }
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; }
事实上,全速工io和半双工io的执行过程基本一样,只不过ioctl需要一个专用的结构体来封装传输的任务,接着将该任务转换成对应的spi_transfer,最后交spidev_sync。