【驱动】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-cphaspi-cpol 即可。
  • spidev0: 由于 spi_demospidev0 使用一样的硬件资源,需要把 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_chipselectmaster->num_chipselect = max_t(int, nb, master->num_chipselect);/* Return error only for an incorrectly formed cs-gpios property */// 如果"cs-gpios"属性未定义或定义错误,则返回0if (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;// 初始化所有引脚编号为-ENOENTfor (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;
}
  1. 函数首先获取设备节点中名为"cs-gpios"的GPIO数量,并将其与主控制器的num_chipselect(芯片选择线数量)进行比较,取较大值。

  2. 然后,函数会为主控制器分配足够的内存来存储GPIO的引脚编号,并将分配的内存地址赋值给cs_gpios指针。

  3. 接下来,函数会将分配的内存初始化为-ENOENT,然后从设备节点中获取cs-gpios属性对应的每个GPIO引脚编号,并将其存储到分配的内存中。

  4. 最后,函数返回状态码,表示注册过程的成功与否。

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);}
}
  1. 首先检查主控制器的设备节点是否存在,如果不存在(为空),则直接返回,不执行任何操作。

  2. 通过for_each_available_child_of_node宏遍历主控制器设备节点的每个可用子节点,其中nc是当前子节点的指针。

  3. 在每次迭代中,函数调用of_register_spi_device来为当前子节点创建一个spi_device对象,并将其赋值给spi

  4. 如果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);
}
  1. 首先声明一个struct device类型的指针dev

  2. 通过调用class_find_device函数来从spi_master_class类中查找与给定设备节点node匹配的设备。其中,&spi_master_class是指向spi_master_class的指针,NULL表示在整个类中查找,node表示要匹配的设备节点,__spi_of_master_match是一个匹配函数。

  3. 如果未找到匹配的设备,即dev为空,则返回NULL表示未找到对应的SPI主控制器。

  4. 如果找到匹配的设备,说明找到了对应的SPI主控制器,此时通过container_of宏计算出spi_master结构体的指针。它利用了设备结构体中的成员偏移来计算包含该成员的结构体的指针。

  5. 最后,返回计算得到的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;
}
  1. 该函数接受一个通知器块(notifier_block)指针nb,一个表示动作的无符号长整型action,以及一个指向设备树重配置数据的指针arg

  2. arg转换为struct of_reconfig_data类型的指针rd,用于访问设备树重配置数据。

  3. 通过调用of_reconfig_get_state_change函数,根据传入的actionarg参数获取设备树变更的状态。

  4. 根据状态进行不同的操作:

  5. 如果状态为OF_RECONFIG_CHANGE_ADD,表示设备被添加:

  • 通过设备节点的父节点找到对应的SPI主控制器,将其赋值给master
  • 如果未找到对应的主控制器,返回NOTIFY_OK表示该设备不属于我们处理的范围。
  • 调用of_register_spi_device函数为设备注册SPI设备,并将返回的spi_device指针赋值给spi
  • 释放对主控制器设备的引用计数,即调用put_device函数。
  • 如果注册失败,打印错误信息并返回相应的错误码。
  1. 如果状态为OF_RECONFIG_CHANGE_REMOVE,表示设备被移除:
  • 根据设备节点查找对应的SPI设备,将其赋值给spi
  • 如果未找到对应的SPI设备,返回NOTIFY_OK表示该设备不属于我们处理的范围。
  • 调用spi_unregister_device函数注销SPI设备。
  • 释放对设备的引用计数,即调用put_device函数。
  1. 最后,函数返回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;
}
  1. 该函数首先声明一个整数类型的变量status,用于存储函数执行过程中的状态。

  2. 接下来,通过调用kmalloc函数为SPI子系统分配一个大小为SPI_BUFSIZ的内核内存缓冲区,并将返回的指针赋值给buf

  3. 如果缓冲区分配失败(buf为空指针),则将status设置为-ENOMEM表示内存分配失败,并跳转到err0标签处进行错误处理。

  4. 接下来,通过调用bus_register函数注册SPI总线类型,将其添加到系统总线列表中。如果注册失败,将status设置为返回的错误码,并跳转到err1标签处进行错误处理。

  5. 然后,通过调用class_register函数注册SPI主控制器类,将其添加到系统设备类列表中。如果注册失败,将status设置为返回的错误码,并跳转到err2标签处进行错误处理。

  6. 如果系统启用了动态设备树功能(CONFIG_OF_DYNAMIC宏定义存在),则调用of_reconfig_notifier_register函数注册设备树重配置的通知回调函数spi_of_notifier

  7. 最后,如果所有的初始化操作都成功执行,函数返回0表示初始化成功。

  8. 如果在初始化过程中发生错误,将依次进行错误处理:注销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;
}
  1. 首先,通过调用of_spi_register_master函数,尝试根据设备树信息注册SPI主控制器。
  2. 检查主控制器的num_chipselect属性,确保至少有一个芯片选择(chip select)。
  3. 如果主控制器的bus_num属性小于0且具有有效的设备树节点,则尝试从设备树中获取总线编号。
  4. 如果总线编号仍然小于0,则使用动态分配的总线编号。这里使用了一个静态的原子变量dyn_bus_id作为计数器,从最大值开始递减分配动态总线编号。
  5. 初始化主控制器的一些字段和锁。使用dev_set_name设置主控制器设备的名称。
  6. 通过调用device_add注册主控制器设备,并将其添加到总线上,以便用户空间可以看到它。
  7. 如果主控制器使用队列驱动程序,则启动队列。如果使用的是非队列驱动程序,则显示警告信息。
  8. 添加统计信息并将主控制器添加到全局主控制器列表中。
  9. 从设备树中注册SPI设备。
  10. 返回操作的结果代码。
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;
}
  1. 首先,检查传入的设备指针dev是否为空,如果为空则返回NULL,表示分配失败。

  2. 接下来,通过调用kzalloc函数为SPI主控制器分配内存空间,大小为size + sizeof(*master)。其中size是驱动程序私有数据的大小,sizeof(*master)spi_master结构体本身的大小。这样做的原因是为了确保分配的内存空间足够容纳驱动程序私有数据以及spi_master结构体本身。通过将两者的大小相加,可以得到分配所需的总内存大小。

  3. 如果内存分配失败,即master为空指针,表示分配失败,返回NULL。

  4. 然后,通过调用device_initialize函数初始化主控制器的设备结构体dev,确保主控制器的设备结构体被正确初始化,设置默认值和状态,并分配必要的资源。

  5. 接着,设置主控制器的属性:bus_num设置为-1表示未指定总线编号,num_chipselect设置为1表示主控制器支持的片选信号数量为1,dev.class设置为指向spi_master_class的指针表示主控制器的设备类。

  6. 最后,设置主控制器的私有数据指针,即通过调用spi_master_set_devdata函数将指针设置为分配的内存空间的结尾处,即&master[1]

    由于master是指向spi_master结构体的指针,因此&master[1]表示master指针所指向的内存之后的一个位置。这种设置的目的是将私有数据指针与分配的内存空间相关联。通过将私有数据指针设置为&master[1],可以确保私有数据位于分配的内存空间中,以便在需要时可以通过访问该指针来访问和操作驱动程序特定的数据。

  7. 函数返回分配的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;
}
  1. 尝试使用设备树风格的匹配。调用of_driver_match_device函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。
  2. 如果设备与驱动程序未通过设备树匹配,尝试使用ACPI匹配。调用acpi_driver_match_device函数,检查设备与驱动程序是否匹配。如果匹配成功,则返回1。
  3. 如果SPI驱动程序具有id_table字段,则使用spi_match_id函数尝试通过ID表进行匹配。如果匹配成功,则返回1。
  4. 如果以上匹配方法都失败,则通过比较设备的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;
}
  1. spi_register_board_info接受一个指向SPI设备描述符数组的指针info,以及描述符的数量n。这些描述符包含了每个SPI设备的配置信息,如设备节点、SPI控制器的总线编号、最大速度、模块别名等。

  2. 函数内部会为每个设备描述符分配一个boardinfo结构体,并将其添加到全局的board_list链表中。

  3. 然后,遍历已注册的SPI控制器(通过spi_master_list链表),并将匹配到的控制器与设备描述符关联起来,以便后续的设备创建与配置。

  4. 最后,函数返回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;
}
  1. 首先,函数接收一个spi_master指针作为参数,表示要连接的SPI控制器。
  2. 函数通过调用spi_master_get增加SPI控制器的引用计数,确保控制器的有效性。如果引用计数增加失败(返回0),则返回NULL。
  3. 函数通过调用kzalloc为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则释放之前增加的SPI控制器的引用计数,并返回NULL。
  4. 函数对新分配的spi_device结构体进行初始化。它设置了设备的主控制器(master)、父设备(parent)为SPI控制器的设备结构体,总线(bus)为SPI总线类型,释放函数(release)为spidev_release,表示在设备释放时调用该函数进行清理,片选GPIO(cs_gpio)初始化为-ENOENT表示未设置。
  5. 函数使用spin_lock_init初始化统计信息(statistics)的自旋锁。
  6. 最后,函数调用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);elsedev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));done:mutex_unlock(&spi_add_lock);return status;
}
  1. 首先,函数获取到要添加的spi_device结构体中的spi_master指针以及其父设备的指针。
  2. 然后,函数对spi_devicechip_select进行验证,确保其值在有效范围内。
  3. 接下来,函数设置spi_device的总线ID字符串,用于标识该设备在SPI总线上的位置。
  4. 为了防止与已存在的设备冲突,函数使用互斥锁对添加操作进行了保护。
  5. 函数通过调用bus_for_each_dev遍历SPI总线上的所有设备,检查是否有其他设备使用了相同的chip_select。如果存在冲突,函数会返回错误。
  6. 如果SPI主控制器设置了cs_gpios(片选GPIO引脚),函数会将相应的GPIO引脚分配给spi_device
  7. 接下来,函数调用spi_setupspi_device进行初始化设置,以确保其正常工作。
  8. 最后,函数使用device_addspi_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;
}
  1. 首先,函数接收一个spi_master指针和一个spi_board_info结构体指针作为参数,分别表示要连接的SPI控制器和描述SPI设备的信息。
  2. 函数通过调用spi_alloc_device为新的SPI设备分配内存空间,并返回一个指向该设备的指针。如果内存分配失败,则返回NULL。
  3. 函数将SPI设备的属性从spi_board_info结构体中复制到新分配的spi_device结构体中。这些属性包括设备的片选引脚号(chip_select)、最大传输速率(max_speed_hz)、工作模式(mode)、中断号(irq)、模块别名(modalias)等。
  4. 如果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;
}
  1. 函数首先检查传输模式(mode)的合法性。如果同时设置了SPI_TX_DUALSPI_TX_QUAD,或者同时设置了SPI_RX_DUALSPI_RX_QUAD,会返回错误。如果设置了SPI_3WIRE模式,并且同时设置了SPI_TX_DUALSPI_TX_QUADSPI_RX_DUALSPI_RX_QUAD,也会返回错误。
  2. 函数检查传输模式是否被支持。如果设备的模式中包含控制器不支持的模式位,会给出警告并将不支持的模式位清除。如果存在不支持的模式位,会返回错误。
  3. 如果设备的每字位数(bits_per_word)为0,则设置默认值为8位。
  4. 函数调用__spi_validate_bits_per_word验证每字位数是否被支持。如果不被支持,会返回错误。
  5. 如果设备的最大时钟速度(max_speed_hz)为0,则设置默认值为控制器的最大时钟速度。
  6. 如果控制器定义了setup函数,会调用该函数进行设备的设置。
  7. 设置片选(chip select)为非激活状态。
  8. 最后,函数打印设备的设置信息,并返回设置的结果。

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);
}
  1. 函数接收两个参数,owner表示驱动的所有者模块,sdrv表示要注册的SPI驱动。
  2. 函数设置驱动的相关属性。首先将驱动的所有者模块设置为owner,将驱动的总线类型设置为spi_bus_type,表示该驱动属于SPI总线类型。
  3. 如果驱动定义了probe函数,将该函数指定为驱动的probe函数。probe函数在设备与驱动匹配成功后调用,用于初始化和注册设备。
  4. 如果驱动定义了remove函数,将该函数指定为驱动的remove函数。remove函数在设备与驱动解除匹配时调用,用于清理和注销设备。
  5. 如果驱动定义了shutdown函数,将该函数指定为驱动的shutdown函数。shutdown函数在系统关机时调用,用于执行驱动的关机处理。
  6. 最后,调用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;
}
  1. 设置SPI主设备的传输函数为spi_queued_transfer,该函数用于执行队列中的数据传输操作。
  2. 检查是否定义了transfer_one_message函数,如果没有定义,则将其设置为默认的spi_transfer_one_message函数。
  3. 调用spi_init_queue函数初始化队列。这个函数可能会设置队列的相关参数,分配内存等。
  4. 如果spi_init_queue返回非零值,表示初始化队列出现问题,会打印错误信息,并跳转到错误处理标签err_init_queue,然后返回错误码。
  5. master->queued标志设置为true,表示队列已经初始化。
  6. 调用spi_start_queue函数启动队列。这个函数可能会启动硬件传输和处理队列中的数据。
  7. 如果spi_start_queue返回非零值,表示启动队列出现问题,会打印错误信息,并跳转到错误处理标签err_start_queue。
  8. 最后,如果所有步骤都成功执行,函数返回0表示初始化和启动队列成功。
