linux 2.6.36+s3c6410 SPI子系统接口讨论 二

23 篇文章 0 订阅

http://www.arm9home.net/read.php?tid=10788


说到用户空间接口就不得不提到cdev结构,这个是字符设备的结构
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};
我们分析一下这个结构,包含了kobject 对象,file_operations 函数结构,kobject 我们都知道这是linux文件系统结构中最重要的结构,用来表示文件对象,这里就不细说了,而file_operations 都包含了什么呢?
/*
* NOTE:
* all file operations except setlease can be called without
* the big kernel lock held in all filesystems.
*/
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};
上面是file_operations 的结构,也就是文件可以操作的API

再回过来看看spidev中的初始化函数中的cdev的注册函数
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
这里注册了spi设备,spidev_fops的定义如下:
static const struct file_operations spidev_fops = {
    .owner =    THIS_MODULE,
    /* REVISIT switch to aio primitives, so that userspace
     * gets more complete API coverage.  It'll simplify things
     * too, except for the locking.
     */
    .write =    spidev_write,
    .read =        spidev_read,
    .unlocked_ioctl = spidev_ioctl,
    .open =        spidev_open,
    .release =    spidev_release,
};
也就是说spi用户空间接口提供了,write 、read、unlocked_ioctl 、open 、release 等五个cdev设备的标准操作。
看到这里大家应该知道该如何操作spi设备了吧。
spidev0.0和spidev1.0都是挂接到spidev字符设备下子设备,它们共用一套设备操作函数。
这里要重点分析的是spidev_ioctl函数,它用来设置spi设备的状态等等操作。
这个函数中以下这一段代码
switch (cmd) {
    /* read requests */
    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;

    /* write requests */
    case SPI_IOC_WR_MODE:
        retval = __get_user(tmp, (u8 __user *)arg);
        if (retval == 0) {
            u8    save = spi->mode;

            if (tmp & ~SPI_MODE_MASK) {
                retval = -EINVAL;
                break;
            }

            tmp |= spi->mode & ~SPI_MODE_MASK;
            spi->mode = (u8)tmp;
            retval = spi_setup(spi);
            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)
                spi->mode |= SPI_LSB_FIRST;
            else
                spi->mode &= ~SPI_LSB_FIRST;
            retval = spi_setup(spi);
            if (retval < 0)
                spi->mode = save;
            else
                dev_dbg(&spi->dev, "%csb first\n",
                        tmp ? 'l' : 'm');
        }
        break;
    case SPI_IOC_WR_BITS_PER_WORD:
        retval = __get_user(tmp, (__u8 __user *)arg);
        if (retval == 0) {
            u8    save = spi->bits_per_word;

            spi->bits_per_word = tmp;
            retval = spi_setup(spi);
            if (retval < 0)
                spi->bits_per_word = save;
            else
                dev_dbg(&spi->dev, "%d bits per word\n", tmp);
        }
        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);
            if (retval < 0)
                spi->max_speed_hz = save;
            else
                dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
        }
        break;
可以看出它支持如下命令
SPI_IOC_RD_MODE    设置读的模式
SPI_IOC_RD_LSB_FIRST   设置读字的格式
SPI_IOC_RD_BITS_PER_WORD 设置读字长
SPI_IOC_RD_MAX_SPEED_HZ   设置读的速度

SPI_IOC_WR_MODE    设置写的模式
SPI_IOC_WR_LSB_FIRST   设置写的字格式
SPI_IOC_WR_BITS_PER_WORD   设置写的字长
SPI_IOC_WR_MAX_SPEED_HZ   设置写的速度

mode的取值可以是如下常量的组合
#define    SPI_CPHA    0x01            /* clock phase */
#define    SPI_CPOL    0x02            /* clock polarity */
#define    SPI_MODE_0    (0|0)            /* (original MicroWire) */
#define    SPI_MODE_1    (0|SPI_CPHA)
#define    SPI_MODE_2    (SPI_CPOL|0)
#define    SPI_MODE_3    (SPI_CPOL|SPI_CPHA)
#define    SPI_CS_HIGH    0x04            /* chipselect active high? */
#define    SPI_LSB_FIRST    0x08            /* per-word bits-on-wire */
#define    SPI_3WIRE    0x10            /* SI/SO signals shared */
#define    SPI_LOOP    0x20            /* loopback mode */
#define    SPI_NO_CS    0x40            /* 1 dev/bus, no chipselect */
#define    SPI_READY    0x80            /* slave pulls low to pause */



前面简要讨论了如何构建和运行SPI,使得大家对如何用SPI有了一个基本的了解,下面我们将深入SPI内部的运行机制的讨论。
同样,我们将从系统运行时的对象开始讨论,而这又离不开对struct的分析,以及系统运行时的内核SPI对象的分析。
我们首先从spi.h文件中定义的各个struct对象开始分析
在这个文件中定义了如下结构
spi_device、spi_driver、spi_master、spi_transfer、spi_message、spi_board_info
spi_device 是SPI从设备,即连接在SPI总线上的外围设备
spi_driver是SPI主机边的协议驱动
spi_master是spi控制器,即s3c6410中的spi端口的软件控制器
spi_transfer :a read/write buffer pair
struct spi_message : one multi-segment SPI transaction

