【驱动】SPI驱动分析(四)-关键API解析
关键API
设备树
设备树解析
我们以Firefly 的SPI demo 分析下dts中对spi的描述:
/* Firefly SPI demo */ &spi1 { spi_demo: spi-demo@00{ status = "okay"; compatible = "firefly,rk3399-spi"; reg = <0x00>; spi-max-frequency = <48000000>; /* rk3399 driver support SPI_CPOL | SPI_CPHA | SPI_CS_HIGH */ //spi-cpha; /* SPI mode: CPHA=1 */ //spi-cpol; /* SPI mode: CPOL=1 */ //spi-cs-high; }; }; &spidev0 { status = "disabled"; };
status
:如果要启用 SPI,则设为okay
,如不启用,设为disable
。spi-demo@00
:由于本例子使用 CS0,故此处设为00
,如果使用 CS1,则设为01
。compatible
:这里的属性必须与驱动中的结构体:of_device_id
中的成员compatible
保持一致。reg
:此处与spi-demo@00
保持一致,本例设为:0x00。spi-max-frequency
:此处设置 spi 使用的最高频率。Firefly-RK3399 最高支持 48000000。spi-cpha,spi-cpol
:SPI 的工作模式在此设置,本例所用的模块 SPI 工作模式为SPI_MODE_0
或者SPI_MODE_3
,这里我们选用SPI_MODE_0
,如果使用SPI_MODE_3
,spi_demo 中打开spi-cpha
和spi-cpol
即可。spidev0
: 由于spi_demo
与spidev0
使用一样的硬件资源,需要把spidev0
关掉才能打开spi_demo
设备树API解析
of_spi_register_master
of_spi_register_master
主要目的是从设备树中获取SPI主控制器的GPIO引脚编号信息,并将其存储在主控制器的cs_gpios
数组中。通过读取设备树中的属性,该函数确定了主控制器具有多少个芯片选择线,并为其分配了足够的内存来存储GPIO引脚编号。源码位于kernel\drivers\spi\spi.c
static int of_spi_register_master(struct spi_master *master) { int nb, i, *cs; struct device_node *np = master->dev.of_node; if (!np) return 0; // 获取设备节点中名为"cs-gpios"的GPIO数量 nb = of_gpio_named_count(np, "cs-gpios"); // 将获取到的GPIO数量与主控制器的num_chipselect取较大值,并赋值给主控制器的num_chipselect master->num_chipselect = max_t(int, nb, master->num_chipselect); /* Return error only for an incorrectly formed cs-gpios property */ // 如果"cs-gpios"属性未定义或定义错误,则返回0 if (nb == 0 || nb == -ENOENT) return 0; // 如果获取GPIO数量出错,则返回错误代码 else if (nb < 0) return nb; // 分配内存以保存GPIO的引脚编号 cs = devm_kzalloc(&master->dev, sizeof(int) * master->num_chipselect, GFP_KERNEL); master->cs_gpios = cs; if (!master->cs_gpios) return -ENOMEM; // 初始化所有引脚编号为-ENOENT for (i = 0; i < master->num_chipselect; i++) cs[i] = -ENOENT; // 从设备节点中获取"cs-gpios"属性对应的每个GPIO引脚编号,并存储到cs数组中 for (i = 0; i < nb; i++) cs[i] = of_get_named_gpio(np, "cs-gpios", i); // 返回成功状态码 return 0; }
-
函数首先获取设备节点中名为"cs-gpios"的GPIO数量,并将其与主控制器的
num_chipselect
(芯片选择线数量)进行比较,取较大值。 -
然后,函数会为主控制器分配足够的内存来存储GPIO的引脚编号,并将分配的内存地址赋值给
cs_gpios
指针。 -
接下来,函数会将分配的内存初始化为
-ENOENT
,然后从设备节点中获取cs-gpios
属性对应的每个GPIO引脚编号,并将其存储到分配的内存中。 -
最后,函数返回状态码,表示注册过程的成功与否。
of_register_spi_devices
of_register_spi_devices
作用是将子节点设备注册到SPI总线上。源码位于kernel\drivers\spi\spi.c
/** * of_register_spi_devices() - Register child devices onto the SPI bus * @master: Pointer to spi_master device * * Registers an spi_device for each child node of master node which has a 'reg' * property. */ static void of_register_spi_devices(struct spi_master *master) { struct spi_device *spi; struct device_node *nc; if (!master->dev.of_node) return; for_each_available_child_of_node(master->dev.of_node, nc) { spi = of_register_spi_device(master, nc); if (IS_ERR(spi)) dev_warn(&master->dev, "Failed to create SPI device for %s\n", nc->full_name); } }
-
首先检查主控制器的设备节点是否存在,如果不存在(为空),则直接返回,不执行任何操作。
-
通过
for_each_available_child_of_node
宏遍历主控制器设备节点的每个可用子节点,其中nc
是当前子节点的指针。 -
在每次迭代中,函数调用
of_register_spi_device
来为当前子节点创建一个spi_device
对象,并将其赋值给spi
。 -
如果
of_register_spi_device
函数返回的spi
是一个错误指针(通过IS_ERR
宏检查),则打印警告信息,表示未能为该子节点创建SPI设备。
of_find_spi_master_by_node
of_find_spi_master_by_node
作用是根据设备节点查找对应的SPI主控制器。源码位于drivers\spi\spi.c
/* the spi masters are not using spi_bus, so we find it with another way */ static struct spi_master *of_find_spi_master_by_node(struct device_node *node) { struct device *dev; dev = class_find_device(&spi_master_class, NULL, node, __spi_of_master_match); if (!dev) return NULL; /* reference got in class_find_device */ return container_of(dev, struct spi_master, dev); }
-
首先声明一个
struct device
类型的指针dev
。 -
通过调用
class_find_device
函数来从spi_master_class
类中查找与给定设备节点node
匹配的设备。其中,&spi_master_class
是指向spi_master_class
的指针,NULL
表示在整个类中查找,node
表示要匹配的设备节点,__spi_of_master_match
是一个匹配函数。 -
如果未找到匹配的设备,即
dev
为空,则返回NULL
表示未找到对应的SPI主控制器。 -
如果找到匹配的设备,说明找到了对应的SPI主控制器,此时通过
container_of
宏计算出spi_master
结构体的指针。它利用了设备结构体中的成员偏移来计算包含该成员的结构体的指针。 -
最后,返回计算得到的
spi_master
指针。
of_spi_notify
of_spi_notify
作为SPI设备树变更的通知回调函数。源码位于drivers\spi\spi.c
static int of_spi_notify(struct notifier_block *nb, unsigned long action, void *arg) { struct of_reconfig_data *rd = arg; struct spi_master *master; struct spi_device *spi; // 通过调用of_reconfig_get_state_change函数获取设备树变更的状态 switch (of_reconfig_get_state_change(action, arg)) { case OF_RECONFIG_CHANGE_ADD: // 根据父节点查找对应的SPI主控制器 master = of_find_spi_master_by_node(rd->dn->parent); if (master == NULL) return NOTIFY_OK; /* not for us */ // 为设备注册SPI设备 spi = of_register_spi_device(master, rd->dn); put_device(&master->dev); if (IS_ERR(spi)) { pr_err("%s: failed to create for '%s'\n", __func__, rd->dn->full_name); return notifier_from_errno(PTR_ERR(spi)); } break; case OF_RECONFIG_CHANGE_REMOVE: /* find our device by node */ // 根据节点查找对应的SPI设备 spi = of_find_spi_device_by_node(rd->dn); if (spi == NULL) return NOTIFY_OK; /* no? not meant for us */ /* unregister takes one ref away */ // 注销SPI设备 spi_unregister_device(spi); /* and put the reference of the find */ // 释放对设备的引用 put_device(&spi->dev); break; } return NOTIFY_OK; }
-
该函数接受一个通知器块(
notifier_block
)指针nb
,一个表示动作的无符号长整型action
,以及一个指向设备树重配置数据的指针arg
。 -
将
arg
转换为struct of_reconfig_data
类型的指针rd
,用于访问设备树重配置数据。 -
通过调用
of_reconfig_get_state_change
函数,根据传入的action
和arg
参数获取设备树变更的状态。 -
根据状态进行不同的操作:
-
如果状态为
OF_RECONFIG_CHANGE_ADD
,表示设备被添加:
- 通过设备节点的父节点找到对应的SPI主控制器,将其赋值给
master
。 - 如果未找到对应的主控制器,返回
NOTIFY_OK
表示该设备不属于我们处理的范围。 - 调用
of_register_spi_device
函数为设备注册SPI设备,并将返回的spi_device
指针赋值给spi
。 - 释放对主控制器设备的引用计数,即调用
put_device
函数。 - 如果注册失败,打印错误信息并返回相应的错误码。
- 如果状态为
OF_RECONFIG_CHANGE_REMOVE
,表示设备被移除:
- 根据设备节点查找对应的SPI设备,将其赋值给
spi
。 - 如果未找到对应的SPI设备,返回
NOTIFY_OK
表示该设备不属于我们处理的范围。 - 调用
spi_unregister_device
函数注销SPI设备。 - 释放对设备的引用计数,即调用
put_device
函数。
- 最后,函数返回
NOTIFY_OK
表示通知处理成功。
SPI子系统初始化
spi_init
spi_init
函数的作用是初始化SPI子系统。它分配一个内核内存缓冲区,注册SPI总线类型和SPI主控制器类,并在需要时注册设备树重配置的通知回调。函数定义在drivers/spi/spi.c
文件中:
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_OF_DYNAMIC)) WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); return 0; err2: bus_unregister(&spi_bus_type); err1: kfree(buf); buf = NULL; err0: return status; }
-
该函数首先声明一个整数类型的变量
status
,用于存储函数执行过程中的状态。 -
接下来,通过调用
kmalloc
函数为SPI子系统分配一个大小为SPI_BUFSIZ
的内核内存缓冲区,并将返回的指针赋值给buf
。 -
如果缓冲区分配失败(
buf
为空指针),则将status
设置为-ENOMEM
表示内存分配失败,并跳转到err0
标签处进行错误处理。 -
接下来,通过调用
bus_register
函数注册SPI总线类型,将其添加到系统总线列表中。如果注册失败,将status
设置为返回的错误码,并跳转到err1
标签处进行错误处理。 -
然后,通过调用
class_register
函数注册SPI主控制器类,将其添加到系统设备类列表中。如果注册失败,将status
设置为返回的错误码,并跳转到err2
标签处进行错误处理。 -
如果系统启用了动态设备树功能(
CONFIG_OF_DYNAMIC
宏定义存在),则调用of_reconfig_notifier_register
函数注册设备树重配置的通知回调函数spi_of_notifier
。 -
最后,如果所有的初始化操作都成功执行,函数返回0表示初始化成功。
-
如果在初始化过程中发生错误,将依次进行错误处理:注销SPI总线类型、释放缓冲区内存,然后返回存储的错误码
status
。
SPI控制器注册
spi_register_master
spi_register_master
函数用于将SPI主控制器注册到SPI总线上,以便与SPI设备进行通信。函数定义在drivers/spi/spi.c
文件中:
SPI主控制器通过某种非SPI总线(如平台总线)连接到其驱动程序。在驱动程序的probe()
函数的最后阶段,会调用spi_register_master
函数来完成与SPI总线的连接。
SPI控制器使用特定于板级(通常是特定于SoC)的总线编号,并将这些编号与芯片选择编号相结合进行板级特定的设备寻址。由于SPI不直接支持动态设备识别,因此需要使用配置表来告知哪个芯片位于哪个地址上。
/** * spi_register_master - register SPI master controller * @master: initialized master, originally from spi_alloc_master() * Context: can sleep * * SPI master controllers connect to their drivers using some non-SPI bus, * such as the platform bus. The final stage of probe() in that code * includes calling spi_register_master() to hook up to this SPI bus glue. * * SPI controllers use board specific (often SOC specific) bus numbers, * and board-specific addressing for SPI devices combines those numbers * with chip select numbers. Since SPI does not directly support dynamic * device identification, boards need configuration tables telling which * chip is at which address. * * This must be called from context that can sleep. It returns zero on * success, else a negative error code (dropping the master's refcount). * After a successful return, the caller is responsible for calling * spi_unregister_master(). * * Return: zero on success, else a negative error code. */ int spi_register_master(struct spi_master *master) { static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1); struct device *dev = master->dev.parent; struct boardinfo *bi; int status = -ENODEV; int dynamic = 0; if (!dev) return -ENODEV; status = of_spi_register_master(master); if (status) return status; /* even if it's just one always-selected device, there must * be at least one chipselect */ if (master->num_chipselect == 0) return -EINVAL; if ((master->bus_num < 0) && master->dev.of_node) master->bus_num = of_alias_get_id(master->dev.of_node, "spi"); /* convention: dynamically assigned bus IDs count down from the max */ if (master->bus_num < 0) { /* FIXME switch to an IDR based scheme, something like * I2C now uses, so we can't run out of "dynamic" IDs */ master->bus_num = atomic_dec_return(&dyn_bus_id); dynamic = 1; } INIT_LIST_HEAD(&master->queue); spin_lock_init(&master->queue_lock); spin_lock_init(&master->bus_lock_spinlock); mutex_init(&master->bus_lock_mutex); master->bus_lock_flag = 0; init_completion(&master->xfer_completion); if (!master->max_dma_len) master->max_dma_len = INT_MAX; /* register the device, then userspace will see it. * registration fails if the bus ID is in use. */ dev_set_name(&master->dev, "spi%u", master->bus_num); status = device_add(&master->dev); if (status < 0) goto done; dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev), dynamic ? " (dynamic)" : ""); /* If we're using a queued driver, start the queue */ if (master->transfer) dev_info(dev, "master is unqueued, this is deprecated\n"); else { status = spi_master_initialize_queue(master); if (status) { device_del(&master->dev); goto done; } } /* add statistics */ spin_lock_init(&master->statistics.lock); mutex_lock(&board_lock); list_add_tail(&master->list, &spi_master_list); list_for_each_entry(bi, &board_list, list) spi_match_master_to_boardinfo(master, &bi->board_info); mutex_unlock(&board_lock); /* Register devices from the device tree and ACPI */ of_register_spi_devices(master); acpi_register_spi_devices(master); done: return status; }
- 首先,通过调用
of_spi_register_master
函数,尝试根据设备树信息注册SPI主控制器。 - 检查主控制器的
num_chipselect
属性,确保至少有一个芯片选择(chip select)。 - 如果主控制器的
bus_num
属性小于0且具有有效的设备树节点,则尝试从设备树中获取总线编号。 - 如果总线编号仍然小于0,则使用动态分配的总线编号。这里使用了一个静态的原子变量
dyn_bus_id
作为计数器,从最大值开始递减分配动态总线编号。 - 初始化主控制器的一些字段和锁。使用
dev_set_name
设置主控制器设备的名称。 - 通过调用
device_add
注册主控制器设备,并将其添加到总线上,以便用户空间可以看到它。 - 如果主控制器使用队列驱动程序,则启动队列。如果使用的是非队列驱动程序,则显示警告信息。
- 添加统计信息并将主控制器添加到全局主控制器列表中。
- 从设备树中注册SPI设备。
- 返回操作的结果代码。
spi_alloc_master
spi_alloc_master
用于分配SPI主控制器(SPI master controller)的内存空间。函数定义在drivers/spi/spi.c
文件中:
/** * spi_alloc_master - allocate SPI master controller * @dev: the controller, possibly using the platform_bus * @size: how much zeroed driver-private data to allocate; the pointer to this * memory is in the driver_data field of the returned device, * accessible with spi_master_get_devdata(). * Context: can sleep * * This call is used only by SPI master controller drivers, which are the * only ones directly touching chip registers. It's how they allocate * an spi_master structure, prior to calling spi_register_master(). * * This must be called from context that can sleep. * * The caller is responsible for assigning the bus number and initializing * the master's methods before calling spi_register_master(); and (after errors * adding the device) calling spi_master_put() to prevent a memory leak. * * Return: the SPI master structure on success, else NULL. */ struct spi_master *spi_alloc_master(struct device *dev, unsigned size) { struct spi_master *master; if (!dev) return NULL; master = kzalloc(size + sizeof(*master), GFP_KERNEL); if (!master) return NULL; device_initialize(&master->dev); master->bus_num = -1; master->num_chipselect = 1; master->dev.class = &spi_master_class; master->dev.parent = dev; spi_master_set_devdata(master, &master[1]); return master; }
-
首先,检查传入的设备指针
dev
是否为空,如果为空则返回NULL,表示分配失败。 -
接下来,通过调用
kzalloc
函数为SPI主控制器分配内存空间,大小为size + sizeof(*master)
。其中size
是驱动程序私有数据的大小,sizeof(*master)
是spi_master
结构体本身的大小。这样做的原因是为了确保分配的内存空间足够容纳驱动程序私有数据以及spi_master
结构体本身。通过将两者的大小相加,可以得到分配所需的总内存大小。 -
如果内存分配失败,即
master
为空指针,表示分配失败,返回NULL。 -
然后,通过调用
device_initialize
函数初始化主控制器的设备结构体dev
,确保主控制器的设备结构体被正确初始化,设置默认值和状态,并分配必要的资源。 -
接着,设置主控制器的属性:
bus_num
设置为-1表示未指定总线编号,num_chipselect
设置为1表示主控制器支持的片选信号数量为1,dev.class
设置为指向spi_master_class
的指针表示主控制器的设备类。 -
最后,设置主控制器的私有数据指针,即通过调用
spi_master_set_devdata
函数将指针设置为分配的内存空间的结尾处,即&master[1]
。由于
master
是指向spi_master
结构体的指针,因此&master[1]
表示master
指针所指向的内存之后的一个位置。这种设置的目的是将私有数据指针与分配的内存空间相关联。通过将私有数据指针设置为&master[1]
,可以确保私有数据位于分配的内存空间中,以便在需要时可以通过访问该指针来访问和操作驱动程序特定的数据。 -
函数返回分配的SPI主控制器结构体的指针
master
。
SPI设备和驱动匹配
spi_match_device
spi_match_device
函数是用于在SPI总线上匹配设备和驱动程序的。函数定义在drivers/spi/spi.c
文件中:
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
函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。 - 如果设备与驱动程序未通过设备树匹配,尝试使用ACPI匹配。调用
acpi_driver_match_device
函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。 - 如果SPI驱动程序具有
id_table
字段,则使用spi_match_id
函数尝试通过ID表进行匹配。如果匹配成功,则返回1。 - 如果以上匹配方法都失败,则通过比较设备的
modalias
和驱动程序的name
字段来进行匹配。如果两者相等,则返回1;否则返回0。
驱动的四种匹配方式
设备树匹配
通过在设备树中为SPI设备和驱动程序分配节点,并在节点中指定属性和配置信息,可以实现设备与驱动程序的匹配。在设备树匹配过程中,驱动程序可以根据设备节点的属性信息来判断与其匹配的设备。
举例spi-imx.c
static const struct of_device_id spi_imx_dt_ids[] = { { .compatible = "fsl,imx6q-spi", .data = (void *)&imx6q_spi_pdata }, { }, }; MODULE_DEVICE_TABLE(of, spi_imx_dt_ids); static struct spi_driver spi_imx_driver = { .driver = { .name = "spi-imx", .of_match_table = spi_imx_dt_ids, }, .probe = spi_imx_probe, .remove = spi_imx_remove, };
ACPI匹配
ACPI(Advanced Configuration and Power Interface)是一种用于配置和管理电源和设备的标准。对于使用ACPI进行配置的SPI设备和驱动程序,可以通过ACPI匹配来确定它们之间的关联关系。ACPI匹配通过比较设备的ACPI设备句柄与驱动程序的ACPI设备句柄来判断它们是否匹配。
举例spi-bcm2835.c
static const struct acpi_device_id spi_bcm2835_acpi_match[] = { { "BCM2835SPI", 0 }, { }, }; MODULE_DEVICE_TABLE(acpi, spi_bcm2835_acpi_match); static struct spi_driver spi_bcm2835_driver = { .driver = { .name = "spi-bcm2835", .acpi_match_table = ACPI_PTR(spi_bcm2835_acpi_match), }, .probe = spi_bcm2835_probe, .remove = spi_bcm2835_remove, };
ID表匹配
驱动程序可以定义一个ID表(struct spi_device_id
数组),其中包含了一组用于识别SPI设备的ID信息,如厂商ID、设备ID等。当驱动程序加载时,会尝试将SPI设备的ID与ID表中的项进行匹配。如果SPI设备的ID与ID表中的某一项匹配成功,则可以确定设备与驱动程序之间的关联关系。
举例spi-s3c64xx.c
static const struct platform_device_id s3c64xx_spi_driver_ids[] = { { .name = "s3c2443-spi", .driver_data = (kernel_ulong_t)&s3c2443_spi_port_config, }, { .name = "s3c6410-spi", .driver_data = (kernel_ulong_t)&s3c6410_spi_port_config, }, { .name = "s5pv210-spi", .driver_data = (kernel_ulong_t)&s5pv210_spi_port_config, }, { .name = "exynos4210-spi", .driver_data = (kernel_ulong_t)&exynos4_spi_port_config, }, { }, }; MODULE_DEVICE_TABLE(spi, s3c64xx_spi_driver_ids); static struct platform_driver s3c64xx_spi_driver = { .driver = { .name = "s3c64xx-spi", .pm = &s3c64xx_spi_pm, .of_match_table = of_match_ptr(s3c64xx_spi_dt_match), }, .probe = s3c64xx_spi_probe, .remove = s3c64xx_spi_remove, .id_table = s3c64xx_spi_driver_ids, };
名称匹配
SPI设备和驱动程序都有一个名称字段,分别是设备的modalias
和驱动程序的name
。通过比较设备的modalias
和驱动程序的name
,可以判断它们是否匹配。如果两者相等,则可以确定设备与驱动程序之间的关联关系。
举例spi-omap2-mcspi.c
static const struct spi_device_id spi_imx_id[] = { { "spi_imx", 0 }, { }, }; MODULE_DEVICE_TABLE(spi, spi_imx_id); static struct spi_driver spi_imx_driver = { .driver = { .name = "spi_imx", .owner = THIS_MODULE, }, .id_table = spi_imx_id, };
SPI从设备注册
spi_register_board_info
spi_register_board_info
用于注册给定板级信息中的SPI设备。这些设备节点在相关的SPI控制器(bus_num)定义之后才创建。这样,即使重新加载控制器驱动程序,Linux也不会忘记这些硬件设备。函数定义在drivers/spi/spi.c
文件中:
/** * spi_register_board_info - register SPI devices for a given board * @info: array of chip descriptors * @n: how many descriptors are provided * Context: can sleep * * Board-specific early init code calls this (probably during arch_initcall) * with segments of the SPI device table. Any device nodes are created later, * after the relevant parent SPI controller (bus_num) is defined. We keep * this table of devices forever, so that reloading a controller driver will * not make Linux forget about these hard-wired devices. * * Other code can also call this, e.g. a particular add-on board might provide * SPI devices through its expansion connector, so code initializing that board * would naturally declare its SPI devices. * * The board info passed can safely be __initdata ... but be careful of * any embedded pointers (platform_data, etc), they're copied as-is. * * Return: zero on success, else a negative error code. */ int spi_register_board_info(struct spi_board_info const *info, unsigned n) { struct boardinfo *bi; int i; if (!n) return -EINVAL; bi = kzalloc(n * sizeof(*bi), GFP_KERNEL); if (!bi) return -ENOMEM; for (i = 0; i < n; i++, bi++, info++) { struct spi_master *master; memcpy(&bi->board_info, info, sizeof(*info)); mutex_lock(&board_lock); list_add_tail(&bi->list, &board_list); list_for_each_entry(master, &spi_master_list, list) spi_match_master_to_boardinfo(master, &bi->board_info); mutex_unlock(&board_lock); } return 0; }
-
spi_register_board_info
接受一个指向SPI设备描述符数组的指针info
,以及描述符的数量n
。这些描述符包含了每个SPI设备的配置信息,如设备节点、SPI控制器的总线编号、最大速度、模块别名等。 -
函数内部会为每个设备描述符分配一个
boardinfo
结构体,并将其添加到全局的board_list
链表中。 -
然后,遍历已注册的SPI控制器(通过
spi_master_list
链表),并将匹配到的控制器与设备描述符关联起来,以便后续的设备创建与配置。 -
最后,函数返回0表示成功,否则返回负数错误代码表示失败。
spi_alloc_device
spi_alloc_device
分配一个新的SPI设备。函数定义在drivers/spi/spi.c
文件中:
/** * spi_alloc_device - Allocate a new SPI device * @master: Controller to which device is connected * Context: can sleep * * Allows a driver to allocate and initialize a spi_device without * registering it immediately. This allows a driver to directly * fill the spi_device with device parameters before calling * spi_add_device() on it. * * Caller is responsible to call spi_add_device() on the returned * spi_device structure to add it to the SPI master. If the caller * needs to discard the spi_device without adding it, then it should * call spi_dev_put() on it. * * Return: a pointer to the new device, or NULL. */ struct spi_device *spi_alloc_device(struct spi_master *master) { struct spi_device *spi; if (!spi_master_get(master)) return NULL; spi = kzalloc(sizeof(*spi), GFP_KERNEL); if (!spi) { spi_master_put(master); return NULL; } spi->master = master; spi->dev.parent = &master->dev; spi->dev.bus = &spi_bus_type; spi->dev.release = spidev_release; spi->cs_gpio = -ENOENT; spin_lock_init(&spi->statistics.lock); device_initialize(&spi->dev); return spi; }
- 首先,函数接收一个
spi_master
指针作为参数,表示要连接的SPI控制器。 - 函数通过调用
spi_master_get
增加SPI控制器的引用计数,确保控制器的有效性。如果引用计数增加失败(返回0),则返回NULL。 - 函数通过调用
kzalloc
为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则释放之前增加的SPI控制器的引用计数,并返回NULL。 - 函数对新分配的
spi_device
结构体进行初始化。它设置了设备的主控制器(master
)、父设备(parent
)为SPI控制器的设备结构体,总线(bus
)为SPI总线类型,释放函数(release
)为spidev_release
,表示在设备释放时调用该函数进行清理,片选GPIO(cs_gpio
)初始化为-ENOENT
表示未设置。 - 函数使用
spin_lock_init
初始化统计信息(statistics
)的自旋锁。 - 最后,函数调用
device_initialize
对设备进行初始化,并返回指向新设备的指针。
spi_add_device
spi_add_device
将使用spi_alloc_device
函数分配的spi_device
结构体添加到SPI总线上。 函数定义在drivers/spi/spi.c
文件中:
/** * spi_add_device - Add spi_device allocated with spi_alloc_device * @spi: spi_device to register * * Companion function to spi_alloc_device. Devices allocated with * spi_alloc_device can be added onto the spi bus with this function. * * Return: 0 on success; negative errno on failure */ int spi_add_device(struct spi_device *spi) { static DEFINE_MUTEX(spi_add_lock); struct spi_master *master = spi->master; struct device *dev = master->dev.parent; int status; /* Chipselects are numbered 0..max; validate. */ if (spi->chip_select >= master->num_chipselect) { dev_err(dev, "cs%d >= max %d\n", spi->chip_select, master->num_chipselect); return -EINVAL; } /* Set the bus ID string */ spi_dev_set_name(spi); /* We need to make sure there's no other device with this * chipselect **BEFORE** we call setup(), else we'll trash * its configuration. Lock against concurrent add() calls. */ mutex_lock(&spi_add_lock); status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check); if (status) { dev_err(dev, "chipselect %d already in use\n", spi->chip_select); goto done; } if (master->cs_gpios) spi->cs_gpio = master->cs_gpios[spi->chip_select]; /* Drivers may modify this initial i/o setup, but will * normally rely on the device being setup. Devices * using SPI_CS_HIGH can't coexist well otherwise... */ status = spi_setup(spi); if (status < 0) { dev_err(dev, "can't setup %s, status %d\n", dev_name(&spi->dev), status); goto done; } /* Device may be bound to an active driver when this returns */ status = device_add(&spi->dev); if (status < 0) dev_err(dev, "can't add %s, status %d\n", dev_name(&spi->dev), status); else dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev)); done: mutex_unlock(&spi_add_lock); return status; }
- 首先,函数获取到要添加的
spi_device
结构体中的spi_master
指针以及其父设备的指针。 - 然后,函数对
spi_device
的chip_select
进行验证,确保其值在有效范围内。 - 接下来,函数设置
spi_device
的总线ID字符串,用于标识该设备在SPI总线上的位置。 - 为了防止与已存在的设备冲突,函数使用互斥锁对添加操作进行了保护。
- 函数通过调用
bus_for_each_dev
遍历SPI总线上的所有设备,检查是否有其他设备使用了相同的chip_select
。如果存在冲突,函数会返回错误。 - 如果SPI主控制器设置了
cs_gpios
(片选GPIO引脚),函数会将相应的GPIO引脚分配给spi_device
。 - 接下来,函数调用
spi_setup
对spi_device
进行初始化设置,以确保其正常工作。 - 最后,函数使用
device_add
将spi_device
添加到设备层次结构中,使其在系统中可见。
spi_new_device
spi_new_device
作用是在特定的SPI控制器上实例化一个新的SPI设备。函数定义在drivers/spi/spi.c
文件中:
/** * spi_new_device - instantiate one new SPI device * @master: Controller to which device is connected * @chip: Describes the SPI device * Context: can sleep * * On typical mainboards, this is purely internal; and it's not needed * after board init creates the hard-wired devices. Some development * platforms may not be able to use spi_register_board_info though, and * this is exported so that for example a USB or parport based adapter * driver could add devices (which it would learn about out-of-band). * * Return: the new device, or NULL. */ struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip) { struct spi_device *proxy; int status; /* NOTE: caller did any chip->bus_num checks necessary. * * Also, unless we change the return value convention to use * error-or-pointer (not NULL-or-pointer), troubleshootability * suggests syslogged diagnostics are best here (ugh). */ proxy = spi_alloc_device(master); if (!proxy) return NULL; WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias)); proxy->chip_select = chip->chip_select; proxy->max_speed_hz = chip->max_speed_hz; proxy->mode = chip->mode; proxy->irq = chip->irq; strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias)); proxy->dev.platform_data = (void *) chip->platform_data; proxy->controller_data = chip->controller_data; proxy->controller_state = NULL; status = spi_add_device(proxy); if (status < 0) { spi_dev_put(proxy); return NULL; } return proxy; }
- 首先,函数接收一个
spi_master
指针和一个spi_board_info
结构体指针作为参数,分别表示要连接的SPI控制器和描述SPI设备的信息。 - 函数通过调用
spi_alloc_device
为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则返回NULL。 - 函数将SPI设备的属性从
spi_board_info
结构体中复制到新分配的spi_device
结构体中。这些属性包括设备的片选引脚号(chip_select
)、最大传输速率(max_speed_hz
)、工作模式(mode
)、中断号(irq
)、模块别名(modalias
)等。 - 如果
spi_add_device
函数成功将SPI设备添加到SPI总线上,则函数返回指向新设备的指针。否则,函数会释放先前分配的内存,并返回NULL。
spi_setup
spi_setup
实现了SPI设备的设置和配置。函数定义在drivers/spi/spi.c
文件中:
/** * spi_setup - setup SPI mode and clock rate * @spi: the device whose settings are being modified * Context: can sleep, and no requests are queued to the device * * SPI protocol drivers may need to update the transfer mode if the * device doesn't work with its default. They may likewise need * to update clock rates or word sizes from initial values. This function * changes those settings, and must be called from a context that can sleep. * Except for SPI_CS_HIGH, which takes effect immediately, the changes take * effect the next time the device is selected and data is transferred to * or from it. When this function returns, the spi device is deselected. * * Note that this call will fail if the protocol driver specifies an option * that the underlying controller or its driver does not support. For * example, not all hardware supports wire transfers using nine bit words, * LSB-first wire encoding, or active-high chipselects. * * Return: zero on success, else a negative error code. */ int spi_setup(struct spi_device *spi) { unsigned bad_bits, ugly_bits; int status; /* check mode to prevent that DUAL and QUAD set at the same time */ if (((spi->mode & SPI_TX_DUAL) && (spi->mode & SPI_TX_QUAD)) || ((spi->mode & SPI_RX_DUAL) && (spi->mode & SPI_RX_QUAD))) { dev_err(&spi->dev, "setup: can not select dual and quad at the same time\n"); return -EINVAL; } /* if it is SPI_3WIRE mode, DUAL and QUAD should be forbidden */ if ((spi->mode & SPI_3WIRE) && (spi->mode & (SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD))) return -EINVAL; /* help drivers fail *cleanly* when they need options * that aren't supported with their current master */ bad_bits = spi->mode & ~spi->master->mode_bits; ugly_bits = bad_bits & (SPI_TX_DUAL | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD); if (ugly_bits) { dev_warn(&spi->dev, "setup: ignoring unsupported mode bits %x\n", ugly_bits); spi->mode &= ~ugly_bits; bad_bits &= ~ugly_bits; } if (bad_bits) { dev_err(&spi->dev, "setup: unsupported mode bits %x\n", bad_bits); return -EINVAL; } if (!spi->bits_per_word) spi->bits_per_word = 8; status = __spi_validate_bits_per_word(spi->master, spi->bits_per_word); if (status) return status; if (!spi->max_speed_hz) spi->max_speed_hz = spi->master->max_speed_hz; if (spi->master->setup) status = spi->master->setup(spi); spi_set_cs(spi, false); dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s%u bits/w, %u Hz max --> %d\n", (int) (spi->mode & (SPI_CPOL | SPI_CPHA)), (spi->mode & SPI_CS_HIGH) ? "cs_high, " : "", (spi->mode & SPI_LSB_FIRST) ? "lsb, " : "", (spi->mode & SPI_3WIRE) ? "3wire, " : "", (spi->mode & SPI_LOOP) ? "loopback, " : "", spi->bits_per_word, spi->max_speed_hz, status); return status; }
- 函数首先检查传输模式(mode)的合法性。如果同时设置了
SPI_TX_DUAL
和SPI_TX_QUAD
,或者同时设置了SPI_RX_DUAL
和SPI_RX_QUAD
,会返回错误。如果设置了SPI_3WIRE
模式,并且同时设置了SPI_TX_DUAL
、SPI_TX_QUAD
、SPI_RX_DUAL
或SPI_RX_QUAD
,也会返回错误。 - 函数检查传输模式是否被支持。如果设备的模式中包含控制器不支持的模式位,会给出警告并将不支持的模式位清除。如果存在不支持的模式位,会返回错误。
- 如果设备的每字位数(
bits_per_word
)为0,则设置默认值为8位。 - 函数调用
__spi_validate_bits_per_word
验证每字位数是否被支持。如果不被支持,会返回错误。 - 如果设备的最大时钟速度(max_speed_hz)为0,则设置默认值为控制器的最大时钟速度。
- 如果控制器定义了
setup
函数,会调用该函数进行设备的设置。 - 设置片选(chip select)为非激活状态。
- 最后,函数打印设备的设置信息,并返回设置的结果。
SPI驱动注册
spi_register_driver
spi_register_driver
完成SPI驱动的注册。函数定义在drivers/spi/spi.c
文件中:
/** * __spi_register_driver - register a SPI driver * @owner: owner module of the driver to register * @sdrv: the driver to register * Context: can sleep * * Return: zero on success, else a negative error code. */ int __spi_register_driver(struct module *owner, struct spi_driver *sdrv) { sdrv->driver.owner = owner; sdrv->driver.bus = &spi_bus_type; if (sdrv->probe) sdrv->driver.probe = spi_drv_probe; if (sdrv->remove) sdrv->driver.remove = spi_drv_remove; if (sdrv->shutdown) sdrv->driver.shutdown = spi_drv_shutdown; return driver_register(&sdrv->driver); }
- 函数接收两个参数,
owner
表示驱动的所有者模块,sdrv
表示要注册的SPI驱动。 - 函数设置驱动的相关属性。首先将驱动的所有者模块设置为
owner
,将驱动的总线类型设置为spi_bus_type
,表示该驱动属于SPI总线类型。 - 如果驱动定义了
probe
函数,将该函数指定为驱动的probe
函数。probe
函数在设备与驱动匹配成功后调用,用于初始化和注册设备。 - 如果驱动定义了
remove
函数,将该函数指定为驱动的remove
函数。remove
函数在设备与驱动解除匹配时调用,用于清理和注销设备。 - 如果驱动定义了
shutdown
函数,将该函数指定为驱动的shutdown
函数。shutdown
函数在系统关机时调用,用于执行驱动的关机处理。 - 最后,调用
driver_register
函数注册驱动,并返回注册的结果。
消息队列初始化
spi_master_initialize_queue
spi_master_initialize_queue
初始化一个SPI主设备的队列,并启动队列的操作。源码位于drivers\spi\spi.c
。
static int spi_master_initialize_queue(struct spi_master *master) { int ret; master->transfer = spi_queued_transfer; if (!master->transfer_one_message) master->transfer_one_message = spi_transfer_one_message; /* Initialize and start queue */ ret = spi_init_queue(master); if (ret) { dev_err(&master->dev, "problem initializing queue\n"); goto err_init_queue; } master->queued = true; ret = spi_start_queue(master); if (ret) { dev_err(&master->dev, "problem starting queue\n"); goto err_start_queue; } return 0; err_start_queue: spi_destroy_queue(master); err_init_queue: return ret; }
- 设置SPI主设备的传输函数为
spi_queued_transfer
,该函数用于执行队列中的数据传输操作。 - 检查是否定义了
transfer_one_message
函数,如果没有定义,则将其设置为默认的spi_transfer_one_message
函数。 - 调用
spi_init_queue
函数初始化队列。这个函数可能会设置队列的相关参数,分配内存等。 - 如果
spi_init_queue
返回非零值,表示初始化队列出现问题,会打印错误信息,并跳转到错误处理标签err_init_queue
,然后返回错误码。 - 将
master->queued
标志设置为true,表示队列已经初始化。 - 调用
spi_start_queue
函数启动队列。这个函数可能会启动硬件传输和处理队列中的数据。 - 如果
spi_start_queue
返回非零值,表示启动队列出现问题,会打印错误信息,并跳转到错误处理标签err_start_queue。 - 最后,如果所有步骤都成功执行,函数返回0表示初始化和启动队列成功。
spi_init_queue
spi_init_queue
函数初始化了spi_master
结构的 kthread_worker
和 kthread_work
结构(内核 kthread_worker
和kthread_work
机制)这个机制相当于是内核线程,并指定了work的执行函数为 spi_pump_messages
,最后如果spi_master
定了spi_controller->rt
的话,意思是开启 realtime 发送,那么将执行线程变为SCHED_FIFO
的实时调度类型,也就是更高的优先级(实时优先级)。源码位于drivers\spi\spi.c
。
static int spi_init_queue(struct spi_master *master) { struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; master->running = false; master->busy = false; init_kthread_worker(&master->kworker); master->kworker_task = kthread_run(kthread_worker_fn, &master->kworker, "%s", dev_name(&master->dev)); if (IS_ERR(master->kworker_task)) { dev_err(&master->dev, "failed to create message pump task\n"); return PTR_ERR(master->kworker_task); } init_kthread_work(&master->pump_messages, spi_pump_messages); /* * Master config will indicate if this controller should run the * message pump with high (realtime) priority to reduce the transfer * latency on the bus by minimising the delay between a transfer * request and the scheduling of the message pump thread. Without this * setting the message pump thread will remain at default priority. */ if (master->rt) { dev_info(&master->dev, "will run message pump with realtime priority\n"); sched_setscheduler(master->kworker_task, SCHED_FIFO, ¶m); } return 0; }
- 创建一个
sched_param
结构体,并将其初始化为MAX_RT_PRIO-1
。这个结构体用于设置线程的调度参数,包括实时优先级。 - 将
master->running
和master->busy
标志设置为false,表示队列当前处于未运行和未忙碌状态。 - 使用
init_kthread_worker
函数初始化master->kworker
结构体,这个结构体用于管理队列中的工作项。 - 调用
kthread_run
函数创建一个内核线程,并将其绑定到kthread_worker_fn
函数上。该线程用于执行队列中的工作项。 - 如果
kthread_run
返回一个错误指针(PTR_ERR
),表示创建线程失败,会打印错误信息并返回错误码。 - 使用
init_kthread_work
函数初始化master->pump_messages
结构体,并将其与spi_pump_messages
函数关联起来。这个函数表示在消息泵线程中要执行的工作。 - 如果
master->rt
为真,则将kworker_task
线程设置为实时优先级(SCHED_FIFO
),以减小传输延迟。函数使用sched_setscheduler
函数设置线程的调度器和参数。 - 如果设置了实时优先级,会打印一条信息说明消息泵将以实时优先级运行。
- 最后,函数返回0表示初始化队列成功。
spi_start_queue
static int spi_start_queue(struct spi_master *master) { unsigned long flags; spin_lock_irqsave(&master->queue_lock, flags); if (master->running || master->busy) { spin_unlock_irqrestore(&master->queue_lock, flags); return -EBUSY; } master->running = true; master->cur_msg = NULL; spin_unlock_irqrestore(&master->queue_lock, flags); queue_kthread_work(&master->kworker, &master->pump_messages); return 0; }
- 使用
spin_lock_irqsave
函数获取队列自旋锁,以确保在操作期间的原子性。自旋锁用于保护多线程访问的临界区域,以防止竞态条件。 - 检查队列是否已经在运行或忙碌。如果是,则表示队列已经在操作中,无法启动新的操作。函数会释放自旋锁并返回-EBUSY错误码。
- 将running标志设置为true,表示队列正在运行。同时,将cur_msg设置为NULL,表示当前没有正在处理的消息。
- 使用
spin_unlock_irqrestore
函数释放自旋锁,允许其他线程继续访问队列。 - 使用
queue_kthread_work
函数将pump_messages
工作项加入kworker
队列。 - 最后,函数返回0表示启动队列操作成功。
数据准备
spi_message_init
spi_message_init
用于初始化一个SPI消息结构体"spi_message"。源码位于 include\linux\spi\spi.h
。
static inline void spi_message_init(struct spi_message *m) { memset(m, 0, sizeof *m); INIT_LIST_HEAD(&m->transfers); }
-
这段代码中使用了memset函数和
INIT_LIST_HEAD
宏。memset函数用于将指定内存区域的内容设置为特定的值,这里将spi_message
结构体的内存设置为零。INIT_LIST_HEAD
宏用于初始化一个链表头,将其next
和prev
指针都指向自身,表示链表为空。 -
spi_message_init
通常用于在使用spi_message
结构体之前对其进行初始化,以确保结构体的所有字段都具有正确的初始值。这样可以避免在使用spi_message
结构体时出现未初始化的字段导致的错误。
spi_message_add_tail
spi_message_add_tail
通常用于构建SPI消息,在执行SPI传输时将多个spi_transfer
结构体添加到spi_message
的传输列表中。这样可以按照特定的顺序执行一系列SPI传输操作。源码位于 include\linux\spi\spi.h
。
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) { list_add_tail(&t->transfer_list, &m->transfers); }
spi_message_add_tail
使用了list_add_tail
函数,它是Linux内核中的一个双向链表操作函数。它用于将一个节点插入到链表的尾部,确保节点的顺序与添加的顺序相符。
spi_message_init_with_transfers
spi_message_init_with_transfers
可以一次性初始化一个spi_message
对象并将一组spi_transfer
结构体添加到其中。这样可以更方便地构建包含多个传输操作的SPI消息。源码位于 include\linux\spi\spi.h
。
/** * spi_message_init_with_transfers - Initialize spi_message and append transfers * @m: spi_message to be initialized * @xfers: An array of spi transfers * @num_xfers: Number of items in the xfer array * * This function initializes the given spi_message and adds each spi_transfer in * the given array to the message. */ static inline void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers) { unsigned int i; spi_message_init(m); for (i = 0; i < num_xfers; ++i) spi_message_add_tail(&xfers[i], m); }
-
调用
spi_message_init
函数对给定的spi_message
进行初始化,确保它是一个空的消息对象。 -
使用循环遍历传入的
spi_transfer
数组,逐个将spi_transfer
结构体添加到spi_message
的传输列表中。 -
在每次迭代中,使用
spi_message_add_tail
函数将当前的spi_transfer
结构体添加到spi_message
的传输列表的尾部。
数据传输
SPI数据传输的整体流程大致如下:
-
主设备初始化
spi_master_setup()
:主设备被配置为适当的工作模式,包括时钟速率、数据位顺序和传输模式等。主设备也会初始化相关的硬件资源和寄存器。 -
主设备选中从设备
spi_setup()
:通过将从设备的片选线(Slave Select)拉低,主设备选择要与之通信的从设备。可以有多个从设备连接到同一个主设备,并通过不同的片选线进行选择。 -
传输开始
spi_transfer_one_message()
:主设备产生时钟信号(SCLK)并将其发送到从设备。时钟信号定义了数据传输的时序和速率。 -
数据传输
spi_sync_transfer()
: 用于同步进行SPI数据传输,等待传输完成。spi_async_transfer()
: 用于异步进行SPI数据传输,可以在传输过程中执行其他任务。在每个时钟周期中,主设备通过主设备输出线(MOSI)将一个数据位发送给从设备,同时从设备通过主设备输入线(MISO)返回一个数据位。数据的传输顺序可以是最高有效位(MSB)先传输或最低有效位(LSB)先传输,取决于设备的配置。
-
时钟信号控制
spi_transfer_one()
、spi_sync_transfer()
、spi_async_transfer()
:时钟信号的边沿(上升沿或下降沿)用于控制数据的采样和传输。具体的时钟边沿使用方式取决于主设备和从设备之间的协议和时序要求。 -
数据传输完成
spi_chipselect()
:完成所需的数据传输后,主设备可以通过将从设备的片选线拉高来结束传输。这会释放从设备,并允许它响应其他主设备的请求。 -
传输结束
spi_finalize_current_message()
:在所有的数据传输完成后,可以根据需要执行清理操作,关闭相关的硬件资源,或者进行进一步的处理和分析。
同步传输(Synchronous Transfer):
- 同步传输是指在进行SPI数据传输时,主设备会等待传输完成后再继续执行后续的代码。
- 主设备在发起数据传输后会一直等待传输完成,然后再返回结果给调用者。
- 在同步传输期间,主设备会阻塞(即挂起)其它任务的执行,直到数据传输完成。
- 同步传输适用于需要等待传输结果并依赖传输结果的场景。
同步传输通常用于对传输结果实时性要求较高的情况,或者需要确保传输结果可靠性的场景。由于同步传输会阻塞主设备的执行,因此在某些实时性要求高的应用中可能更合适。
异步传输(Asynchronous Transfer):
- 异步传输是指在进行SPI数据传输时,主设备可以继续执行后续的代码,而不会等待传输完成。
- 主设备发起数据传输后会立即返回给调用者,传输过程在后台执行。
- 主设备可以在数据传输进行的同时执行其他任务,不会阻塞其它操作。
- 异步传输适用于不需要立即获取传输结果,或者需要同时进行多个传输操作的场景。
异步传输通常用于对传输实时性要求相对较低的情况,或者需要同时进行多个传输操作的场景。由于异步传输不会阻塞主设备的执行,因此可以更好地提高系统的响应性和并发性。
异步方式
spi_async
spi_async
函数用于实现异步传输,异步传输允许在传输过程中执行其他任务,而不需要等待传输完成。传输完成后,将调用回调函数来处理传输结果。源码位于drivers\spi\spi.c
。
/** * spi_async - asynchronous SPI transfer * @spi: device with which data will be exchanged * @message: describes the data transfers, including completion callback * Context: any (irqs may be blocked, etc) * * This call may be used in_irq and other contexts which can't sleep, * as well as from task contexts which can sleep. * * The completion callback is invoked in a context which can't sleep. * Before that invocation, the value of message->status is undefined. * When the callback is issued, message->status holds either zero (to * indicate complete success) or a negative error code. After that * callback returns, the driver which issued the transfer request may * deallocate the associated memory; it's no longer in use by any SPI * core or controller driver code. * * Note that although all messages to a spi_device are handled in * FIFO order, messages may go to different devices in other orders. * Some device might be higher priority, or have various "hard" access * time requirements, for example. * * On detection of any fault during the transfer, processing of * the entire message is aborted, and the device is deselected. * Until returning from the associated message completion callback, * no other spi_message queued to that device will be processed. * (This rule applies equally to all the synchronous transfer calls, * which are wrappers around this core asynchronous primitive.) * * Return: zero on success, else a negative error code. */ int spi_async(struct spi_device *spi, struct spi_message *message) { struct spi_master *master = spi->master; int ret; unsigned long flags; ret = __spi_validate(spi, message); if (ret != 0) return ret; spin_lock_irqsave(&master->bus_lock_spinlock, flags); if (master->bus_lock_flag) ret = -EBUSY; else ret = __spi_async(spi, message); spin_unlock_irqrestore(&master->bus_lock_spinlock, flags); return ret; }
- 函数接受一个
spi_device
结构体指针spi
和一个spi_message
结构体指针message
作为参数,表示要进行异步SPI传输的设备和消息。 - 首先,调用
__spi_validate()
函数对spi
和message
进行验证,确保它们有效。如果验证失败,将返回错误代码。 - 接下来,通过获取
spi_master
结构体指针master
来获取SPI主设备的相关信息。 - 使用自旋锁
bus_lock_spinlock
来保护对主设备的并发访问。 - 在获取自旋锁后,检查
master->bus_lock_flag
标志位,如果已经被占用,则返回EBUSY
错误代码,表示总线忙。 - 如果总线未被占用,则调用
__spi_async()
函数来执行异步的SPI传输操作。 - 在完成传输后,释放自旋锁并返回传输的结果代码。
__spi_async
static int __spi_async(struct spi_device *spi, struct spi_message *message) { struct spi_master *master = spi->master; message->spi = spi; SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async); SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async); trace_spi_message_submit(message); return master->transfer(spi, message); }
- 函数接受一个
spi_device
结构体指针spi
和一个spi_message
结构体指针message
作为参数,表示要进行异步SPI传输的设备和消息。 - 首先,将
message->spi
设置为传入的spi
设备,以便在后续的传输过程中访问设备信息。 - 使用
SPI_STATISTICS_INCREMENT_FIELD()
宏对主设备和从设备的统计数据进行增加,用于记录异步传输的统计信息。 - 调用
trace_spi_message_submit()
函数进行追踪记录,用于跟踪和分析SPI消息的提交情况。 - 最后,调用
master->transfer()
函数来执行实际的SPI传输操作,将spi
设备和message
消息传递给传输函数。
spi_queued_transfer
/** * spi_queued_transfer - transfer function for queued transfers * @spi: spi device which is requesting transfer * @msg: spi message which is to handled is queued to driver queue * * Return: zero on success, else a negative error code. */ static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg) { return __spi_queued_transfer(spi, msg, true); }
spi_queued_transfer
会调用到__spi_queued_transfer
。
__spi_queued_transfer
__spi_queued_transfer
函数用于将消息添加到SPI主设备的传输队列中,并启动后台的消息处理工作。源码位于drivers\spi\spi.c
。
static int __spi_queued_transfer(struct spi_device *spi, struct spi_message *msg, bool need_pump) { struct spi_master *master = spi->master; unsigned long flags; spin_lock_irqsave(&master->queue_lock, flags); if (!master->running) { spin_unlock_irqrestore(&master->queue_lock, flags); return -ESHUTDOWN; } msg->actual_length = 0; msg->status = -EINPROGRESS; list_add_tail(&msg->queue, &master->queue); if (!master->busy && need_pump) queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); return 0; }
- 函数接受一个
spi_device
结构体指针spi
,一个spi_message
结构体指针msg
,和一个布尔值need_pump
作为参数,表示要进行SPI队列传输的设备、消息和是否需要启动消息处理。 - 首先,使用自旋锁
queue_lock
保护对主设备队列的并发访问。 - 如果主设备的状态为非运行状态(
!master->running
),则表示设备已经关闭,无法进行传输操作。此时解锁自旋锁并返回-ESHUTDOWN
错误代码。 - 设置消息的
actual_length
为0,表示传输的实际长度尚未确定。将消息的status
设置为-EINPROGRESS
,表示传输正在进行中。 - 将消息添加到主设备的队列中,使用
list_add_tail()
函数将消息的链表节点添加到主设备队列的尾部。 - 如果主设备当前没有忙碌的传输并且需要启动消息处理(
!master->busy && need_pump
),则使用queue_kthread_work()
函数将消息处理工作项添加到内核线程的工作队列中,以便在后台异步处理消息。 - 最后,解锁自旋锁并返回0,表示消息已成功添加到主设备队列中。
同步方式
spi_sync
spi_sync
函数用于实现同步传输,其数据传输也是调用的__spi_queued_transfer
函数,只是第三个参数为false。源码在drivers/spi/spi.c
文件:
/** * spi_sync - blocking/synchronous SPI data transfers * @spi: device with which data will be exchanged * @message: describes the data transfers * Context: can sleep * * This call may only be used from a context that may sleep. The sleep * is non-interruptible, and has no timeout. Low-overhead controller * drivers may DMA directly into and out of the message buffers. * * Note that the SPI device's chip select is active during the message, * and then is normally disabled between messages. Drivers for some * frequently-used devices may want to minimize costs of selecting a chip, * by leaving it selected in anticipation that the next message will go * to the same chip. (That may increase power usage.) * * Also, the caller is guaranteeing that the memory associated with the * message will not be freed before this call returns. * * Return: zero on success, else a negative error code. */ int spi_sync(struct spi_device *spi, struct spi_message *message) { return __spi_sync(spi, message, 0); }
__spi_sync
__spi_sync
实现了同步的SPI传输。它根据传输方法的不同(队列传输或其他方式),选择合适的传输方式,并在传输完成后等待通知。源码在drivers/spi/spi.c
文件:
static int __spi_sync(struct spi_device *spi, struct spi_message *message, int bus_locked) { DECLARE_COMPLETION_ONSTACK(done); int status; struct spi_master *master = spi->master; unsigned long flags; status = __spi_validate(spi, message); if (status != 0) return status; message->complete = spi_complete; message->context = &done; message->spi = spi; SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync); SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync); if (!bus_locked) mutex_lock(&master->bus_lock_mutex); /* If we're not using the legacy transfer method then we will * try to transfer in the calling context so special case. * This code would be less tricky if we could remove the * support for driver implemented message queues. */ if (master->transfer == spi_queued_transfer) { spin_lock_irqsave(&master->bus_lock_spinlock, flags); trace_spi_message_submit(message); status = __spi_queued_transfer(spi, message, false); spin_unlock_irqrestore(&master->bus_lock_spinlock, flags); } else { status = spi_async_locked(spi, message); } if (!bus_locked) mutex_unlock(&master->bus_lock_mutex); if (status == 0) { /* Push out the messages in the calling context if we * can. */ if (master->transfer == spi_queued_transfer) { SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync_immediate); SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync_immediate); __spi_pump_messages(master, false); } wait_for_completion(&done); status = message->status; } message->context = NULL; return status; }
- 函数接受一个
spi_device
结构体指针spi
,一个spi_message
结构体指针message
,以及一个整数bus_locked
,表示要进行同步SPI传输的设备、消息和总线锁定标志。 - 首先,调用
__spi_validate()
函数对设备和消息进行验证,以确保它们的有效性。如果验证失败,则返回相应的错误代码。 - 设置消息的
complete
字段为spi_complete
,表示传输完成后的回调函数。将context
设置为&done
,用于在传输完成时等待通知。将消息的spi
字段设置为当前的SPI设备。 - 增加统计数据,记录同步SPI传输的次数。
- 如果总线未锁定(
!bus_locked
),则获取总线锁定互斥锁。 - 根据主设备的传输方法来决定传输的方式。如果使用队列传输方法(
master->transfer == spi_queued_transfer
),则获取总线锁定自旋锁,将消息提交到队列,并调用__spi_queued_transfer()
函数进行队列传输。否则,调用spi_async_locked()
函数进行异步传输。 - 如果总线未锁定,则释放总线锁定互斥锁。
- 如果传输成功(
status == 0
),根据传输方法决定是否立即推出消息。如果使用队列传输方法,增加相应的统计数据,并调用__spi_pump_messages()
函数在当前上下文中处理队列中的消息。 - 等待传输完成的通知,使用
wait_for_completion()
函数挂起当前任务,直到完成通知触发。获取传输的最终状态,即message->status
。 - 将
message->context
设置为NULL
,清除传输完成的上下文。 - 返回传输的最终状态。
与异步传输不同的是:
- 异步传输是将
__spi_pump_messages
作为工作挂到内核工作线程,由内核工作线程来完成数据的传输; - 而同步传输则是在内部调用了
__spi_pump_messages
方法,针对不同的情景情况而不同(只有同步传输、既有同步传输,也有异步传输):__spi_pump_messages
函数内部可能直接调用ctrl->transfer_one_message通过SPI控制器进行数据传输;- 也有可能将
__spi_pump_messages
作为工作挂到内核工作线程,由内核工作线程完成数据的传输;
- 此外
__spi_pump_messages
函数传入的第二个参数不同,异步方式传入true,同步方式传入false。
spi_complete
SPI数据同步传输是通过completion完成的,completion是一种轻量级的机制,它允许一个线程告诉另一个线程工作已经完成。也就是说我们当前线程要等待内核工作线程把数据传输完成。
在__spi_sync
函数中,首先通过DECLARE_COMPLETION_ONSTATCK
声明了一个struct completion
类型的变量,
/* * struct completion - structure used to maintain state for a "completion" * * This is the opaque structure used to maintain the state for a "completion". * Completions currently use a FIFO to queue threads that have to wait for * the "completion" event. * * See also: complete(), wait_for_completion() (and friends _timeout, * _interruptible, _interruptible_timeout, and _killable), init_completion(), * reinit_completion(), and macros DECLARE_COMPLETION(), * DECLARE_COMPLETION_ONSTACK(). */ struct completion { unsigned int done; // 用于同步的原子量 wait_queue_head_t wait; // 等待队列头 };
宏DECLARE_COMPLETION_ONSTATCK
会初始化done成员为0,以及等待队列头wait;等待队列wait用于保存处于睡眠状态的线程。
__spi_sync
函数最后调用了wait_for_completion
函数,该函数会将done减一,当done为负数将会导致当前线程进入休眠状态。
直至message通过SPI控制器传输完成,从而在回调函数spi_complete
将当前线程唤醒。
/* Utility methods for SPI protocol drivers, layered on * top of the core. Some other utility methods are defined as * inline functions. */ static void spi_complete(void *arg) { complete(arg); }
complete函数定义在kernel/sched/completion.c
:
/** * complete: - signals a single thread waiting on this completion * @x: holds the state of this particular completion * * This will wake up a single thread waiting on this completion. Threads will be * awakened in the same order in which they were queued. * * See also complete_all(), wait_for_completion() and related routines. * * If this function wakes up a task, it executes a full memory barrier before * accessing the task state. */ void complete(struct completion *x) { unsigned long flags; spin_lock_irqsave(&x->wait.lock, flags); if (x->done != UINT_MAX) x->done++; __wake_up_locked(&x->wait, TASK_NORMAL, 1); spin_unlock_irqrestore(&x->wait.lock, flags); }
complete函数为唤醒函数,当然是将done加一,当done大于等于0则唤醒等待队列中的的线程。
__spi_pump_messages
__spi_pump_messages
会处理队列中的下一条消息,如果队列仍然非空,则继续调用 __spi_pump_messages()
函数来处理下一条消息。源码在drivers/spi/spi.c
文件:
/** * __spi_pump_messages - function which processes spi message queue * @master: master to process queue for * @in_kthread: true if we are in the context of the message pump thread * * This function checks if there is any spi message in the queue that * needs processing and if so call out to the driver to initialize hardware * and transfer each message. * * Note that it is called both from the kthread itself and also from * inside spi_sync(); the queue extraction handling at the top of the * function should deal with this safely. */ static void __spi_pump_messages(struct spi_master *master, bool in_kthread) { unsigned long flags; bool was_busy = false; int ret; /* Lock queue */ spin_lock_irqsave(&master->queue_lock, flags); /* Make sure we are not already running a message */ if (master->cur_msg) { spin_unlock_irqrestore(&master->queue_lock, flags); return; } /* If another context is idling the device then defer */ if (master->idling) { queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); return; } /* Check if the queue is idle */ if (list_empty(&master->queue) || !master->running) { if (!master->busy) { spin_unlock_irqrestore(&master->queue_lock, flags); return; } /* Only do teardown in the thread */ if (!in_kthread) { queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); return; } master->busy = false; master->idling = true; spin_unlock_irqrestore(&master->queue_lock, flags); kfree(master->dummy_rx); master->dummy_rx = NULL; kfree(master->dummy_tx); master->dummy_tx = NULL; if (master->unprepare_transfer_hardware && master->unprepare_transfer_hardware(master)) dev_err(&master->dev, "failed to unprepare transfer hardware\n"); if (master->auto_runtime_pm) { pm_runtime_mark_last_busy(master->dev.parent); pm_runtime_put_autosuspend(master->dev.parent); } trace_spi_master_idle(master); spin_lock_irqsave(&master->queue_lock, flags); master->idling = false; spin_unlock_irqrestore(&master->queue_lock, flags); return; } /* Extract head of queue */ master->cur_msg = list_first_entry(&master->queue, struct spi_message, queue); list_del_init(&master->cur_msg->queue); if (master->busy) was_busy = true; else master->busy = true; spin_unlock_irqrestore(&master->queue_lock, flags); if (!was_busy && master->auto_runtime_pm) { ret = pm_runtime_get_sync(master->dev.parent); if (ret < 0) { dev_err(&master->dev, "Failed to power device: %d\n", ret); return; } } if (!was_busy) trace_spi_master_busy(master); if (!was_busy && master->prepare_transfer_hardware) { ret = master->prepare_transfer_hardware(master); if (ret) { dev_err(&master->dev, "failed to prepare transfer hardware\n"); if (master->auto_runtime_pm) pm_runtime_put(master->dev.parent); return; } } trace_spi_message_start(master->cur_msg); if (master->prepare_message) { ret = master->prepare_message(master, master->cur_msg); if (ret) { dev_err(&master->dev, "failed to prepare message: %d\n", ret); master->cur_msg->status = ret; spi_finalize_current_message(master); return; } master->cur_msg_prepared = true; } ret = spi_map_msg(master, master->cur_msg); if (ret) { master->cur_msg->status = ret; spi_finalize_current_message(master); return; } ret = master->transfer_one_message(master, master->cur_msg); if (ret) { dev_err(&master->dev, "failed to transfer one message from queue\n"); return; } }
- 首先,通过获取
master->queue_lock
自旋锁来锁定队列。 - 检查是否已经有正在运行的消息,如果有,则解锁并返回。
- 检查是否有其他上下文正在空闲设备。如果是,则将任务添加到kworker线程中并解锁,然后返回。
- 检查队列是否为空或主设备处于非运行状态。如果队列为空且设备非运行状态,则解锁并返回。
- 如果主设备不忙,但当前上下文不是kworker线程,则将任务添加到kworker线程中并解锁,然后返回。
- 如果主设备不忙,则设置
master->busy
为true,表示设备正在处理消息。 - 如果是第一次处理消息且启用了自动运行时电源管理(auto_runtime_pm),则获取运行时电源,保持设备处于活动状态。
- 如果是第一次处理消息,则记录设备处于忙状态的跟踪信息。
- 如果是第一次处理消息且需要准备传输硬件(prepare_transfer_hardware函数不为空),则调用
master->prepare_transfer_hardware()
来准备传输硬件。 - 跟踪消息开始的信息。
- 如果定义了
master->prepare_message
函数,则调用该函数来准备消息。 - 对消息进行映射,将消息映射到具体的硬件传输。
- 调用
master->transfer_one_message()
函数执行消息传输。
这段代码实际上比较饶人,就我个人人为,它其实就是想做这么一件事:
- 从消息队列去获取
spi_message
,然后调用SPI控制器驱动提供的transfer_one
函数进行消息的传输,传输完成后就回调spi_finalize_current_message
,再次将__spi_pump_messages
放到内核工作线程执行; - 当然了当消息队列被输出完的话,内核工作线程就没工作可执行了,所以
__spi_pump_messages
函数内部又多了一些状态的判断,判断何时需要将__spi_pump_messages
放到内核工作线程执行;
spi_transfer_one_message
spi_transfer_one_message
实现了对SPI消息进行传输的默认处理方式。源码在drivers/spi/spi.c
文件:
/* * spi_transfer_one_message - Default implementation of transfer_one_message() * * This is a standard implementation of transfer_one_message() for * drivers which impelment a transfer_one() operation. It provides * standard handling of delays and chip select management. */ static int spi_transfer_one_message(struct spi_master *master, struct spi_message *msg) { struct spi_transfer *xfer; bool keep_cs = false; int ret = 0; unsigned long ms = 1; struct spi_statistics *statm = &master->statistics; struct spi_statistics *stats = &msg->spi->statistics; spi_set_cs(msg->spi, true); SPI_STATISTICS_INCREMENT_FIELD(statm, messages); SPI_STATISTICS_INCREMENT_FIELD(stats, messages); list_for_each_entry(xfer, &msg->transfers, transfer_list) { trace_spi_transfer_start(msg, xfer); spi_statistics_add_transfer_stats(statm, xfer, master); spi_statistics_add_transfer_stats(stats, xfer, master); if (xfer->tx_buf || xfer->rx_buf) { reinit_completion(&master->xfer_completion); ret = master->transfer_one(master, msg->spi, xfer); if (ret < 0) { SPI_STATISTICS_INCREMENT_FIELD(statm, errors); SPI_STATISTICS_INCREMENT_FIELD(stats, errors); dev_err(&msg->spi->dev, "SPI transfer failed: %d\n", ret); goto out; } if (ret > 0) { ret = 0; ms = xfer->len * 8 * 1000 / xfer->speed_hz; ms += ms + 100; /* some tolerance */ ms = wait_for_completion_timeout(&master->xfer_completion, msecs_to_jiffies(ms)); } if (ms == 0) { SPI_STATISTICS_INCREMENT_FIELD(statm, timedout); SPI_STATISTICS_INCREMENT_FIELD(stats, timedout); dev_err(&msg->spi->dev, "SPI transfer timed out\n"); msg->status = -ETIMEDOUT; } } else { if (xfer->len) dev_err(&msg->spi->dev, "Bufferless transfer has length %u\n", xfer->len); } trace_spi_transfer_stop(msg, xfer); if (msg->status != -EINPROGRESS) goto out; if (xfer->delay_usecs) udelay(xfer->delay_usecs); if (xfer->cs_change) { if (list_is_last(&xfer->transfer_list, &msg->transfers)) { keep_cs = true; } else { spi_set_cs(msg->spi, false); udelay(10); spi_set_cs(msg->spi, true); } } msg->actual_length += xfer->len; } out: if (ret != 0 || !keep_cs) spi_set_cs(msg->spi, false); if (msg->status == -EINPROGRESS) msg->status = ret; if (msg->status && master->handle_err) master->handle_err(master, msg); spi_finalize_current_message(master); return ret; }
- 首先,通过调用
spi_set_cs()
将SPI片选信号置为激活状态。 - 增加主设备和消息的统计信息中的
messages
字段。 - 使用
list_for_each_entry()
循环遍历消息中的每个传输项。 - 跟踪当前传输的起始位置。
- 在主设备和消息的统计信息中添加传输的统计数据。
- 如果传输项中的
tx_buf
或rx_buf
不为空,则初始化传输完成的等待完成量(master->xfer_completion
),然后调用master->transfer_one()
函数执行实际的传输操作。如果传输失败(返回值小于0),则增加错误计数,输出错误信息,并跳转到out
标签处。 - 如果传输项的返回值大于0,表示传输成功但需要等待传输完成。计算超时等待时间
ms
,然后使用wait_for_completion_timeout()
等待传输完成。 - 如果传输超时(
ms
等于0),增加超时计数,输出错误信息,并将消息的状态设置为-ETIMEDOUT
。 - 如果传输项中的
tx_buf
和rx_buf
都为空,并且传输长度不为0,则输出错误信息,表示无缓冲区传输长度非零。 - 跟踪当前传输的结束位置。
- 如果消息的状态不是
-EINPROGRESS
,则跳转到out
标签处。 - 如果传输项中设置了延迟(
delay_usecs
),则使用udelay()
进行延迟。 - 如果传输项的
cs_change
标志为真,则根据是否为传输列表的最后一个传输项来决定是否保持片选信号激活状态。如果是最后一个传输项,则保持激活状态,否则,先将片选信号置为非激活状态,延迟10微秒,然后再将片选信号置为激活状态。 - 增加消息的实际长度。
- 在
out
标签处,根据传输的返回值和keep_cs
标志来决定是否将片选信号置为非激活状态。 - 如果消息的状态仍为
-EINPROGRESS
,则将其设置为传输的返回值。 - 如果消息的状态非零且主设备的
handle_err
函数不为空,则调用master->handle_err()
函数处理错误。 - 最后,调用
spi_finalize_current_message()
函数来完成当前消息的处理。 - 返回传输的返回值。
spi_finalize_current_message
spi_finalize_current_message
用于完成当前消息的处理并将其从队列中移除。源码在drivers/spi/spi.c
文件:
/** * spi_finalize_current_message() - the current message is complete * @master: the master to return the message to * * Called by the driver to notify the core that the message in the front of the * queue is complete and can be removed from the queue. */ void spi_finalize_current_message(struct spi_master *master) { struct spi_message *mesg; unsigned long flags; int ret; spin_lock_irqsave(&master->queue_lock, flags); mesg = master->cur_msg; spin_unlock_irqrestore(&master->queue_lock, flags); spi_unmap_msg(master, mesg); if (master->cur_msg_prepared && master->unprepare_message) { ret = master->unprepare_message(master, mesg); if (ret) { dev_err(&master->dev, "failed to unprepare message: %d\n", ret); } } spin_lock_irqsave(&master->queue_lock, flags); master->cur_msg = NULL; master->cur_msg_prepared = false; queue_kthread_work(&master->kworker, &master->pump_messages); spin_unlock_irqrestore(&master->queue_lock, flags); trace_spi_message_done(mesg); mesg->state = NULL; if (mesg->complete) mesg->complete(mesg->context); }
- 获取当前正在处理的消息
cur_msg
并保存到本地变量mesg
中,使用自旋锁保护读取操作,确保原子性。 - 调用
spi_unmap_msg()
函数,解除消息中已映射的缓冲区。这个步骤是为了确保在消息处理完成后,相关的资源可以被释放,防止内存泄漏。 - 检查当前消息是否已经准备好并且主设备实现了
unprepare_message()
函数。如果满足条件,则调用unprepare_message()
函数对消息进行反准备操作。这个操作通常用于释放为消息准备的特定资源,如申请的DMA通道或中断处理程序等。如果反准备操作返回非零值,表示出现了错误,将输出错误信息。 - 使用自旋锁保护对当前消息和相关标志的修改操作。首先,将
cur_msg
指针和准备状态标志cur_msg_prepared
设置为NULL和false,表示当前消息已完成处理。然后,通过调用queue_kthread_work()
将工作项pump_messages
排队到内核工作队列中。这个工作项用于在后台线程中继续处理后续的消息。通过将工作项排队,可以确保消息处理的连续性和顺序性。 - 使用跟踪功能
trace_spi_message_done()
记录消息的完成情况。这可以用于调试和性能分析。 - 将消息的状态指针
state
设置为NULL,表示消息已经完成处理。 - 如果消息的
complete
成员不为空,则调用相应的回调函数,通知上层应用程序消息的完成情况。这个回调函数可以是应用程序自定义的函数,用于处理消息完成后的操作。
数据结构关系图
下面来自其他博主绘制的一张数据结构之间的关系图:这种图很清晰的介绍了:
- spi_controller、spi_device、spi_driver之间的关系;
- SPI控制器注册时的初始化部分;
- SPI控制器数据部分。
本文参考
http://jake.dothome.co.kr/spi-1
http://jake.dothome.co.kr/spi-2
https://blog.csdn.net/m0_64560763/article/details/127335361
https://www.cnblogs.com/xinghuo123/p/12991945.html
https://www.cnblogs.com/xinghuo123/p/12992015.html
https://www.cnblogs.com/xinghuo123/p/12994825.html
https://www.cnblogs.com/xinghuo123/p/12994850.html
https://www.cnblogs.com/xinghuo123/p/13046827.html
https://www.cnblogs.com/xinghuo123/p/13047065.html
https://blog.csdn.net/eastcnme/article/details/104447479
https://stephenzhou.blog.csdn.net/article/details/99866773?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-5-99866773-blog-104447479.pc_relevant_3mothn_strategy_and_data_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-5-99866773-blog-104447479.pc_relevant_3mothn_strategy_and_data_recovery&utm_relevant_index=10
https://blog.csdn.net/u012846795/article/details/114529068**
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~