spi设备层
spi驱动层
spi核心层
spi收发数据流程
ioctl
write
设备平台 NXP IMX8QXP,内核版本:linux-4.14.78。
spi设备层
drivers/spi/spidev.c
使用module_init注册模块驱动,register_chrdev注册spi字符设备,主设备号153;spidev_fops实现spi字符设备的open,read,write和ioctrl函数;class_create创建spidev_class类,以后用spidev_class类来创建dev目录下的spi设备节点;spi_register_driver注册spi设备驱动。
static int __init spidev_init(void){ int status; /* Claim our 256 reserved device numbers. Then register a class * that will key udev/mdev to add/remove /dev nodes. Last,register * the driver which manages those device numbers. */ BUILD_BUG_ON(N_SPI_MINORS > 256); status = register_chrdev(SPIDEV_MAJOR,"spi",&spidev_fops); if (status < 0) return status; spidev_class = class_create(THIS_MODULE,"spidev"); if (IS_ERR(spidev_class)) { unregister_chrdev(SPIDEV_MAJOR,spidev_spi_driver.driver.name); return PTR_ERR(spidev_class); } status = spi_register_driver(&spidev_spi_driver); if (status < 0) { class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR,spidev_spi_driver.driver.name); } return status;}module_init(spidev_init); Linux在核心中,设备和驱动是分开的,设备驱动和设备驱动信息,设备和设备信息也是分开的,设备信息是dts描述、使用文件match进行匹配。
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,}; dts文件编译成dtb,dtb当内核启动时,将其加载到内存中并分析到相应的结构中。设备驱动注册时dt中搜索compatible匹配字符串。
static const struct of_device_id spidev_dt_ids[] = { { .compatible = "rohm,dh2228fv" , { .compatible = "lineartechnology,ltc2488" , { .compatible = "ge,achc" , { .compatible = "semtech,sx1301" , { .compatible = "spi3,genvict3"}, { .compatible = "xdja,xdja_spi2_0"}, { .compatible = "siliconlabs,si3210" , {},MODULE_DEVICE_TABLE(of,spidev_dt_ids); arch/arm64/boot/dts/freescale/fsl-imx8x-mek.dtsi配置在文件中spi2设备节点,设备驱动"xdja,xdja_spi2_0",因此,探针函数将在这里成功匹配,然后开始执行spidev_probe。
&lpspi2 { #address-cells = <1>; #size-cells = <0>; fsl,spi-num-chipselects = <1>; cs-gpio = <&gpio1 0 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lpspi2>; assigned-clock-rates = <128000000>; status = "okay"; spidev2_0:xdja_spi2_0@ #address-cells = <1>; #size-cells = <0>; compatible = "xdja,xdja_spi2_0"; spi-max-frequency = <50000000>; reg = <0>; status = "okay"; };}; spidev_probe函数,of_match_device检查驱动是否匹配,然后初始化锁,获取次版本号后使用device_create创建设备节点:spidev2.0;传入的spi指针在spi.c函数已经在文件中使用of_register_spi_device设备节点信息的分析。max_speed_hz设置设备驱动数据实际上是spi->dev->driver_data =spidev,创建了spidev各种赋值结构体指针后传递driver_data后续使用。
crw------- 1 root root 153Jan 1000:00 dev/spidev2.0 static int spidev_probe(struct spi_device *spi){ struct spidev_data *spidev; int status; unsigned long minor; /* * spidev should never be referenced in DT without a specific * compatible string,it is a Linux implementation thing * rather than a description of the hardware. */ if (spi->dev.of_node && !of_match_device(spidev_dt_ids,&spi->dev)) { dev_err(&spi->dev,"buggy DT: spidev listed directly in DT\n"); WARN_ON(spi->dev.of_node && !of_match_device(spidev_dt_ids,&spi->dev)); } spidev_probe_acpi(spi); /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev),GFP_KERNEL); if (!spidev) return -ENOMEM; /* Initialize the driver data */ spidev->spi = spi; spin_lock_init(&spidev->spi_lock); mutex_init(&spidev->buf_lock); INIT_LIST_HEAD(&spidev->device_entry); /* If we can allocate a minor number,hook up this device. * Reusing minors is fine so long as udev or mdev is working. */ mutex_lock(&device_list_lock); minor = find_first_zero_bit(minors, N_SPI_MINORS); if (minor < N_SPI_MINORS) { struct device *dev; spidev->devt = MKDEV(SPIDEV_MAJOR, minor); dev = device_create(spidev_class, &spi->dev, spidev->devt, spidev, "spidev%d.%d", spi->master->bus_num, spi->chip_select); status = PTR_ERR_OR_ZERO(dev); } else { dev_dbg(&spi->dev, "no minor number available!\n"); status = -ENODEV; } if (status == 0) { set_bit(minor, minors); list_add(&spidev->device_entry, &device_list); } mutex_unlock(&device_list_lock); spidev->speed_hz = spi->max_speed_hz; if (status == 0) spi_set_drvdata(spi, spidev); else kfree(spidev); return status;}
dts中配置了几个spi设备节点,那么在注册设备驱动时就会匹配几次,就会触发几次探针spidev_probe函数几次。
spi驱动层
drivers/spi/spi-fsl-lpspi.c
使用module_platform_driver在platform总线上注册,spi驱动是操作硬件寄存器的层面,所以匹配的驱动就是操作controller。
static struct platform_driver fsl_lpspi_driver = { .driver = { .name = DRIVER_NAME, .of_match_table = fsl_lpspi_dt_ids, .pm = &fsl_lpspi_pm_ops, }, .probe = fsl_lpspi_probe, .remove = fsl_lpspi_remove,};module_platform_driver(fsl_lpspi_driver);
match匹配.compatible = "fsl,imx7ulp-spi"字符,lpspi2描述了CPU spi控制器的基本信息,包括寄存器基地址和长度,中断号,时钟信息。
static const struct of_device_id fsl_lpspi_dt_ids[] = { { .compatible = "fsl,imx7ulp-spi", }, { /* sentinel */ }};MODULE_DEVICE_TABLE(of, fsl_lpspi_dt_ids);
lpspi2: lpspi@5a020000 { compatible = "fsl,imx7ulp-spi"; reg = <0x0 0x5a020000 0x0 0x10000>; interrupts = <GIC_SPI 218 IRQ_TYPE_LEVEL_HIGH>; interrupt-parent = <&gic>; clocks = <&clk IMX8QXP_SPI2_CLK>, <&clk IMX8QXP_SPI2_IPG_CLK>; clock-names = "per", "ipg"; assigned-clocks = <&clk IMX8QXP_SPI2_CLK>; assigned-clock-rates = <20000000>; power-domains = <&pd_dma_lpspi2>; dma-names = "tx","rx"; dmas = <&edma2 5 0 0>, <&edma2 4 0 1>; status = "disabled"; };&lpspi2 { #address-cells = <1>; #size-cells = <0>; fsl,spi-num-chipselects = <1>; cs-gpio = <&gpio1 0 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lpspi2>; assigned-clock-rates = <128000000>; status = "okay";
匹配"fsl,imx7ulp-spi"字符后执行fsl_lpspi_probe探针函数,首先申请spi_controller,同时在把申请的fsl_lpspi_data放在spi_controller后面,然后设置相应的参数;fsl_lpspi = controller->dev->driver_data,这样fsl_lpspi与controller关联起来,pdev->dev->driver_data = fsl_lpspi也关联起来。设置controller的参数和函数指针。
struct spi_controller *__spi_alloc_controller(struct device *dev, unsigned int size, bool slave){ struct spi_controller *ctlr; if (!dev) return NULL; ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL); if (!ctlr) return NULL; device_initialize(&ctlr->dev); ctlr->bus_num = -1; ctlr->num_chipselect = 1; ctlr->slave = slave; if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave) ctlr->dev.class = &spi_slave_class; else ctlr->dev.class = &spi_master_class; ctlr->dev.parent = dev; pm_suspend_ignore_children(&ctlr->dev, true); spi_controller_set_devdata(ctlr, &ctlr[1]); return ctlr;}
fsl_lpspi_data是NXP spi驱动自己实现的特有结构体,后续会用到。
指定spi_controller的transfer_one_message函数,使用NXP的message方式。
controller->transfer_one_message = fsl_lpspi_transfer_one_msg;
platform_get_resource获取IO资源,得到操作寄存器的基地址,devm_request_irq申请中断函数,spi走的中断处理,有数据时执行中断函数fsl_lpspi_isr。
ret = devm_request_irq(&pdev->dev, irq, fsl_lpspi_isr, 0, dev_name(&pdev->dev), fsl_lpspi);
获取时钟并使能时钟,获取tx/rxfifosize并设置。
static int fsl_lpspi_probe(struct platform_device *pdev){ struct fsl_lpspi_data *fsl_lpspi; struct spi_controller *controller; struct resource *res; int ret, irq; u32 temp; if (of_property_read_bool((&pdev->dev)->of_node, "spi-slave")) controller = spi_alloc_slave(&pdev->dev, sizeof(struct fsl_lpspi_data)); else controller = spi_alloc_master(&pdev->dev, sizeof(struct fsl_lpspi_data)); if (!controller) return -ENOMEM; platform_set_drvdata(pdev, controller); controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32); controller->bus_num = pdev->id; fsl_lpspi = spi_controller_get_devdata(controller); fsl_lpspi->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, fsl_lpspi); fsl_lpspi->is_slave = of_property_read_bool((&pdev->dev)->of_node, "spi-slave"); controller->transfer_one_message = fsl_lpspi_transfer_one_msg; controller->prepare_transfer_hardware = lpspi_prepare_xfer_hardware; controller->unprepare_transfer_hardware = lpspi_unprepare_xfer_hardware; controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; controller->flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX; controller->dev.of_node = pdev->dev.of_node; controller->bus_num = pdev->id; controller->slave_abort = fsl_lpspi_slave_abort; init_completion(&fsl_lpspi->xfer_done); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); fsl_lpspi->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(fsl_lpspi->base)) { ret = PTR_ERR(fsl_lpspi->base); goto out_controller_put; } irq = platform_get_irq(pdev, 0); if (irq < 0) { ret = irq; goto out_controller_put; } ret = devm_request_irq(&pdev->dev, irq, fsl_lpspi_isr, 0, dev_name(&pdev->dev), fsl_lpspi); if (ret) { dev_err(&pdev->dev, "can't get irq%d: %d\n", irq, ret); goto out_controller_put; } fsl_lpspi->clk_per = devm_clk_get(&pdev->dev, "per"); if (IS_ERR(fsl_lpspi->clk_per)) { ret = PTR_ERR(fsl_lpspi->clk_per); goto out_controller_put; } fsl_lpspi->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); if (IS_ERR(fsl_lpspi->clk_ipg)) { ret = PTR_ERR(fsl_lpspi->clk_ipg); goto out_controller_put; } /* enable the clock */ ret = fsl_lpspi_init_rpm(fsl_lpspi); if (ret) goto out_controller_put; ret = pm_runtime_get_sync(fsl_lpspi->dev); if (ret < 0) { dev_err(fsl_lpspi->dev, "failed to enable clock\n"); return ret; } temp = readl(fsl_lpspi->base + IMX7ULP_PARAM); fsl_lpspi->txfifosize = 1 << (temp & 0x0f); fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f); ret = devm_spi_register_controller(&pdev->dev, controller); if (ret < 0) { dev_err(&pdev->dev, "spi_register_controller error.\n"); goto out_controller_put; } return 0;out_controller_put: spi_controller_put(controller); return ret;}
根据IMX7ULP_IER寄存器的Transmit Data Interrupt Enable 传输数据中断使能标志位,和IMX7ULP_SR寄存器的Transmit Data Flag ,来判断是否需要发送数据。
static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id){ u32 temp_SR, temp_IER; struct fsl_lpspi_data *fsl_lpspi = dev_id; temp_IER = readl(fsl_lpspi->base + IMX7ULP_IER); fsl_lpspi_intctrl(fsl_lpspi, 0); temp_SR = readl(fsl_lpspi->base + IMX7ULP_SR); fsl_lpspi_read_rx_fifo(fsl_lpspi); if ((temp_SR & SR_TDF) && (temp_IER & IER_TDIE)) { fsl_lpspi_write_tx_fifo(fsl_lpspi); return IRQ_HANDLED; } if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) { writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR); complete(&fsl_lpspi->xfer_done); return IRQ_HANDLED; } return IRQ_NONE;}
中断处理函数中,判断IMX7ULP_RSR寄存器的bit[1] RX FIFO is empty,不为空就一直读取数据。
static void fsl_lpspi_read_rx_fifo(struct fsl_lpspi_data *fsl_lpspi){ while (!(readl(fsl_lpspi->base + IMX7ULP_RSR) & RSR_RXEMPTY)) fsl_lpspi->rx(fsl_lpspi);}
fsl_lpspi_transfer_one_msg函数中的fsl_lpspi_setup_transfer里面有设置fsl_lpspi->rx和fsl_lpspi->tx回调函数。在进行全双工收发数据时,通过使用msg加入到controller的queue中的方式,最终调用内核线程函数spi_pump_messages,最终调用到fsl_lpspi_transfer_one里面,这里最后调用fsl_lpspi->tx进行数据发送,接收数据其实是用的中断回调函数,不过fsl_lpspi->rx在数据收发时已经设置了,所以总结来说收发的时序不确定的。
static void fsl_lpspi_setup_transfer(struct spi_device *spi, struct spi_transfer *t){ struct fsl_lpspi_data *fsl_lpspi = spi_controller_get_devdata(spi->controller); fsl_lpspi->config.mode = spi->mode; fsl_lpspi->config.bpw = t ? t->bits_per_word : spi->bits_per_word; fsl_lpspi->config.speed_hz = t ? t->speed_hz : spi->max_speed_hz; fsl_lpspi->config.chip_select = spi->chip_select; if (!fsl_lpspi->config.speed_hz) fsl_lpspi->config.speed_hz = spi->max_speed_hz; if (!fsl_lpspi->config.bpw) fsl_lpspi->config.bpw = spi->bits_per_word; /* Initialize the functions for transfer */ if (fsl_lpspi->config.bpw <= 8) { fsl_lpspi->rx = fsl_lpspi_buf_rx_u8; fsl_lpspi->tx = fsl_lpspi_buf_tx_u8; } else if (fsl_lpspi->config.bpw <= 16) { fsl_lpspi->rx = fsl_lpspi_buf_rx_u16; fsl_lpspi->tx = fsl_lpspi_buf_tx_u16; } else { fsl_lpspi->rx = fsl_lpspi_buf_rx_u32; fsl_lpspi->tx = fsl_lpspi_buf_tx_u32; }
devm_spi_register_controller函数进入到spi.c文件中注册spi。参考链接:内核 kthread_worker 和 kthread_work 机制_爱洋葱的博客-CSDN博客_kthread_queue_work
注册内核线程worker,初始化一个内核work并设置执行函数spi_pump_messages,最后把work挂到worker上面并设置执行函数。__spi_pump_messages会把queue队列里面的msg取出来进行数据发送。
devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr) spi_register_controller(ctlr); spi_controller_initialize_queue(ctlr) ctlr->transfer = spi_queued_transfer; spi_init_queue(ctlr) 1、注册kthread_worker kthread_init_worker(&ctlr->kworker); 2、为kthread_worker创建一个内核线程来处理 work ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, "%s", dev_name(&ctlr->dev)); 3、初始化kthread_work,设置work执行函数spi_pump_messages kthread_init_work(&ctlr->pump_messages, spi_pump_messages); spi_pump_messages(struct kthread_work *work) __spi_pump_messages(ctlr, true); ctlr->transfer_one_message(ctlr, ctlr->cur_msg) spi_start_queue(ctlr) 4、把work挂到worker上 kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
把spi_controller的链表挂在全局的spi_controller_list链表上面,
static LIST_HEAD(board_list);static LIST_HEAD(spi_controller_list); list_add_tail(&ctlr->list, &spi_controller_list);
搜索board_list链表,查看是否有相同的bus_num号,然后spi_new_device添加新的设备;但是没有哪里在添加boardinfo这种老版本的文件设备描述方式,所以这里应该不会执行。
list_for_each_entry(bi, &board_list, list) spi_match_controller_to_boardinfo(ctlr, &bi->board_info);
of_register_spi_devices函数中会根据dts中的node节点信息,查看是否需要添加设备,里面的实现基本与spi_new_device一致。
static void of_register_spi_devices(struct spi_controller *ctlr){ struct spi_device *spi; struct device_node *nc; if (!ctlr->dev.of_node) return; for_each_available_child_of_node(ctlr->dev.of_node, nc) { if (of_node_test_and_set_flag(nc, OF_POPULATED)) continue; spi = of_register_spi_device(ctlr, nc); if (IS_ERR(spi)) { dev_warn(&ctlr->dev, "Failed to create SPI device for %pOF\n", nc); of_node_clear_flag(nc, OF_POPULATED); } }}
spi核心层
drivers/spi/spi.c
postcore_initcall(spi_init);在module_init和module_platform_driver之前执行,bus_register
和class_register分别注册。
struct bus_type spi_bus_type = { .name = "spi", .dev_groups = spi_dev_groups, .match = spi_match_device, .uevent = spi_uevent,};EXPORT_SYMBOL_GPL(spi_bus_type);static struct class spi_master_class = { .name = "spi_master", .owner = THIS_MODULE, .dev_release = spi_controller_release, .dev_groups = spi_master_groups,};static struct class spi_slave_class = { .name = "spi_slave", .owner = THIS_MODULE, .dev_release = spi_controller_release, .dev_groups = spi_slave_groups,};
static int __init spi_init(void){ int status; buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); if (!buf) { status = -ENOMEM; goto err0; } status = bus_register(&spi_bus_type); if (status < 0) goto err1; status = class_register(&spi_master_class); if (status < 0) goto err2; if (IS_ENABLED(CONFIG_SPI_SLAVE)) { status = class_register(&spi_slave_class); if (status < 0) goto err3; } if (IS_ENABLED(CONFIG_OF_DYNAMIC)) WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); if (IS_ENABLED(CONFIG_ACPI)) WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier)); return 0;err3: class_unregister(&spi_master_class);err2: bus_unregister(&spi_bus_type);err1: kfree(buf); buf = NULL;err0: return status;}
spi设备与驱动匹配方式,通过.match = spi_match_device函数进行匹配,使用驱动中的.of_match_table = fsl_lpspi_dt_ids, 里面定义了compatible,{ .compatible = "fsl,imx7ulp-spi", },
使用设备树中的compatible与驱动名进行匹配。
static int spi_match_device(struct device *dev, struct device_driver *drv){ const struct spi_device *spi = to_spi_device(dev); const struct spi_driver *sdrv = to_spi_driver(drv); /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI */ if (acpi_driver_match_device(dev, drv)) return 1; if (sdrv->id_table) return !!spi_match_id(sdrv->id_table, spi); return strcmp(spi->modalias, drv->name) == 0;}of_driver_match_device of_match_device(drv->of_match_table, dev) of_match_node(const struct of_device_id *matches, dev->of_node); __of_match_nodestaticconst struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node){ const struct of_device_id *best_match = NULL; int score, best_score = 0; if (!matches) return NULL; for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) { score = __of_device_is_compatible(node, matches->compatible, matches->type, matches->name); if (score > best_score) { best_match = matches; best_score = score; } } return best_match;}
name和type都为NULL,只有.compatible = "fsl,imx7ulp-spi",
static int __of_device_is_compatible(const struct device_node *device, const char *compat, const char *type, const char *name){ struct property *prop; const char *cp; int index = 0, score = 0; /* Compatible match has highest priority */ if (compat && compat[0]) { prop = __of_find_property(device, "compatible", NULL); for (cp = of_prop_next_string(prop, NULL); cp; cp = of_prop_next_string(prop, cp), index++) { if (of_compat_cmp(cp, compat, strlen(compat)) == 0) { score = INT_MAX/2 - (index << 2); break; } } if (!score) return 0; }
系统中查看注册的设备信息。
root@OpenWrt:/# ls /sys/bus/spi/ -ldrwxr-xr-x 2 root root 0 Jan 1 00:00 devicesdrwxr-xr-x 10 root root 0 Jan 1 00:08 drivers-rw-r--r-- 1 root root 4096 Jan 1 00:08 drivers_autoprobe--w------- 1 root root 4096 Jan 1 00:08 drivers_probe--w------- 1 root root 4096 Jan 1 00:08 ueventroot@OpenWrt:/# ls /sys/class/spidev/ -llrwxrwxrwx 1 root root 0 Jan 1 00:00 spidev2.0 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2/spi2.0/spidev/spidev2.0root@OpenWrt:/# ls /sys/class/spi_master/ -llrwxrwxrwx 1 root root 0 Jan 1 00:00 spi2 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2lrwxrwxrwx 1 root root 0 Jan 1 00:00 spi3 -> ../../devices/platform/5a030000.lpspi/spi_master/spi3root@OpenWrt:/# ls /sys/class/spi_slave/ -l
spi收发数据流程
ioctl
spidev_fops提供给上层文件系统调用,主要查看open,ioctl,read,write。
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,};
read和write只支持半双工,ioctl支持全双工,应用程序收发数据一般使用ioctl系统调用,使用SPI_IOC_MESSAGE命令,下面应用程序进行spi环回测试代码。
static const char *device = "/dev/spidev2.0";static uint8_t mode;static uint8_t bits = 8;static uint32_t speed = 500000;static uint16_t delay; static void transfer(int fd){ int ret; uint8_t tx[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x95, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD, 0xF0, 0x0D, }; uint8_t rx[ARRAY_SIZE(tx)] = {0, }; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = ARRAY_SIZE(tx), .delay_usecs = delay, .speed_hz = speed, .bits_per_word = bits, }; ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 1) pabort("can't send spi message"); for (ret = 0; ret < ARRAY_SIZE(tx); ret++) { if (!(ret % 6)) puts(""); printf("%.2X ", rx[ret]); } puts("");}
spidev_get_ioc_message函数检查参数,memdup_user(u_ioc, tmp)分配内存拷贝用户程序的spi_ioc_transfer结构体数据到ioc。
spidev_message函数进行数据处理和收发。
static longspidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ unsigned n_ioc; struct spi_ioc_transfer *ioc; 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;
申请spi_transfer内存,spidev->tx_buffer和spidev->rx_buffer在spidev_open函数中申请内存,接收数据使用k_tmp->rx_buf = rx_buf = spidev->rx_buffer,收到数据拷贝时rx_buf = spidev->rx_buffer;,在函数最后使用copy_to_user拷贝rx_buf到u_tmp->rx_buf用户程序的buf中。
发送数据k_tmp->tx_buf = tx_buf = spidev->tx_buffer,使用copy_from_user函数把用户数据拷贝到tx_buf中,最后把spi_ioc_transfer信息赋值给spi_transfer,然后spi_message_add_tail(k_tmp, &msg);把spi_transfer挂在spi_message的transfers链表中。
最后使用spidev_sync(spidev, &msg)把msg加入到spi_controller的queue队列中。
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_transfer *k_tmp; struct spi_ioc_transfer *u_tmp; unsigned n, total, tx_total, rx_total; u8 *tx_buf, *rx_buf; int status = -EFAULT; spi_message_init(&msg); k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); if (k_xfers == NULL) return -ENOMEM; /* Construct spi_message, copying any tx data to bounce buffer. * We walk the array of user-provided transfers, using each one * to initialize a kernel version of the same transfer. */ tx_buf = spidev->tx_buffer; rx_buf = spidev->rx_buffer; total = 0; tx_total = 0; rx_total = 0; 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; /* Since the function returns the total length of transfers * on success, restrict the total to positive int values to * avoid the return value looking like an error. Also check * each transfer length to avoid arithmetic overflow. */ if (total > INT_MAX || k_tmp->len > INT_MAX) { status = -EMSGSIZE; goto done; } if (u_tmp->rx_buf) { /* this transfer needs space in RX bounce buffer */ rx_total += k_tmp->len; if (rx_total > bufsiz) { status = -EMSGSIZE; goto done; } k_tmp->rx_buf = rx_buf; rx_buf += k_tmp->len; } if (u_tmp->tx_buf) { /* this transfer needs space in TX bounce buffer */ tx_total += k_tmp->len; if (tx_total > bufsiz) { status = -EMSGSIZE; goto done; } k_tmp->tx_buf = tx_buf; if (copy_from_user(tx_buf, (const u8 __user *) (uintptr_t) u_tmp->tx_buf, u_tmp->len)) goto done; tx_buf += k_tmp->len; } k_tmp->cs_change = !!u_tmp->cs_change; k_tmp->tx_nbits = u_tmp->tx_nbits; k_tmp->rx_nbits = u_tmp->rx_nbits; 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; if (!k_tmp->speed_hz) k_tmp->speed_hz = spidev->speed_hz;#ifdef VERBOSE dev_dbg(&spidev->spi->dev, " xfer len %u %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 */ rx_buf = spidev->rx_buffer; for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { if (u_tmp->rx_buf) { if (copy_to_user((u8 __user *) (uintptr_t) u_tmp->rx_buf, rx_buf, u_tmp->len)) { status = -EFAULT; goto done; } rx_buf += u_tmp->len; } } status = total;done: kfree(k_xfers); return status;}
函数spidev_sync调用流程:
spidev_sync(spidev, &msg) spi = spidev->spi; spi_sync(spi, message) __spi_sync(spi, message) __spi_queued_transfer(spi, message, false) struct spi_controller *ctlr = spi->controller; list_add_tail(&msg->queue, &ctlr->queue);
write
write函数,拷贝用户层数据spidev->tx_buffer,tx_buffer在open函数中申请内存4096字节,使用sync同步传输,
/* Write-only message with current device setup */static ssize_tspidev_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;}
最终加入到spi->controller->queue队列里面,然后被内核线程函数取出数据发送。
static inline ssize_tspidev_sync_write(struct spidev_data *spidev, size_t len){ struct spi_transfer t = { .tx_buf = spidev->tx_buffer, .len = len, .speed_hz = spidev->speed_hz, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); return spidev_sync(spidev, &m);}
参考文章链接:
Linux SPI 驱动分析(1)— 结构框架_爱洋葱的博客-CSDN博客_linux spi驱动框架
Linux SPI 驱动分析(2)— 框架层源码分析_爱洋葱的博客-CSDN博客_spi_message_init