spi_init_queue

spi_init_queue 函数初始化了spi_master结构的 kthread_workerkthread_work 结构(内核 kthread_workerkthread_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, &param);}return 0;
}
  1. 创建一个sched_param结构体,并将其初始化为MAX_RT_PRIO-1。这个结构体用于设置线程的调度参数,包括实时优先级。
  2. master->runningmaster->busy标志设置为false,表示队列当前处于未运行和未忙碌状态。
  3. 使用init_kthread_worker函数初始化master->kworker结构体,这个结构体用于管理队列中的工作项。
  4. 调用kthread_run函数创建一个内核线程,并将其绑定到kthread_worker_fn函数上。该线程用于执行队列中的工作项。
  5. 如果kthread_run返回一个错误指针(PTR_ERR),表示创建线程失败,会打印错误信息并返回错误码。
  6. 使用init_kthread_work函数初始化master->pump_messages结构体,并将其与spi_pump_messages函数关联起来。这个函数表示在消息泵线程中要执行的工作。
  7. 如果master->rt为真,则将kworker_task线程设置为实时优先级(SCHED_FIFO),以减小传输延迟。函数使用sched_setscheduler函数设置线程的调度器和参数。
  8. 如果设置了实时优先级,会打印一条信息说明消息泵将以实时优先级运行。
  9. 最后,函数返回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;
}
  1. 使用spin_lock_irqsave函数获取队列自旋锁,以确保在操作期间的原子性。自旋锁用于保护多线程访问的临界区域,以防止竞态条件。
  2. 检查队列是否已经在运行或忙碌。如果是,则表示队列已经在操作中,无法启动新的操作。函数会释放自旋锁并返回-EBUSY错误码。
  3. 将running标志设置为true,表示队列正在运行。同时,将cur_msg设置为NULL,表示当前没有正在处理的消息。
  4. 使用spin_unlock_irqrestore函数释放自旋锁,允许其他线程继续访问队列。
  5. 使用queue_kthread_work函数将pump_messages工作项加入kworker队列。
  6. 最后,函数返回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);
}
  1. 这段代码中使用了memset函数和INIT_LIST_HEAD宏。memset函数用于将指定内存区域的内容设置为特定的值,这里将spi_message结构体的内存设置为零。INIT_LIST_HEAD宏用于初始化一个链表头,将其nextprev指针都指向自身,表示链表为空。

  2. 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);
}
  1. 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);
}
  1. 调用spi_message_init函数对给定的spi_message进行初始化,确保它是一个空的消息对象。

  2. 使用循环遍历传入的spi_transfer数组,逐个将spi_transfer结构体添加到spi_message的传输列表中。

  3. 在每次迭代中,使用spi_message_add_tail函数将当前的spi_transfer结构体添加到spi_message的传输列表的尾部。

