• spidev的使用(SPI用户态API)


    参考资料:

    内核驱动:drivers\spi\spidev.c
    内核提供的测试程序:tools\spi\spidev_fdx.c
    内核文档:Documentation\spi\spidev

    一、spidev驱动程序分析

    内核驱动:drivers\spi\spidev.c

    1.1 驱动框架

    在这里插入图片描述
    设备树示例:

    spidev0: spidev@0 {
    	compatible = "spidev";
    	reg = <0>;
    	spi-max-frequency = <50000000>;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    设备树里某个spi设备节点的compatible属性等于下列值,就会跟spidev驱动匹配:

    • “rohm,dh2228fv”
    • “lineartechnology,ltc2488”
    • “spidev”
      匹配后,spidev.c的spidev_probe会被调用,它会:
    • 分配一个spidev_data结构体,用来记录对于的spi_device
    • spidev_data会被记录在一个链表里
    • 分配一个此设备号,以后可以根据这个次设备号在链表里找到spidev_data
    • device_create:这会生产一个设备节点/dev/spidevB.D,B表示总线号,D表示它是这个SPI Master下第几个设备
      以后,我们就可以通过/dev/spidevB.D来访问spidev驱动程序

    1.2 驱动程序分析:

    static struct spi_driver spidev_spi_driver = {
        .driver = {
            .name =     "spidev",
            .of_match_table = of_match_ptr(spidev_dt_ids),
            .acpi_match_table = ACPI_PTR(spidev_acpi_ids),
        },
        .probe =    spidev_probe,
        .remove =   spidev_remove,
    };
    
    static int __init spidev_init(void)
    {
        int status;
    
        BUILD_BUG_ON(N_SPI_MINORS > 256);
        status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);	//注册spidev_fops
    		//...
        spidev_class = class_create(THIS_MODULE, "spidev");		//创建spidev类
    		//...
        status = spi_register_driver(&spidev_spi_driver);		//注册spi_driver
    		//...
        return status;
    }
    module_init(spidev_init);
    
    
    static int spidev_probe(struct spi_device *spi)
    {
    	struct spidev_data	*spidev;
    	int			status;
    	unsigned long		minor;
    
    	//....
    	spidev_probe_acpi(spi);
    
    	/* Allocate driver data */
    	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);		//分配结构体
    	//...
    
    	/* Initialize the driver data */
    	spidev->spi = spi;						//1. spidev_data里记录spi_device结构体
    	//...
    	mutex_lock(&device_list_lock);
    	minor = find_first_zero_bit(minors, N_SPI_MINORS);		//2. 找到一个空闲的次设备号
    	if (minor < N_SPI_MINORS) {
    		struct device *dev;
    
    		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
    		dev = device_create(spidev_class, &spi->dev, spidev->devt,		//3. 创建一个设备,通过/dev/spidevx.x
    				    spidev, "spidev%d.%d",
    				    spi->master->bus_num, spi->chip_select);		//spi的第几个spi_master设备,spi的片选信号信息
    		status = PTR_ERR_OR_ZERO(dev);
    	} else {
    		//....
    	}
    	if (status == 0) {
    		set_bit(minor, minors);
    		list_add(&spidev->device_entry, &device_list);		//4. 这个spidev_data会放入device_list链表中
    		//app调用/dev/spidevx.x时,通过次设备号找到spidev_data结构体,从而找到spi_device。
    	}
    	mutex_unlock(&device_list_lock);
    
    	spidev->speed_hz = spi->max_speed_hz;
    
    	//....
    
    	return status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    spidev.c通过file_operation向App提供接口:

    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,		//设置频率、模式,进行双工传输(同时读写)
    	.compat_ioctl = spidev_compat_ioctl,
    	.open =		spidev_open,
    	.release =	spidev_release,
    	.llseek =	no_llseek,
    };
    
    static int spidev_open(struct inode *inode, struct file *filp)
    {
    	struct spidev_data	*spidev;
    	int			status = -ENXIO;
    
    	mutex_lock(&device_list_lock);
    	//1. 在链表中寻找和inode下的注册的次设备号的
    	list_for_each_entry(spidev, &device_list, device_entry) {
    		if (spidev->devt == inode->i_rdev) {
    			status = 0;
    			break;
    		}
    	}
    	//...
    
    	spidev->users++;
    	filp->private_data = spidev;		//2.把找到的spidev_data保存在私有数据中
    	nonseekable_open(inode, filp);
    	//....
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    1.2.1 读函数

    static ssize_t
    spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
    {
    	struct spidev_data	*spidev;
    	ssize_t			status = 0;
    	//...
    	spidev = filp->private_data;		//从私有数据中获取spidev_data数据
    
    	mutex_lock(&spidev->buf_lock);
    	status = spidev_sync_read(spidev, count);		//1.读数据
    	if (status > 0) {
    		unsigned long	missing;
    
    		missing = copy_to_user(buf, spidev->rx_buffer, status);		//2.copy_to_user
    		if (missing == status)
    			status = -EFAULT;
    		else
    			status = status - missing;
    	}
    	mutex_unlock(&spidev->buf_lock);
    
    	return status;
    }
    
    static inline ssize_t
    spidev_sync_read(struct spidev_data *spidev, size_t len)
    {
    	struct spi_transfer	t = {
    			.rx_buf		= spidev->rx_buffer,		//2.指定了rx_buffer
    			.len		= len,
    			.speed_hz	= spidev->speed_hz,
    		};
    	struct spi_message	m;			//1.构造一个message
    	//3. 发起传输
    	spi_message_init(&m);			//初始化spi_message
    	spi_message_add_tail(&t, &m);	//把transfer放入message中
    	return spidev_sync(spidev, &m);		//发起传输
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1.2.2 写函数

    static ssize_t
    spidev_write(struct file *filp, const char __user *buf,
    		size_t count, loff_t *f_pos)
    {
    	struct spidev_data	*spidev;
    	ssize_t			status = 0;
    	unsigned long		missing;
    
    	/* chipselect only toggles at start or end of operation */
    	if (count > bufsiz)
    		return -EMSGSIZE;
    
    	spidev = filp->private_data;
    
    	mutex_lock(&spidev->buf_lock);
    	missing = copy_from_user(spidev->tx_buffer, buf, count);
    	if (missing == 0)
    		status = spidev_sync_write(spidev, count);		//调用函数去写操作
    	else
    		status = -EFAULT;
    	mutex_unlock(&spidev->buf_lock);
    
    	return status;
    }
    
    static inline ssize_t
    spidev_sync_write(struct spidev_data *spidev, size_t len)
    {
    	struct spi_transfer	t = {
    			.tx_buf		= spidev->tx_buffer,		//2.指定tx_buffer
    			.len		= len,		//指定长度
    			.speed_hz	= spidev->speed_hz,
    		};
    	struct spi_message	m;		//1.构造一个消息
    	//3.初始化消息,把t放到message的尾部
    	spi_message_init(&m);
    	spi_message_add_tail(&t, &m);
    	return spidev_sync(spidev, &m);	//4.进行SPI的同步操作
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1.2.3 通过ioctl读写参数

    static long
    spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    	int			retval = 0;
    	struct spidev_data	*spidev;
    	struct spi_device	*spi;
    	u32			tmp;
    	unsigned		n_ioc;
    	struct spi_ioc_transfer	*ioc;
    
    	/* Check type and command number */
    	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
    		return -ENOTTY;
    
    	/* guard against device removal before, or while,
    	 * we issue this ioctl.
    	 */
    	spidev = filp->private_data;
    	spin_lock_irq(&spidev->spi_lock);
    	spi = spi_dev_get(spidev->spi);
    	spin_unlock_irq(&spidev->spi_lock);
    
    	if (spi == NULL)
    		return -ESHUTDOWN;
    
    	/* use the buffer lock here for triple duty:
    	 *  - prevent I/O (from us) so calling spi_setup() is safe;
    	 *  - prevent concurrent SPI_IOC_WR_* from morphing
    	 *    data fields while SPI_IOC_RD_* reads them;
    	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
    	 */
    	mutex_lock(&spidev->buf_lock);
    
    	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_MODE32:
    		retval = put_user(spi->mode & SPI_MODE_MASK,
    					(__u32 __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(spidev->speed_hz, (__u32 __user *)arg);
    		break;
    
    	/* write requests */
    	case SPI_IOC_WR_MODE:
    	case SPI_IOC_WR_MODE32:
    		if (cmd == SPI_IOC_WR_MODE)
    			retval = get_user(tmp, (u8 __user *)arg);
    		else
    			retval = get_user(tmp, (u32 __user *)arg);
    		if (retval == 0) {
    			u32	save = spi->mode;
    
    			if (tmp & ~SPI_MODE_MASK) {
    				retval = -EINVAL;
    				break;
    			}
    
    			tmp |= spi->mode & ~SPI_MODE_MASK;
    			spi->mode = (u16)tmp;
    			retval = spi_setup(spi);
    			if (retval < 0)
    				spi->mode = save;
    			else
    				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
    		}
    		break;
    	case SPI_IOC_WR_LSB_FIRST:
    		retval = get_user(tmp, (__u8 __user *)arg);
    		if (retval == 0) {
    			u32	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)
    				spidev->speed_hz = tmp;
    			else
    				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
    			spi->max_speed_hz = save;
    		}
    		break;
    
    	default:
    		/* segmented and/or full-duplex I/O request */
    		/* Check message and copy into scratch area */
    		ioc = spidev_get_ioc_message(cmd,
    				(struct spi_ioc_transfer __user *)arg, &n_ioc);
    		if (IS_ERR(ioc)) {
    			retval = PTR_ERR(ioc);
    			break;
    		}
    		if (!ioc)
    			break;	/* n_ioc is also 0 */
    
    		/* translate to spi_message, execute */
    		retval = spidev_message(spidev, ioc, n_ioc);
    		kfree(ioc);
    		break;
    	}
    
    	mutex_unlock(&spidev->buf_lock);
    	spi_dev_put(spi);
    	return retval;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145

    二、spidev应用程序分析

    内核提供的测试程序:tools\spi\spidev_fdx.c

    2.1 使用方法

    spidev_fdx [-h] [-m N] [ -r N] /dev/spidevB.D

    • -h:打印用法
    • -m N:先写1个字节0xaa,再读N个字节,注意:不是同时写同时读
    • -r N:读N个字节

    2.2 代码分析

    2.2.1 显示设备属性:

    static void dumpstat(const char *name, int fd)
    {
    	__u8	lsb, bits;
    	__u32	mode, speed;
    
    	if (ioctl(fd, SPI_IOC_RD_MODE32, &mode) < 0) {
    		perror("SPI rd_mode");
    		return;
    	}
    	if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {
    		perror("SPI rd_lsb_fist");
    		return;
    	}
    	if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {
    		perror("SPI bits_per_word");
    		return;
    	}
    	if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {
    		perror("SPI max_speed_hz");
    		return;
    	}
    
    	printf("%s: spi mode 0x%x, %d bits %sper word, %d Hz max\n",
    			name, mode, bits, lsb ? "(lsb first) " : "", speed);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.2.2 读数据

    static void do_read(int fd, int len)
    {
    	unsigned char	buf[32], *bp;
    	int		status;
    
    	/* read at least 2 bytes, no more than 32 */
    	if (len < 2)
    		len = 2;
    	else if (len > sizeof(buf))
    		len = sizeof(buf);
    	memset(buf, 0, sizeof buf);
    
    	status = read(fd, buf, len);		//读取数据
    	if (status < 0) {
    		perror("read");
    		return;
    	}
    	if (status != len) {
    		fprintf(stderr, "short read\n");
    		return;
    	}
    
    	printf("read(%2d, %2d): %02x %02x,", len, status,
    		buf[0], buf[1]);
    	status -= 2;
    	bp = buf + 2;
    	while (status-- > 0)
    		printf(" %02x", *bp++);
    	printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.2.3 先读再写

    static void do_msg(int fd, int len)
    {
    	struct spi_ioc_transfer	xfer[2];
    	unsigned char		buf[32], *bp;
    	int			status;
    
    	memset(xfer, 0, sizeof xfer);
    	memset(buf, 0, sizeof buf);
    
    	if (len > sizeof buf)
    		len = sizeof buf;
    //1.先写
    	buf[0] = 0xaa;
    	xfer[0].tx_buf = (unsigned long)buf;
    	xfer[0].len = 1;
    //2.后读
    	xfer[1].rx_buf = (unsigned long) buf;
    	xfer[1].len = len;
    
    	status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
    	if (status < 0) {
    		perror("SPI_IOC_MESSAGE");
    		return;
    	}
    
    	printf("response(%2d, %2d): ", len, status);
    	for (bp = buf; len; len--)
    		printf(" %02x", *bp++);
    	printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.2.4 同时读写

    static void do_msg(int fd, int len)
    {
    	struct spi_ioc_transfer	xfer[2];
    	unsigned char		buf[32], *bp;
    	unsigned char		buf_rx[32], *bp;
    	int			status;
    
    	memset(xfer, 0, sizeof xfer);
    	memset(buf, 0, sizeof buf);
    
    	if (len > sizeof buf)
    		len = sizeof buf;
    //设置同一个xfer的tx_buf,rx_buf即可同时读写
    	buf[0] = 0xaa;
    	xfer[0].tx_buf = (unsigned long)buf;
    	xfer[0].rx_buf = (unsigned long)buf_rx;
    	xfer[0].len = 1en;
    
    	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
    	if (status < 0) {
    		perror("SPI_IOC_MESSAGE");
    		return;
    	}
    
    	printf("response(%2d, %2d): ", len, status);
    	for (bp = buf; len; len--)
    		printf(" %02x", *bp++);
    	printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    三、spidev的缺点

    使用read、write函数时,只能读、写,之二十半双工方式
    使用ioctl可以达到全双工的读写
    但是spidev有2个缺点:

    • 不支持中断
    • 只支持同步操作,不支持异步操作:就是read/write/ioctl这些函数只能执行完毕才可返回
  • 相关阅读:
    Debezium系列之:debezium把sqlserver数据库多张表的数据发送到一个kafka topic
    Unity关于GL绘制和Gizmos绘制的Matrix问题。
    让测试人头疼的这几件“小事”
    Nginx配置微服务避免actuator暴露
    GIT使用踩坑!!!我的妈妈呀,烦死了烦死了烦死了!!!
    ST表(RMQ问题)
    骑士CMS01 74cms v4.2.111 后台getshell漏洞复现
    C 语言宏 + 内联汇编实现 MIPS 系统调用
    数据标准的制定落地
    设计模式之代理模式
  • 原文地址:https://blog.csdn.net/ch122633/article/details/125946596