在spidev.h中定义了如下结构
struct spi_ioc_transfer - describes a single SPI transfer
This structure is mapped directly to the kernel spi_transfer structure;
从注释中可以知道这个结构是对内核中的spi_transfer 的映射

我们再看看s3c64xx-spi.h中的定义,这里定义了
struct s3c64xx_spi_csinfo - ChipSelect description
struct s3c64xx_spi_info - SPI Controller defining structure

在spidev.c中定义了
spidev_data结构

在spi.c中定义了
boardinfo结构

在spi-s3c64xx.c中定义了
struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
s3c64xx_spi_driver_data 是SPI驱动的运行时信息

上述这些结构构成了SPI运行的基础架构,也就是spi的静态结构,但是要搞清楚spi的运行机制从用户接口的read和write开始顺藤摸瓜会比较简单
让我们看看函数
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
这个函数是spi用户接口的函数
这个函数中status = spidev_sync_read(spidev, count);负责从spi总线上读出数据
missing = copy_to_user(buf, spidev->buffer, status);负责将数据从内核空间拷贝到用户空间

我们再分析spidev_sync_read函数
static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
    struct spi_transfer    t = {
            .rx_buf        = spidev->buffer,
            .len        = len,
        };
    struct spi_message    m;

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    return spidev_sync(spidev, &m);
}
这个函数生成了一个spi_message    和一个spi_transfer    
并把他们加入到了一个传输队列中。spi_message_init函数就不细讲了,就是初始化spi_message    的结构
而spi_message_add_tail函数做了什么工作呢?是将spi_transfer加入到spi_message的传输任务列表
即spi_transfer其实是一个原子的传输任务单元

最终由spidev_sync负责执行,这个函数是同步执行的所以函数内部由异步执行的
status = spi_async(spidev->spi, message);
和wait_for_completion(&done);转换为同步执行
我们再看spi_async函数都做了什么?
它执行了一个内部函数ret = __spi_async(spi, message);
这个内部函数真正的传输动作是master->transfer(spi, message);
而我们追踪到这里发现spi_master结构中transfer仅仅是一个预定义的函数指针
我们回过头来从spi_master的初始化过程开始追踪,发现在spi_s3c64xx.c  中s3c64xx_spi_probe这个函数初始化spi_master时将函数s3c64xx_spi_transfer安装到了spi_master的函数指针transfer上,这种方法被大量用在linux内核中,这里暂时偏题一下,这种方法有什么好处呢?就是可以预先定义行为,但是却不用给出行为的实现,而行为的实现由于和具体的芯片架构有关系,所以由架构类的代码在初始化进行组装,最终形成完整的对象。就好比linux内核负责提供了汽车底盘,而大量的芯片厂商负责了给底盘组装轮子等外围设备,所以底盘厂商只要定义好接口就可以了。
让我们看看这个函数都干了什么?
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);

    queue_work(sdd->workqueue, &sdd->work);


    spin_unlock_irqrestore(&sdd->lock, flags);

    return 0;
}
注意加粗的函数,原来它仅仅是将传输任务加到了spi_master的运行时数据s3c64xx_spi_driver_data的任务队列中
而queue_work是干什么的?
分析s3c64xx_spi_driver_data这个结构对work和workqueue的作用不是很了解,而work和workqueue似乎又和执行transfer有非常大的关系。

分析spi_master的初始化过程发现work是由这个宏命令初始化的
    INIT_WORK(&sdd->work, s3c64xx_spi_work);
分析s3c64xx_spi_work函数,发现传输任务最终由handle_msg函数负责执行
也就是说 handle_msg函数最终操纵芯片执行了传输任务

我们先搁下handle_msg不表,先来回忆一下这个过程,可以发现整个过程是这样的,具体的传输任务其实最后都是由芯片厂商提供的代码完成,而linux内核团队负责制定了spi的用户标准,并为芯片厂商制定了向下的接口标准,这就是linux内核负责的核心负责的工作,它是与架构无关的,它负责制定标准。芯片厂商根据linux内核的标准,又提供了一些芯片相关的基础设施,而由主板厂商负责进行设备的组装。最终完成了spi控制器和spi从设备在内核中的安装工作,包括静态的数据结构和动态的函数功能的组装。具体的函数功能主要由芯片厂商的代码负责完成。

整个linux体系的生态链完整的呈现在我们的面前,linux内核研发团队、芯片厂商、主板厂商、应用厂商的分工明确而清晰。

到这里整个SPI的read过程的运行机制已经十分的清晰,而write过程与此类似就不剖析了。

下一帖,我们再来详细剖析handle_msg的工作,由于它其实是s3c64xx的代码,与架构相关,并且利用了片上的DMA控制器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值