数据传输

SPI数据传输的整体流程大致如下:

  1. 主设备初始化spi_master_setup():主设备被配置为适当的工作模式,包括时钟速率、数据位顺序和传输模式等。主设备也会初始化相关的硬件资源和寄存器。

  2. 主设备选中从设备spi_setup():通过将从设备的片选线(Slave Select)拉低,主设备选择要与之通信的从设备。可以有多个从设备连接到同一个主设备,并通过不同的片选线进行选择。

  3. 传输开始spi_transfer_one_message():主设备产生时钟信号(SCLK)并将其发送到从设备。时钟信号定义了数据传输的时序和速率。

  4. 数据传输spi_sync_transfer(): 用于同步进行SPI数据传输,等待传输完成。

    spi_async_transfer(): 用于异步进行SPI数据传输,可以在传输过程中执行其他任务。

    在每个时钟周期中,主设备通过主设备输出线(MOSI)将一个数据位发送给从设备,同时从设备通过主设备输入线(MISO)返回一个数据位。数据的传输顺序可以是最高有效位(MSB)先传输或最低有效位(LSB)先传输,取决于设备的配置。

  5. 时钟信号控制spi_transfer_one()spi_sync_transfer()spi_async_transfer():时钟信号的边沿(上升沿或下降沿)用于控制数据的采样和传输。具体的时钟边沿使用方式取决于主设备和从设备之间的协议和时序要求。

  6. 数据传输完成spi_chipselect():完成所需的数据传输后,主设备可以通过将从设备的片选线拉高来结束传输。这会释放从设备,并允许它响应其他主设备的请求。

  7. 传输结束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;elseret = __spi_async(spi, message);spin_unlock_irqrestore(&master->bus_lock_spinlock, flags);return ret;
}
  • 函数接受一个 spi_device 结构体指针 spi 和一个 spi_message 结构体指针 message 作为参数,表示要进行异步SPI传输的设备和消息。
  • 首先,调用 __spi_validate() 函数对 spimessage 进行验证,确保它们有效。如果验证失败,将返回错误代码。
  • 接下来,通过获取 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;elsemaster->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_bufrx_buf 不为空,则初始化传输完成的等待完成量(master->xfer_completion),然后调用 master->transfer_one() 函数执行实际的传输操作。如果传输失败(返回值小于0),则增加错误计数,输出错误信息,并跳转到 out 标签处。
  • 如果传输项的返回值大于0,表示传输成功但需要等待传输完成。计算超时等待时间 ms,然后使用 wait_for_completion_timeout() 等待传输完成。
  • 如果传输超时(ms 等于0),增加超时计数,输出错误信息,并将消息的状态设置为 -ETIMEDOUT
  • 如果传输项中的 tx_bufrx_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);
}
  1. 获取当前正在处理的消息 cur_msg 并保存到本地变量 mesg 中,使用自旋锁保护读取操作,确保原子性。
  2. 调用 spi_unmap_msg() 函数,解除消息中已映射的缓冲区。这个步骤是为了确保在消息处理完成后,相关的资源可以被释放,防止内存泄漏。
  3. 检查当前消息是否已经准备好并且主设备实现了 unprepare_message() 函数。如果满足条件,则调用 unprepare_message() 函数对消息进行反准备操作。这个操作通常用于释放为消息准备的特定资源,如申请的DMA通道或中断处理程序等。如果反准备操作返回非零值,表示出现了错误,将输出错误信息。
  4. 使用自旋锁保护对当前消息和相关标志的修改操作。首先,将 cur_msg 指针和准备状态标志 cur_msg_prepared 设置为NULL和false,表示当前消息已完成处理。然后,通过调用 queue_kthread_work() 将工作项 pump_messages 排队到内核工作队列中。这个工作项用于在后台线程中继续处理后续的消息。通过将工作项排队,可以确保消息处理的连续性和顺序性。
  5. 使用跟踪功能 trace_spi_message_done() 记录消息的完成情况。这可以用于调试和性能分析。
  6. 将消息的状态指针 state 设置为NULL,表示消息已经完成处理。
  7. 如果消息的 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**

