第一部分,将对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。