https://stephenzhou.blog.csdn.net/article/details/100040318

https://blog.csdn.net/qq_42017846/article/details/130515879

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/186206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++ Primer Plus学习记录】循环和文本输入

目录 1.使用原始的cin进行输入 2.使用cin.get(char)进行补救 3.使用哪一个cin.get() 4.文件尾条件 循环完后的一项最常见、最重要的任务&#xff1a;逐字符地读取来自文件或键盘的文本。 cin对象支持3种不同模式的单字符输入&#xff0c;其用户接口各不相同。下面介绍如何…

鸿蒙HarmonyOS应用开发-ColumnRow组件

1 概述 一个丰富的页面需要很多组件组成&#xff0c;那么&#xff0c;我们如何才能让这些组件有条不紊地在页面上布局呢&#xff1f;这就需要借助容器组件来实现。 容器组件是一种比较特殊的组件&#xff0c;它可以包含其他的组件&#xff0c;而且按照一定的规律布局&#xf…

UE5富文本框学习(用途:A(名字)用刀(图片)击杀B(名字))

UE5-UMG教程-通用控件&#xff1a;多格式文本块&#xff08;RichTextBlock&#xff09;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Pu4y1k7Z2/?p54&spm_id_frompageDriver 结果示例&#xff1a; 1.添加富文本框 2.添加文字样式库 点添加&#xff0c;更改每行行…

Embedding压缩之hash embedding

在之前的两篇文章 CTR特征重要性建模&#xff1a;FiBiNet&FiBiNet模型、CTR特征建模&#xff1a;ContextNet & MaskNet中&#xff0c;阐述了特征建模的重要性&#xff0c;并且介绍了一些微博在特征建模方面的研究实践&#xff0c;再次以下面这张图引出今天的主题&#…

Unity 一些常用注解

在Unity中有一些比较常用的注解&#xff1a; 1、[SerializeField]&#xff1a;将私有字段或属性显示在 Unity 编辑器中&#xff0c;使其可以在 Inspector 窗口中进行编辑。 2、[Range(min, max)]&#xff1a;限制数值字段或属性的范围&#xff0c;在 Inspector 窗口中以滑动条…

简单好用!日常写给 ChatGPT 的几个提示词技巧

ChatGPT 很强&#xff0c;但是有时候又显得很蠢&#xff0c;下面是使用 GPT4 的一个实例&#xff1a; 技巧一&#xff1a;三重冒号 """ 引用内容使用三重冒号 """&#xff0c;让 ChatGPT 清晰引用的内容&#xff1a; 技巧二&#xff1a;角色设定…

【C++】string模拟

string讲解&#xff1a;【C】String类-CSDN博客 基本框架 #pragma once #include <iostream> using namespace std; ​ namespace wzf {class string{public:// 默认构造函数string(): _str(new char[1]), _size(0), _capacity(0){_str[0] \0; // 在没有内容时仍要有终…

计算机体系结构----基本概念(一)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----计算机系统结构教程(第二版)张晨曦等 计算机体系结构----基本概念 计算机系统设计的定量原理1. Amdahl定律&#xff08;阿姆达尔定律&#xff09;2. CPU性能公式3. 程序的局部性原理 计算…

计算机组成原理期中题库

计算机组成原理题目集 2.1 下面是关于计算机中存储器容量单位的叙述&#xff0c;其中错误的是 A. 最基本的计量单位是字节&#xff08;Byte&#xff09;&#xff0c;一个字节等于8bit B. 一台计算机的编址单位、指令字长和数据字长都一样&#xff0c;且是字节的整数倍 C. 最小…

C++ 学习之函数成员指针的一个小细节

看看下面的代码&#xff0c;你能看出错误吗 class A { public:void fun(){}}; int main() {A a;void (A:: * p)() &A::fun;(*p)(); } 这段代码在调用成员函数时存在问题。正确的方式是使用对象来调用成员函数&#xff0c;而不是通过指针。以下是修正后的代码&#xff1a…

JAVA基础进阶(十一)

一、创建线程的三种方式 Java语言中是用Thread类来表示线程&#xff0c;线程的创建和开启都是通过Thread类来实现的。 继承Thread类重写run方法。 调用线程对象的start()方法启动线程&#xff08;启动后还是执行run方法的&#xff09;,而不是调用创建的子类对象的run()方法。…

MSUSB30模拟开关可Pin to Pin兼容FSUSB30/SGM7222

MSUSB30/MSUSB30N 是一款高速、低功耗双刀双掷 USB 模拟开关芯片&#xff0c;其工作电压范围是1.8V 至5.5V。可Pin to Pin兼容FSUSB30/SGM7222。其具有低码间偏移、高通道噪声隔离度、宽带宽的特性。 MSUSB30/MSUSB30N 主要应用范围包括&#xff1a;具有 USB2.0 接口的手持设备…

事件机制?

事件流&#xff1a; 描述的页面接收事件的顺序。先进行事件捕获 到达目标元素 在进行事件冒泡 分为事件捕获和事件冒泡 事件冒泡&#xff1a;从具体元素从内向外依次触发事件 从下面这个小案例可以清楚了解什么是事件冒泡 <!DOCTYPE html> <html lang"en"…

C++ -- 每日选择题 -- Day2

第一题 1. 下面代码中sizeof(A)结果为&#xff08;&#xff09; #pragma pack(2) class A {int i;union U{char str[13];int i;}u;void func() {};typedef char* cp;enum{red,green,blue}color; }; A&#xff1a;20 B&#xff1a;21 C&#xff1a;22 D&#xff1a;24 答案及解析…

智能优化算法应用:基于纵横交叉算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于纵横交叉算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于纵横交叉算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.纵横交叉算法4.实验参数设定5.算法结果6.参考…

【字符串探秘:手工雕刻的String类模拟实现大揭秘】

【本节目标】 1. string类的模拟实现 2.C基本类型互转string类型 3.编码表 &#xff1a;值 --- 符号对应的表 4.扩展阅读 1. string类的模拟实现 1.1 经典的string类问题 上面已经对string类进行了简单的介绍&#xff0c;大家只要能够正常使用即可。在面试中&#xff0c;…

1.网络编程基础知识 - 基础概念、TCP网络通信、UDP网络通信

网络编程 文章目录 网络编程一、概念1.1 网络1.2 IP地址1.2.1 IPv4 介绍1.2.2 IPv6 介绍1.2.3 查看IP地址 1.3 域名和端口1.4 网络协议1.5 TCP与UDP1.6 InetAddress类1.7 Socket 二、TCP网络通信编程2.1 介绍2.2 案例2.2.1 字节流编程案例12.2.2 字节流编程案例22.2.3 字符流编…

5. 文件属性和目录

5. 文件属性和目录 1. Linux 系统的文件类型1.1 普通文件1.2 目录文件1.3 字符设备文件和块设备文件1.4 符号链接文件1.5 管道文件1.6 套接字文件 2. stat 系统调用2.1 struct stat 结构体2.2 st_mode 变量2.3 struct timespec 结构体 3. fstat 和 lstat 函数3.1 fstat 函数3.2…

python中的序列

文章目录 序列类型标准类型运算符标准类型运算符序列类型运算符字符串 序列类型 字符串 列表 元组 由元组构成的列表 标准类型运算符 &#xff08;1&#xff09;按字符串大小比较 标准类型运算符 序列类型运算符 序列类型转换内建函数 注&#xff1a; &#xff08;1&#xff…

深入理解MySQL索引底层数据结构与算法

索引的本质 索引是帮助MySQL高效获取数据的排好序的数据结构 索引的数据结构 二叉树红黑数Hash表B-Tree MySQL索引底层为啥不用二叉树 如图&#xff0c;对单边增长的数据&#xff0c;索引效率没有什么提升 MySQL索引底层为啥不用红黑数 红黑数&#xff1a;二叉平衡树 随…