Linux内核SPI子系统初始化驱动架构
linuxSPI驱动——gpio模拟spi驱动(转载)
linuxSPI驱动——gpio模拟spi驱动(转载)⼀:⾸先在我的平台注册platform_device,保证能让spi-gpio.c能执⾏到probe函数。
1: struct spi_gpio_platform_data {2: unsigned sck;3: unsigned mosi;4: unsigned miso;5:6: u16 num_chipselect;7: };1: //#define NCS GPIO_PB(2) //定义SS所对应的GPIO接⼝编号2: //#define SCLK GPIO_PB(0) //定义SCLK所对应的GPIO接⼝编号3: //#define MOSI GPIO_PB(4) //定义SCLK所对应的GPIO接⼝编号4: //#define MISO GPIO_PB(1)5: static struct spi_gpio_platform_data jz_spi_gpio_data = {6: .sck = GPIO_PB(0), //GPIO_SPI_SCK,7: .mosi = GPIO_PB(4), //GPIO_SPI_MOSI,8: .miso = GPIO_PB(1), //GPIO_SPI_MISO,9: .num_chipselect = 1,10: };11:12: struct platform_device jz_spi_gpio_device = {13: .name = "spi_gpio",14: .id = 0,15: .dev = {16: .platform_data = &jz_spi_gpio_data,17: },18: };注册platform device1: platform_device_register(&jz_spi_gpio_device);⼆:注册platform_driver在spi_gpio.c⾥⾯注册platform driver1: MODULE_ALIAS("platform:" DRIVER_NAME);2:3: static struct platform_driver spi_gpio_driver = {4: = DRIVER_NAME,5: .driver.owner = THIS_MODULE,6: .remove = __exit_p(spi_gpio_remove),7: };8:9: static int __init spi_gpio_init(void)10: {11: return platform_driver_probe(&spi_gpio_driver, spi_gpio_probe);12: }13: module_init(spi_gpio_init);14:15: static void __exit spi_gpio_exit(void)16: {17: platform_driver_unregister(&spi_gpio_driver);18: }19: module_exit(spi_gpio_exit);20:21:22: MODULE_DESCRIPTION("SPI master driver using generic bitbanged GPIO ");23: MODULE_AUTHOR("David Brownell");24: MODULE_LICENSE("GPL");三:具体算法分析1: struct spi_gpio {2: struct spi_bitbang bitbang; /* gpio 模拟spi算法相关的结构 */3: struct spi_gpio_platform_data pdata; /* spi platform data 对应模拟spi的四个gpio编号 */4: struct platform_device *pdev; /* 对应注册的 platform device */5: };1:2: static int __init spi_gpio_probe(struct platform_device *pdev)3: {4: int status;5: struct spi_master *master;6: struct spi_gpio *spi_gpio;7: struct spi_gpio_platform_data *pdata;8: u16 master_flags = 0;9:10: pdata = pdev->dev.platform_data; /* 存放spi的四根gpio */11: #ifdef GENERIC_BITBANG12: if (!pdata || !pdata->num_chipselect)13: return -ENODEV;14: #endif15:16: /* 申请注册四个gpio */17: status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags);18: if (status < 0) {19: return status;20: }21:22: /* alloc a spi master ,master->dev->p->driver_data = &master[1]*/23: master = spi_alloc_master(&pdev->dev, sizeof *spi_gpio);24: if (!master) {25: status = -ENOMEM;26: goto gpio_free;27: }28: /* spi_gpio指向⼀块空间, 即指向mstaer[1]29: pdev->dev->p->driver_data = spi_gpio;30: 初始化spi_gpio31: */32: spi_gpio = spi_master_get_devdata(master);33: platform_set_drvdata(pdev, spi_gpio);34:35: spi_gpio->pdev = pdev;36: if (pdata)37: spi_gpio->pdata = *pdata;38:39: master->flags = master_flags;40: master->bus_num = pdev->id;41: master->num_chipselect = SPI_N_CHIPSEL;42: master->setup = spi_gpio_setup; /* setup ⽐如cs引脚申请 */43: master->cleanup = spi_gpio_cleanup;44: /* spi_gpio->bitbang.master = master */45: spi_gpio->bitbang.master = spi_master_get(master);46: spi_gpio->bitbang.chipselect = spi_gpio_chipselect;47: /* spi_gpio->bitbang.txrx_word 数组函数四个元素指针,分别指向spi四种mode算法函数 */ 48: if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) {49: spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0;50: spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1;51: spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2;52: spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3;53: } else {54: spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0;55: spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1;56: spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2;57: spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3;58: }59: /* spi_gpio->bitbang.setup_transfer初始化传输的bits_per_word和speed */60: spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer;61: spi_gpio->bitbang.flags = SPI_CS_HIGH;62: /* spi_gpio->bitbang相关算法接⼝初始化 */63: status = spi_bitbang_start(&spi_gpio->bitbang);64: if (status < 0) {65: spi_master_put(spi_gpio->bitbang.master);66: gpio_free:67: if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)68: gpio_free(SPI_MISO_GPIO);69: if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI)70: gpio_free(SPI_MOSI_GPIO);71: gpio_free(SPI_SCK_GPIO);72: spi_master_put(master);73: }74:75: return status;76: }四:总之最终让spi_gpi0整个对象存放了整个gpio模拟spi的算法结构;⽽pdev->dev->p->driver_data = spi_gpio;platform device和 platform driver两者match结果是:root@CarRadio:/# ls /sys/bus/platform/devices/spi_gpio.0/ driver modalias power spi0.0 spi_master subsystem uevent root@CarRadio:/# ls /sys/bus/platform/devices/spi_gpio.0/driver/ spi_gpio.0 uevent。
嵌入式系统设计智慧树知到答案章节测试2023年山东科技大学
第一章测试1.和PC系统机相比嵌入式系统不具备以下哪个特点()。
A:可执行多任务B:系统精简C:系统内核小D:专用性强答案:A2.嵌入式系统有硬件和软件部分构成,以下()不属于嵌入式系统软件。
A:驱动B:嵌入式中间件C:系统软件D:FPGA编程软件答案:D3.以下哪个不是嵌入式系统设计的主要目标?()A:低成本B:超高性能C:低功耗D:实时要求高答案:B4.下面哪个系统不属于嵌入式系统()A:MP3 播放器B:“银河玉衡”核心路由器C:“银河”巨型计算机D:GPS 接收机答案:C5.下面关于哈佛结构描述正确的是()A:存储空间与 IO 空间分离B:程序存储空间与数据存储空间合并C:存储空间与 IO 空间合并D:程序存储空间与数据存储空间分离答案:D6.嵌入式操作系统的主要目标并不包括()A:代码体积B:实时处理能力C:与硬件的交互能力D:强大多任务支持答案:D7.以下属于嵌入式操作系统的是()A:VxWorks操作系统B:Ubuntu操作系统C:µC/OS-II操作系统D:Linux操作系统答案:AC8.以MCU为核心的嵌入式产品至少应包括()A:输出部分B:通信部分C:输入部分D:显示部分答案:ABC第二章测试1.Cortex-M处理器采用的架构是()A:v7B:v6C:v5TED:v4T答案:A2.Cortex-M3的提供的流水线是()A:5级B:2级C:3级D:8级答案:C3.Cortex-M3系列处理器支持Thumb-2指令集。
()A:对B:错答案:A4.STM32系列MCU在使用电池供电时,提供3.3~5V的低电压工作能力。
()A:对B:错答案:B5.STM32处理器的LQPF100封装芯片的最小系统只需7个滤波电容作为外围器件。
()A:错B:对答案:B6.以下哪项关于 SRAM和 DRAM的区别是不对。
()A:SRAM 比 DRAM慢B:SRAM比 DRAM耗电多C:DRM需要周期性刷新D:DRAM 存储密度比 SRAM高得多答案:A第三章测试1.固件包里的Library文件夹包括一个标准的模板工程,该工程编译所有的库文件和所有用于创建一个新工程所必须的用户可修改文件。
Linux下SPI驱动测试程序
Linux下的SPI总线驱动(一)2013-04-12 15:08:46分类:LINUX版权所有,转载请说明转自一.SPI理论介绍SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。
和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。
由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。
SCLK信号线只由主设备控制,从设备不能控制信号线。
同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。
也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。
不同的SPI 设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。
二.SPI驱动移植我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植Step1:在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include <linux/spi/>#include <../mach-s3c2410/include/mach/>然后加入如下代码:static struct spi_board_info s3c2410_spi0_board[] ={[0] = {.modalias = "spidev", us_num = 0, hip_select = 0, rq = IRQ_EINT9, ax_speed_hz = 500 * 1000,in_cs = S3C2410_GPG(2),.num_cs = 1, us_num = 0, pio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, odalias = "spidev",.bus_num = 1,.chip_select = 0,.irq = IRQ_EINT2,.max_speed_hz = 500 * 1000,}};static struct s3c2410_spi_info s3c2410_spi1_platdata = {.pin_cs = S3C2410_GPG(3),.num_cs = 1,.bus_num = 1,.gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,};Step2:在mini2440_devices[]平台数组中添加如下代码:&s3c_device_spi0,&s3c_device_spi1,Step3:最后在mini2440_machine_init函数中加入如下代码:&s3c2410_spi0_platdata;spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board)); &s3c2410_spi1_platdata;spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board)); Step4:最后需要修改arch/arm/plat-s3c24xx/KConfig文件找到config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13boolhelpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7boolhelpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.修改为config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13bool "S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13"helpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7bool "S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7"helpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.Step5:最后make menuconfig配置,选中System Type和SPI support相应文件Step6:执行make生成zInage,将编译好的内核导入开发板,并且编译测试程序运行即可。
Linux下SPI驱动测试程序
L i n u x下S P I驱动测试程序-CAL-FENGHAI-(2020YEAR-YICAI)_JINGBIANLinux下的SPI总线驱动(一)2013-04-12 15:08:46分类:LINUX版权所有,转载请说明转自一.SPI理论介绍SPI总线全名,串行外围设备接口,是一种串行的主从接口,集成于很多微控制器内部。
和I2C使用2根线相比,SPI总线使用4根线:MOSI (SPI 总线主机输出/ 从机输入)、 MISO (SPI总线主机输入/从机输出)、SCLK(时钟信号,由主设备产生)、CS(从设备使能信号,由主设备控制)。
由于SPI总线有专用的数据线用于数据的发送和接收,因此可以工作于全双工,当前市面上可以找到的SPI外围设备包括RF芯片、智能卡接口、E2PROM、RTC、触摸屏传感器、ADC。
SCLK信号线只由主设备控制,从设备不能控制信号线。
同样,在一个基于SPI的设备中,至少有一个主控设备。
这样传输的特点:这样的传输方式有一个优点,与普通的串行通讯不同,普通的串行通讯一次连续传送至少8位数据,而SPI允许数据一位一位的传送,甚至允许暂停,因为SCLK 时钟线由主控设备控制,当没有时钟跳变时,从设备不采集或传送数据。
也就是说,主设备通过对SCLK时钟线的控制可以完成对通讯的控制。
SPI还是一个数据交换协议:因为SPI的数据输入和输出线独立,所以允许同时完成数据的输入和输出。
不同的SPI设备的实现方式不尽相同,主要是数据改变和采集的时间不同,在时钟信号上沿或下沿采集有不同定义,具体请参考相关器件的文档。
在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。
在多个从设备的系统中,每个从设备需要独立的使能信号,硬件上比I2C 系统要稍微复杂一些。
二.SPI驱动移植我们下面将的驱动的移植是针对Mini2440的SPI驱动的移植Step1 :在Linux Source Code中修改arch/arm/mach-s3c2440/文件,加入头文件:#include <linux/spi/>#include <../mach-s3c2410/include/mach/>然后加入如下代码:static struct spi_board_info s3c2410_spi0_board[] ={[0] = {.modalias = "spidev", us_num = 0, hip_select = 0, rq = IRQ_EINT9, ax_speed_hz = 500 * 1000, in_cs = S3C2410_GPG(2),.num_cs = 1, us_num = 0, pio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13, odalias = "spidev",.bus_num = 1,.chip_select = 0,.irq = IRQ_EINT2,.max_speed_hz = 500 * 1000,}};static struct s3c2410_spi_info s3c2410_spi1_platdata = {.pin_cs = S3C2410_GPG(3),.num_cs = 1,.bus_num = 1,.gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,};Step2:在mini2440_devices[]平台数组中添加如下代码:&s3c_device_spi0,&s3c_device_spi1,Step3:最后在mini2440_machine_init函数中加入如下代码:&s3c2410_spi0_platdata;spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));&s3c2410_spi1_platdata;spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));Step4:最后需要修改arch/arm/plat-s3c24xx/KConfig文件找到config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13boolhelpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7boolhelpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.修改为config S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13bool "S3C24XX_SPI_BUS0_GPE11_GPE12_GPE13"helpSPI GPIO configuration code for BUS0 when connected toGPE11, GPE12 and GPE13.config S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7bool "S3C24XX_SPI_BUS1_GPG5_GPG6_GPG7"helpSPI GPIO configuration code for BUS 1 when connected toGPG5, GPG6 and GPG7.Step5:最后make menuconfig配置,选中System Type和SPI support相应文件Step6:执行make生成zInage,将编译好的内核导入开发板,并且编译测试程序运行即可。
Linuxkernel有关spi设备树参数解析
Linuxkernel有关spi设备树参数解析⼀、最近做了⼀个 spi 设备驱动从板级设备驱动升级到设备树设备驱动,这其中要了解 spi 设备树代码的解析。
⼆、设备树配置如下:503 &spi0 {504 status = "okay";505 pinctrl-name = "default";506 pinctrl-0 = <&spi0_pins>;507 ti,pindir-d0-out-d1-in;508509 wk2124A {510 compatible = "wk2124A"; // 匹配字符串511 reg = <0>; // cs512 # spi-cpha = <1>; // 配置 spi 的模式513 # spi-tx-bus-width = <1>; // 这是是 spi-tx 的总线宽度514 # spi-rx-bus-width = <1>;515 spi-max-frequency = <10000000>; // spi 最⼤速率配置516 };517 };三、代码跟踪// drivers/spi/spi.c2772 postcore_initcall(spi_init); // spi_init2733 static int __init spi_init(void)2734 {2735 int status;27362737 buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);2738 if (!buf) {2739 status = -ENOMEM;2740 goto err0;2741 }27422743 status = bus_register(&spi_bus_type);2744 if (status < 0)2745 goto err1;27462747 status = class_register(&spi_master_class);2748 if (status < 0)2749 goto err2;27502751 if (IS_ENABLED(CONFIG_OF_DYNAMIC))2752 WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); // 这⾥要注册主机和从机27532754 return 0;27552756 err2:2757 bus_unregister(&spi_bus_type);2758 err1:2759 kfree(buf);2760 buf = NULL;2761 err0:2762 return status;2763 }2726 static struct notifier_block spi_of_notifier = {2727 .notifier_call = of_spi_notify,2728 };2686 static int of_spi_notify(struct notifier_block *nb, unsigned long action,2687 void *arg)2688 {2689 struct of_reconfig_data *rd = arg;2690 struct spi_master *master;2691 struct spi_device *spi;26922693 switch (of_reconfig_get_state_change(action, arg)) {2694 case OF_RECONFIG_CHANGE_ADD:2695 master = of_find_spi_master_by_node(rd->dn->parent); // 找到主机节点2696 if (master == NULL)2697 return NOTIFY_OK; /* not for us */26982699 spi = of_register_spi_device(master, rd->dn); // ---> 注册设备2700 put_device(&master->dev);27222723 return NOTIFY_OK;2724 }1428 #if defined(CONFIG_OF)1429 static struct spi_device *1430 of_register_spi_device(struct spi_master *master, struct device_node *nc)1431 {1432 struct spi_device *spi;1433 int rc;1434 u32 value;14351436 /* Alloc an spi_device */1437 spi = spi_alloc_device(master);1438 if (!spi) {1439 dev_err(&master->dev, "spi_device alloc error for %s\n",1440 nc->full_name);1441 rc = -ENOMEM;1442 goto err_out;1443 }14441445 /* Select device driver */1446 rc = of_modalias_node(nc, spi->modalias, // 匹配到从机1447 sizeof(spi->modalias));1448 if (rc < 0) {1449 dev_err(&master->dev, "cannot find modalias for %s\n",1450 nc->full_name);1451 goto err_out;1452 }14531454 /* Device address */1455 rc = of_property_read_u32(nc, "reg", &value); // 设备节点 reg 表⽰ cs1456 if (rc) {1457 dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",1458 nc->full_name, rc);1459 goto err_out;1460 }1461 spi->chip_select = value;14621463 /* Mode (clock phase/polarity/etc.) */ // 选择 spi 的模式1464 if (of_find_property(nc, "spi-cpha", NULL))1465 spi->mode |= SPI_CPHA;1466 if (of_find_property(nc, "spi-cpol", NULL))1467 spi->mode |= SPI_CPOL;1468 if (of_find_property(nc, "spi-cs-high", NULL)) // 选择 spi cs 是⾼有效还是低有效 1469 spi->mode |= SPI_CS_HIGH;1470 if (of_find_property(nc, "spi-3wire", NULL))1471 spi->mode |= SPI_3WIRE;1472 if (of_find_property(nc, "spi-lsb-first", NULL))1473 spi->mode |= SPI_LSB_FIRST;14741475 /* Device DUAL/QUAD mode */ // 选择单线还是双线通道1476 if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {1477 switch (value) {1478 case 1:1479 break;1480 case 2:1481 spi->mode |= SPI_TX_DUAL;1482 break;1483 case 4:1484 spi->mode |= SPI_TX_QUAD;1485 break;1486 default:1487 dev_warn(&master->dev,1488 "spi-tx-bus-width %d not supported\n",1489 value);1490 break;1491 }1492 }14931494 if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {1495 switch (value) {1496 case 1:1497 break;1498 case 2:1499 spi->mode |= SPI_RX_DUAL;1500 break;1501 case 4:1502 spi->mode |= SPI_RX_QUAD;1503 break;1504 default:1505 dev_warn(&master->dev,1506 "spi-rx-bus-width %d not supported\n",1508 break;1509 }1510 }15111512 /* Device speed */ // 设备速度配置1513 rc = of_property_read_u32(nc, "spi-max-frequency", &value);1514 if (rc) {1515 dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n", 1516 nc->full_name, rc);1517 goto err_out;1518 }1519 spi->max_speed_hz = value;15201521 /* Store a pointer to the node in the device structure */1522 of_node_get(nc);1523 spi->dev.of_node = nc; // 保存设备结构体15241525 /* Register the new device */1526 rc = spi_add_device(spi);1527 if (rc) {1528 dev_err(&master->dev, "spi_device register error %s\n",1529 nc->full_name);1530 goto err_out;1531 }15321533 return spi;15341535 err_out:1536 spi_dev_put(spi);1537 return ERR_PTR(rc);1538 }。
Linux驱动:SPI驱动编写要点
Linux驱动:SPI驱动编写要点题外话:⾯对成功和失败,⼀个⼈有没有“冠军之⼼”,直接影响他的表现。
⼏周前剖析了Linux SPI 驱动框架,算是明⽩个所以然,对于这么⼀个庞⼤的框架,并不是每⼀⾏代码都要⾃⼰去敲,因为前⼈已经把这个框架搭建好了,作为驱动开发者的我们只需要搞清楚哪⼀部分是需要⾃⼰修改或重新编写就OK了。
结合Linux内核⾯向对象的设计思想,SPI总的设计思路⼤概是这样的:第①处:内核中抽象了SPI控制器,让spi_master成为他的象征,他的实例化对象就是与硬⽣⽣的SPI控制器对应的,在Linux内核中习惯将集成到SOC上的控制器⽤假想的platform总线来进⾏管理,于是乎spi_master的实例化就得依靠platform_device和platform_driver来联⼿完成了。
细嚼慢咽:这⾥的platform_device就相当于是spi_master的静态描述:包括⼏号控制器、寄存器地址、引脚配置等等,把这些信息以“资源”的形式挂在platform_device上,等platform_driver找到命中注定的那个他之后就可以获得这个(静态描述)资源,probe中⽤这些资源就⽣出了spi_master实例对象。
这⼀系列流程前辈们都已经做好了,我们要做的就是将这静态描述platform_device修改成和⾃⼰SOC中的spi 控制器⼀致的特性即可。
即:适当修改arch/arm/mach-s5pv210/dev-spi.c中platform_device涉及的成员。
1// SPI0的寄存器地址2static struct resource s5pv210_spi0_resource[] = {3 [0] = {4 .start = S5PV210_PA_SPI0,5 .end = S5PV210_PA_SPI0 + 0x100 - 1,6 .flags = IORESOURCE_MEM,7 },8 [1] = {9 .start = DMACH_SPI0_TX,10 .end = DMACH_SPI0_TX,11 .flags = IORESOURCE_DMA,12 },13 [2] = {14 .start = DMACH_SPI0_RX,15 .end = DMACH_SPI0_RX,16 .flags = IORESOURCE_DMA,17 },18 [3] = {19 .start = IRQ_SPI0,20 .end = IRQ_SPI0,21 .flags = IORESOURCE_IRQ,22 },23 };2425/**26 * struct s3c64xx_spi_info - SPI Controller defining structure27 * @src_clk_nr: Clock source index for the CLK_CFG[SPI_CLKSEL] field.28 * @src_clk_name: Platform name of the corresponding clock.29 * @clk_from_cmu: If the SPI clock/prescalar control block is present30 * by the platform's clock-management-unit and not in SPI controller.31 * @num_cs: Number of CS this controller emulates.32 * @cfg_gpio: Configure pins for this SPI controller.33 * @fifo_lvl_mask: All tx fifo_lvl fields start at offset-634 * @rx_lvl_offset: Depends on tx fifo_lvl field and bus number35 * @high_speed: If the controller supports HIGH_SPEED_EN bit36 * @tx_st_done: Depends on tx fifo_lvl field37*/38static struct s3c64xx_spi_info s5pv210_spi0_pdata = {39 .cfg_gpio = s5pv210_spi_cfg_gpio, //将GPIO配置成SPI0引脚的函数40 .fifo_lvl_mask = 0x1ff,41 .rx_lvl_offset = 15,42 .high_speed = 1, //看s5pv210的使⽤⼿册P901可知:这是⽤来配置CH_CFG寄存器的,主要是210⽤于从设备时......43 .tx_st_done = 25,44 };4546static u64 spi_dmamask = DMA_BIT_MASK(32);4748struct platform_device s5pv210_device_spi0 = {49 .name = "s3c64xx-spi",50 .id = 0,51 .num_resources = ARRAY_SIZE(s5pv210_spi0_resource),52 .resource = s5pv210_spi0_resource,53 .dev = {54 .dma_mask = &spi_dmamask,55 .coherent_dma_mask = DMA_BIT_MASK(32),56 .platform_data = &s5pv210_spi0_pdata,//特殊的spi_master数据57 },58 };platform_device第②处:添加/修改SPI外设“静态描述”的结构。
Linux内核中的IPSEC实现
Linux内核中的IPSEC实现(2)本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@来源:4. 状态(xfrm_state)处理本节所介绍的函数都在net/xfrm/xfrm_state.c中定义。
4.1 状态分配状态分配函数为xfrm_state_alloc(), 该函数被pfkey_msg2xfrm_state()函数调用, pfkey_msg2xfrm_state()函数是将标准的pfkey_msg(SA结构)转换为xfrm状态, 同时该函数也被其他状态处理函数调用.struct xfrm_state *xfrm_state_alloc(void){struct xfrm_state *x;// 分配空间x = kzalloc(sizeof(struct xfrm_state), GFP_ATOMIC);if (x) {// 使用数初始化为1atomic_set(&x->refcnt, 1);// 被0个ipsec通道使用atomic_set(&x->tunnel_users, 0);// 初始化链表节点, 状态可按目的地址, 源地址和SPI挂接到不同链表INIT_HLIST_NODE(&x->bydst);INIT_HLIST_NODE(&x->bysrc);INIT_HLIST_NODE(&x->byspi);// 状态定时器init_timer(&x->timer);// 定时器处理函数x->timer.function = xfrm_timer_handler;x->timer.data = (unsigned long)x;// 回放检测定时器init_timer(&x->rtimer);// 回放定时器处理函数x->rtimer.function = xfrm_replay_timer_handler;x->rtimer.data = (unsigned long)x;x->curlft.add_time = (unsigned long)_sec; // SA生命期参数x->lft.soft_byte_limit = XFRM_INF;x->lft.soft_packet_limit = XFRM_INF;x->lft.hard_byte_limit = XFRM_INF;x->lft.hard_packet_limit = XFRM_INF;// 回放处理参数x->replay_maxage = 0;x->replay_maxdiff = 0;// 初始化状态锁spin_lock_init(&x->lock);}return x;}EXPORT_SYMBOL(xfrm_state_alloc);// 状态定时器超时处理函数static void xfrm_timer_handler(unsigned long data){struct xfrm_state *x = (struct xfrm_state*)data;unsigned long now = (unsigned long)_sec;long next = LONG_MAX;int warn = 0;spin_lock(&x->lock);// 如果该xfrm状态已经处于死亡状态, 可以返回了if (x->km.state == XFRM_STATE_DEAD)goto out;// 如果处于生命期到期状态, 转到期处理if (x->km.state == XFRM_STATE_EXPIRED)goto expired;// 如果到期了还要强制要增加一些时间if (x->lft.hard_add_expires_seconds) {// 计算强制增加的超时时间long tmo = x->lft.hard_add_expires_seconds +x->curlft.add_time - now;// 没法增加超时了, 到期if (tmo <= 0)goto expired;if (tmo < next)next = tmo;}// 如果到期了还要强制要增加的使用时间if (x->lft.hard_use_expires_seconds) {// 计算强制增加的使用时间long tmo = x->lft.hard_use_expires_seconds +(x->e_time ? : now) - now;// 没法增加超时了, 到期if (tmo <= 0)goto expired;if (tmo < next)next = tmo;}// dying表示软性增加超时已经不可用if (x->km.dying)goto resched;// 如果到期了还要软性要增加一些时间if (x->lft.soft_add_expires_seconds) {// 计算软性增加的时间long tmo = x->lft.soft_add_expires_seconds +x->curlft.add_time - now;// 软性增加超时不可用了if (tmo <= 0)warn = 1;else if (tmo < next)next = tmo;}// 如果到期了还要软性要增加的使用时间if (x->lft.soft_use_expires_seconds) {// 计算软性增加的使用时间long tmo = x->lft.soft_use_expires_seconds +(x->e_time ? : now) - now;// 软性增加超时不可用了if (tmo <= 0)warn = 1;else if (tmo < next)next = tmo;}// dying即为软性增加超时是否可用标志x->km.dying = warn;// 软性增加超时已比不可用, 进行状态的超时到期通知if (warn)km_state_expired(x, 0, 0);resched:// 如果增加的超时有效, 修改定时器超时时间if (next != LONG_MAX)mod_timer(&x->timer, jiffies + make_jiffies(next));goto out;expired:// 状态到期if (x->km.state == XFRM_STATE_ACQ && x->id.spi == 0) {// 如果这个状态是ACQ类型状态(不是用户空间主动建立的状态,而是内核根据策略主动要求// 用户空间进行IKE协商建立的状态)// 状态设置为到期x->km.state = XFRM_STATE_EXPIRED;// 唤醒等待队列准备进行垃圾搜集操作wake_up(&km_waitq);next = 2;goto resched;}// 删除状态, 进行状态的到期通知if (!__xfrm_state_delete(x) && x->id.spi)// 1表示是硬性到期了km_state_expired(x, 1, 0);out:spin_unlock(&x->lock);}// 回放定时器超时回调函数static void xfrm_replay_timer_handler(unsigned long data){struct xfrm_state *x = (struct xfrm_state*)data;spin_lock(&x->lock);// 只是状态为有效时才检查if (x->km.state == XFRM_STATE_VALID) {// 是否有NETLINK的监听者if (xfrm_aevent_is_on())// 通知回放超时事件xfrm_replay_notify(x, XFRM_REPLAY_TIMEOUT);else// 设置通知推迟标志x->xflags |= XFRM_TIME_DEFER;}spin_unlock(&x->lock);}状态初始化:int xfrm_init_state(struct xfrm_state *x){struct xfrm_state_afinfo *afinfo;int family = x->props.family;int err;err = -EAFNOSUPPORT;// 获取协议族信息结构afinfo = xfrm_state_get_afinfo(family);if (!afinfo)goto error;err = 0;// 协议族信息初始化if (afinfo->init_flags)err = afinfo->init_flags(x);xfrm_state_put_afinfo(afinfo);if (err)goto error;err = -EPROTONOSUPPORT;// 获取可用协议(ah, esp, ipcomp, ip)x->type = xfrm_get_type(x->id.proto, family);if (x->type == NULL)goto error;err = x->type->init_state(x);if (err)goto error;// 获取可用模式(transport, tunnel)x->mode = xfrm_get_mode(x->props.mode, family);if (x->mode == NULL)goto error;// 状态设置为VALIDx->km.state = XFRM_STATE_VALID;error:return err;}EXPORT_SYMBOL(xfrm_init_state);4.2 状态删除状态删除函数为xfrm_state_delete(), 该函数被pfkey_delete函数调用.// 这个函数只是__xfrm_state_delete()加锁的包裹函数int xfrm_state_delete(struct xfrm_state *x){int err;spin_lock_bh(&x->lock);err = __xfrm_state_delete(x);spin_unlock_bh(&x->lock);return err;}EXPORT_SYMBOL(xfrm_state_delete);// 实际的相同删除操作函数, 必须保证在x->lock加锁状态下执行int __xfrm_state_delete(struct xfrm_state *x){int err = -ESRCH;// 如果状态已经是DEAD就不操作了if (x->km.state != XFRM_STATE_DEAD) {// 设置状态为DEADx->km.state = XFRM_STATE_DEAD;// xfrm_state_lock是全局的状态链表操作锁spin_lock(&xfrm_state_lock);// 从目的地址索引的链表中断开hlist_del(&x->bydst);// 从源地址索引的链表中断开hlist_del(&x->bysrc);// 从SPI索引的链表中断开if (x->id.spi)hlist_del(&x->byspi);// xfrm状态总数减一xfrm_state_num--;spin_unlock(&xfrm_state_lock);/* All xfrm_state objects are created by xfrm_state_alloc.* The xfrm_state_alloc call gives a reference, and that* is what we are dropping here.*/// 减少该状态引用计数__xfrm_state_put(x);err = 0;}return err;}EXPORT_SYMBOL(__xfrm_state_delete);4.3 删除全部状态删除全部状态函数为xfrm_state_flush(), 该函数被pfkey_flush函数调用.// 删除某种协议proto的所有状态void xfrm_state_flush(u8 proto){int i;spin_lock_bh(&xfrm_state_lock);// 循环所有HASH链表for (i = 0; i <= xfrm_state_hmask; i++) {struct hlist_node *entry;struct xfrm_state *x;restart:// 在按目的地址进行索引的链表中循环hlist_for_each_entry(x, entry, xfrm_state_bydst+i, bydst) {// 要满足两个条件:// 非正在被ipsec通道使用的状态; 协议类型匹配if (!xfrm_state_kern(x) &&xfrm_id_proto_match(x->id.proto, proto)) {// 先hold住状态,防止在解开xfrm_state_lock锁, 又没被进入xfrm_state_delete()前// 被意外删除了, 此处考虑得比较仔细xfrm_state_hold(x);// 先解开xfrm_state_lock, 在xfrm_state_delete()中要重新上锁spin_unlock_bh(&xfrm_state_lock);// 删除状态xfrm_state_delete(x);// 减少刚才的引用计数xfrm_state_put(x);// 重新加锁, 循环spin_lock_bh(&xfrm_state_lock);goto restart;}}}spin_unlock_bh(&xfrm_state_lock);wake_up(&km_waitq);}EXPORT_SYMBOL(xfrm_state_flush);4.4 状态增加或更新状态增加函数为xfrm_state_add(), 状态更新函数为xfrm_state_update(),这两个函数都被pfkey_add函数调用.// 添加xfrm状态int xfrm_state_add(struct xfrm_state *x){struct xfrm_state *x1;int family;int err;// 当协议为为ESP, AH, COMP以及ANY时为真, 其他为假int use_spi = xfrm_id_proto_match(x->id.proto, IPSEC_PROTO_ANY);family = x->props.family;spin_lock_bh(&xfrm_state_lock);// 根据xfrm的地址, SPI, 协议, 协议族等信息查找内核中是否已经存在相同的xfrm x1 = __xfrm_state_locate(x, use_spi, family);if (x1) {// 确实已经存在, 返回错误xfrm_state_put(x1);x1 = NULL;err = -EEXIST;goto out;}if (use_spi && x->km.seq) {// 如果序列号有效, 根据序列号查找内核中是否已经存在相同的xfrm x1 = __xfrm_find_acq_byseq(x->km.seq);// 找到, 但如果目的地址不符合的话, 仍试为没找到if (x1 && xfrm_addr_cmp(&x1->id.daddr, &x->id.daddr, family)) {xfrm_state_put(x1);x1 = NULL;}}// 如果没找到x1, 根据各种信息再查找xfrmif (use_spi && !x1)x1 = __find_acq_core(family, x->props.mode, x->props.reqid,x->id.proto,&x->id.daddr, &x->props.saddr, 0);// 如果x和现在内核中的xfrm匹配的话为x生成genid参数// 会用到一个静态单文件全局变量: xfrm_state_genid__xfrm_state_bump_genids(x);// 将新xfrm插入内核的各xfrm表, 这些表是以HASH表形式实现的, 分别根据// 源地址, 目的地址形成两个HASH表__xfrm_state_insert(x);err = 0;out:spin_unlock_bh(&xfrm_state_lock);// 如果按后来的条件找到x1, 删除之, 该状态不需要了if (x1) {// 将找到的x1从链表中删除,xfrm_state_delete(x1);// 释放x1xfrm_state_put(x1);}return err;}EXPORT_SYMBOL(xfrm_state_add);// 更新xfrm状态int xfrm_state_update(struct xfrm_state *x){struct xfrm_state *x1;int err;int use_spi = xfrm_id_proto_match(x->id.proto, IPSEC_PROTO_ANY);spin_lock_bh(&xfrm_state_lock);// 查找内核中相应的xfrm, 找不到的话出错x1 = __xfrm_state_locate(x, use_spi, x->props.family);err = -ESRCH;if (!x1)goto out;// 如果该xfrm正在被IPSEC通道使用, 返回错误if (xfrm_state_kern(x1)) {xfrm_state_put(x1);err = -EEXIST;goto out;}// 找到的x1本来就是在acquire状态, 直接将x插入系统xfrm表就行了if (x1->km.state == XFRM_STATE_ACQ) {__xfrm_state_insert(x);x = NULL;}err = 0;out:spin_unlock_bh(&xfrm_state_lock);if (err)return err;if (!x) {// 将找到的acquire状态的xfrm删除, 正确返回xfrm_state_delete(x1);xfrm_state_put(x1);return 0;}// 找到了x1, 状态也不是acquire, 即进行正常的更新x1中的数据为x的数据err = -EINVAL;spin_lock_bh(&x1->lock);if (likely(x1->km.state == XFRM_STATE_VALID)) {// 拷贝封装处理if (x->encap && x1->encap)memcpy(x1->encap, x->encap, sizeof(*x1->encap));// 拷贝care of的地址if (x->coaddr && x1->coaddr) {memcpy(x1->coaddr, x->coaddr, sizeof(*x1->coaddr));}// 没有SPI时拷贝选择子if (!use_spi && memcmp(&x1->sel, &x->sel, sizeof(x1->sel)))memcpy(&x1->sel, &x->sel, sizeof(x1->sel));// 拷贝生命期memcpy(&x1->lft, &x->lft, sizeof(x1->lft));x1->km.dying = 0;// 1秒钟的超时mod_timer(&x1->timer, jiffies + HZ);if (x1->e_time)xfrm_state_check_expire(x1);err = 0;}spin_unlock_bh(&x1->lock);xfrm_state_put(x1);return err;}EXPORT_SYMBOL(xfrm_state_update);4.5 状态插入状态插入函数为xfrm_state_insert(), 该函数被ipcomp_tunnel_attach()函数(net/ipv4/ipcomp.c)调用// xfrm_state_insert只是个包裹函数, 加xfrm_state_lock锁后调用__xfrm_state_bump_genids 和// __xfrm_state_insertvoid xfrm_state_insert(struct xfrm_state *x){spin_lock_bh(&xfrm_state_lock);__xfrm_state_bump_genids(x);__xfrm_state_insert(x);spin_unlock_bh(&xfrm_state_lock);}EXPORT_SYMBOL(xfrm_state_insert);/* xfrm_state_lock is held */// 碰撞检查, 看是否有多个连接状态, 要进行区别static void __xfrm_state_bump_genids(struct xfrm_state *xnew){unsigned short family = xnew->props.family;u32 reqid = xnew->props.reqid;struct xfrm_state *x;struct hlist_node *entry;unsigned int h;// 计算状态HASH值来找相关链表h = xfrm_dst_hash(&xnew->id.daddr, &xnew->props.saddr, reqid, family);hlist_for_each_entry(x, entry, xfrm_state_bydst+h, bydst) {// 如果已经在链表中的状态的协议族, 请求ID, 源地址, 目的地址都和新状态匹配if (x->props.family == family &&x->props.reqid == reqid &&!xfrm_addr_cmp(&x->id.daddr, &xnew->id.daddr, family) &&!xfrm_addr_cmp(&x->props.saddr, &xnew->props.saddr, family))// 将这些状态的genid参数设置为当前xfrm_state_genid(全局变量)x->genid = xfrm_state_genid;}}static void __xfrm_state_insert(struct xfrm_state *x){unsigned int h;// 将新状态的genid设置为当前xfrm_state_genid值加一,和其他碰撞的状态区分开x->genid = ++xfrm_state_genid;// 添加到按目的地址HASH的链表h = xfrm_dst_hash(&x->id.daddr, &x->props.saddr,x->props.reqid, x->props.family);hlist_add_head(&x->bydst, xfrm_state_bydst+h);// 添加到按源地址HASH的链表h = xfrm_src_hash(&x->id.daddr, &x->props.saddr, x->props.family);hlist_add_head(&x->bysrc, xfrm_state_bysrc+h);if (x->id.spi) {// 添加到按SPI进行HASH的链表h = xfrm_spi_hash(&x->id.daddr, x->id.spi, x->id.proto,x->props.family);hlist_add_head(&x->byspi, xfrm_state_byspi+h);}// 修改定时器, 超时仅1秒mod_timer(&x->timer, jiffies + HZ);// 如果设置了回放最大时间间隔, 超时改为该值if (x->replay_maxage)mod_timer(&x->rtimer, jiffies + x->replay_maxage);// 唤醒等待队列wake_up(&km_waitq);// 状态总数加1xfrm_state_num++;// HASH扩大检查, 检查是否需要扩展HASH表数量xfrm_hash_grow_check(x->bydst.next != NULL);}4.6 状态查找状态查找函数有好几个, 分别按不同条件来查找状态, 注意找到状态后, 都会增加状态的引用计数.4.6.1 xfrm_state_lookup// 只是__xfrm_state_lookup的包裹函数,是根据SPI进行HASH后查找struct xfrm_state *xfrm_state_lookup(xfrm_address_t *daddr, __be32 spi, u8 proto,unsigned short family){struct xfrm_state *x;spin_lock_bh(&xfrm_state_lock);x = __xfrm_state_lookup(daddr, spi, proto, family);spin_unlock_bh(&xfrm_state_lock);return x;}EXPORT_SYMBOL(xfrm_state_lookup);static struct xfrm_state *__xfrm_state_lookup(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family){// 根据SPI进行HASHunsigned int h = xfrm_spi_hash(daddr, spi, proto, family);struct xfrm_state *x;struct hlist_node *entry;// 循环相应的SPI链表hlist_for_each_entry(x, entry, xfrm_state_byspi+h, byspi) {// 比较协议族, SPI, 和协议是否相同if (x->props.family != family ||x->id.spi != spi ||x->id.proto != proto)continue;// 比较目的地址是否相同switch (family) {case AF_INET:if (x->id.daddr.a4 != daddr->a4)continue;break;case AF_INET6:if (!ipv6_addr_equal((struct in6_addr *)daddr,(struct in6_addr *)x->id.daddr.a6))continue;break;};// 找到, 增加状态引用计数, 返回xfrm_state_hold(x);return x;}return NULL;}4.6.2 按地址查找状态// 只是__xfrm_state_lookup_byaddr的包裹函数,是根据目的地址进行HASH后查找struct xfrm_state *xfrm_state_lookup_byaddr(xfrm_address_t *daddr, xfrm_address_t *saddr,u8 proto, unsigned short family){struct xfrm_state *x;spin_lock_bh(&xfrm_state_lock);x = __xfrm_state_lookup_byaddr(daddr, saddr, proto, family);spin_unlock_bh(&xfrm_state_lock);return x;}EXPORT_SYMBOL(xfrm_state_lookup_byaddr);static struct xfrm_state *__xfrm_state_lookup_byaddr(xfrm_address_t *daddr, xfrm_address_t *saddr, u8 proto, unsigned short family){// 根据目的地址计算HASH值unsigned int h = xfrm_src_hash(daddr, saddr, family);struct xfrm_state *x;struct hlist_node *entry;// 循环相应的源地址链表hlist_for_each_entry(x, entry, xfrm_state_bysrc+h, bysrc) {// 比较协议族和协议是否相同if (x->props.family != family ||x->id.proto != proto)continue;// 比较源地址和目的地址是否相同switch (family) {case AF_INET:if (x->id.daddr.a4 != daddr->a4 ||x->props.saddr.a4 != saddr->a4)continue;break;case AF_INET6:if (!ipv6_addr_equal((struct in6_addr *)daddr,(struct in6_addr *)x->id.daddr.a6) ||!ipv6_addr_equal((struct in6_addr *)saddr,(struct in6_addr *)x->props.saddr.a6))continue;break;};// 找到, 增加状态引用计数, 返回xfrm_state_hold(x);return x;}return NULL;}4.6.3 __xfrm_state_locate这个函数只是__xfrm_state_lookup和__xfrm_state_lookup_byaddr的组合函数static inline struct xfrm_state *__xfrm_state_locate(struct xfrm_state *x, int use_spi, int family){if (use_spi)return __xfrm_state_lookup(&x->id.daddr, x->id.spi,x->id.proto, family);elsereturn __xfrm_state_lookup_byaddr(&x->id.daddr,&x->props.saddr,x->id.proto, family);}4.6.4 查找ACQUIRE类型的状态ACQUIRE类型的SA的产生是内核发现数据需要进行保护, 但却没有找到相关的SA, 就向用户空间的IKE协商程序发送ACQUIRE请求, 并生成一个ACQUIRE类型的SA, 如果用户空间协议协商成功会生成合适的SA传内核, 内核就会替换此ACQUIRE的SA, 因此ACQUIRE不是真正可用的SA, 只是表示有此SA的需求, 等待用户空间程序协商结果。
sdio linux驱动流程图
SD卡的工作原理想了解SD卡的工作原理,首先需要了解的就是SD卡协议了,这个在网上可以轻松的下载到。
在了解协议后,就可以看看下面的一些开发思路了。
首先看下脱离操作系统如何在S3C2410上实现SD卡的读写。
过程可以分为3个大的步骤:初始化sd卡、写sd卡、读sd卡;下面的过程是我通过realview-MDK环境测试过的。
一、初始化sd卡二、写sd卡写sd卡可以分为3种方式:POLL、中断、DMA (1)POLL写三、读sd卡读sd卡也可分为3中方式:POLL、中断、DMA (1)POLL读SD卡linux驱动工作原理,说了下脱离操作系统如何在S3C2410上实现SD卡的读写。
了解了脱离操作系统的工作原理后,现在可以思考linux是如何管理管理SD卡的了。
Linux中SD驱动可以分为3层:块设备层(mmc_block.c ,mmc_sysfs.c,mmc_queue.c)、mmc协议层(mmc.c)、sd驱动层(s3c2410_sdi.c)。
下面从以下几个方面理解驱动:1、s3c2410_sdi.c代码初始化过程;2、SD卡块设备注册过程;3、request及数据传输的实现。
下面介绍的过程参考的代码是内核版本是2.6.8,其它版本过程类似。
一、s3c2410_sdi.c代码初始化过程二、SD卡块设备注册过程三、request及数据传输的实现SD卡调试关键点:1. 上电时要延时足够长的时间给SD卡一个准备过程,在我的程序里是5秒,根据不同的卡设置不同的延时时间。
SD 卡初始化第一步在发送CMD命令之前,在片选有效的情况下首先要发送至少74个时钟,否则将有可能出现SD卡不能初始化的问题。
2. SD卡发送复位命令CMD0后,要发送版本查询命令CMD8,返回状态一般分两种,若返回0x01表示此SD卡接受CMD8,也就是说此SD卡支持版本2;若返回0x05则表示此SD卡支持版本1。
因为不同版本的SD卡操作要求有不一样的地方,所以务必查询SD卡的版本号,否则也会出现SD卡无法正常工作的问题。
嵌入式Linux下基于SPI总线的网络设备驱动设计与实现
第 2 卷 第 2 期 9 3
VO . 9 12 No. 23
计算 机 工程 与 设 计
Co u e n ie rn n sg mp trE g n e g a dDe in i
20 年 1 月 08 2
De .2 0 c 0 8
嵌入式 Lnx i 下基于 S I u P总线的网络设备驱动设计与实现
张 晓雷 陈相 宁 , 郭 剑 ,
(.南京 大 学 网络 安 全通信 实验 室 ,江 苏 南京 2 09 ; 1 10 3 2 .瑞博 强 芯 ( 津)科技 有 限公 司 南京 研发 中心 ,江 苏 南京 20 9 ) 天 10 3
摘 要 : 于 S I 基 P 总线 的 网络 驱动设 备是 一种 新型 的 网络 设备 , 其驱动 程序设 计 尚未经 过 系统分 析 。在 分析嵌 入 式 Ln x的 iu SI P 总线特 点 的基 础上 , ¥ C 4 0为 MC 以 E C 86 为 以太 网控 制 芯 片作 为设计 实例 , 以 3 21 U、 N 2 J0 首次详 细分析 介绍 了基 于 S I P 总
其 它 事 务 。 此 , 发 基 于 S I 线 的 网络 驱 动 程 序 除 了要 涉 因 开 P总 及 Ln x i 内核 网络 驱 动 程 序 的知 识 外 , 要 对 这 类 驱 动 程 序 体 u 还 系 结 构 有 深 入 了解 。 本 文 从 工 业 实 现 的角 度 详 细 分 析 了在 嵌 入 式 Ln x 基 于 i 下 u S I 线 的 网 络 驱动 体 系 结 构 , P总 以业 界 最 新 的E 2J0以太 网 NC 86 控 制 芯 片 为 设计 实例 进 一 步 分 析 和 实现 了 以 ¥ C 40为 MC 3 21 U 的嵌 入 式 Lnx . i 2 u 4内核 下 的 以太 网驱 动 程 序 , 后 给 出总 结 。 最
标准linux系统启动流程
标准linux系统启动流程
标准Linux系统的启动流程通常分为以下几个阶段,BIOS/UEFI 启动、引导加载程序、内核加载、系统初始化和用户空间启动。
首先是BIOS/UEFI启动阶段。
当计算机开机时,计算机会首先执行基本输入/输出系统(BIOS)或统一可扩展固件接口(UEFI)固件程序。
在这个阶段,计算机进行自检(POST)以及硬件初始化,然后寻找启动设备。
接下来是引导加载程序阶段。
在BIOS/UEFI确定了启动设备之后,它会加载引导加载程序(如GRUB或LILO)。
引导加载程序的作用是加载操作系统内核并将控制权转交给内核。
然后是内核加载阶段。
引导加载程序会加载Linux内核,内核是操作系统的核心部分,负责管理系统的各种资源和提供各种系统服务。
一旦内核加载完成,它开始初始化系统的各种硬件设备,并创建第一个用户空间进程init。
接着是系统初始化阶段。
在内核初始化完毕后,init进程接管控制权,开始进行系统初始化。
这个阶段包括挂载文件系统、启动
系统服务和设置系统参数等操作。
最后是用户空间启动阶段。
一旦系统初始化完成,init进程会启动其他系统服务和用户空间进程,最终将控制权交给登录管理器(如GDM或KDM),用户就可以登录系统了。
总的来说,标准Linux系统的启动流程经历了硬件初始化、引导加载程序、内核加载、系统初始化和用户空间启动等多个阶段,最终完成了整个系统的启动过程。
Linux内核mmc_sdio_sd卡子系统驱动架构
MMC/SD Memory/SDIO卡mmc子系统驱动架构工作报告2014-3-11深圳目标:分析整理(MMC/SD Memory/SDIO卡)mmc子系统驱动架构;本文要点:1、mmc子系统后端初始化相关的数据结构;2、mmc子系统初始化驱动架构;3、mmc请求处理;4、基本的sd协议规范;SDIO/SD Memory/MMC卡的区别:●SDIO card is to provide high-speed data I/O with low power consumption for mobileelectronic devices.●The SDIO (Secure Digital I/O) card is based on and compatible with the SD memory card. Thiscompatibility includes mechanical, electrical, power, signaling and software.●SD Memory Card is a memory card that is specifically designed to meet the security,capacity, performance, and environment requirements inherent in newly emerging audio and video consumer electronic devices.The SD Memory Card will include a content protection mechanism.The SD Memory Card security system uses mutual authentication and a "new cipher algorithm" to protect against illegal usage of the card content.A Non-secure access to the user's own content is also available.●The MMC/eMMC is an universal low cost data storage and communication media. It isdesigned to cover a wide area of applications as smart phones, cameras, organizers, PDAs, digital recorders, MP3 players, pagers, electronic toys, etc. Targeted features are high mobility and high performance at a low cost price.These features include low power consumption and high data throughput at the memory card interface.●SD Memory卡是从MMC卡发展过来的,它更注重内容保护,SD Memory卡在外形上同MMC卡保持一致,且兼容MMC卡兼容规范.所以就发展轨道来看,是MMC卡==>SD Memory卡==>SDIO卡,依次兼容,保持一致硬件图:(以SD 总线为例)注意:在默认速度下,SD 总线支持单master(application)与多slaves(cards)连接;而在高速和UHS-I(极高速)下,SD 总线只支持单master 与单slave 连接;(经分析:当前linux 版本代码针对的是单master 与单slave 连接模式)D0-D3, CMDVSSVDD CLKD0-D3, CMDCLK VDD VSSCLK VDD VSS D0-D3(A) CMD(A)D0-D3(B) CMD(B)SD MemoryCard(B)SD Memory Card(A)Host数据结构1.初始化:mmc_card {} .dev ... ... .cid .csd *host ... ...mmc_host {} *card.class_dev *ops .disable .detect ... ... .ios *bus_opsmmc_csd{} .max_dtr .cmdclass .capacity ... ...mmc_cid{} .manfid ... ...mmc_host_ops{} *request *set_ios *get_ro ... ...sdhci_host {} *mmc ... ...(前端)gendisk {} *driverfs_dev .major .first_minor ... ...mmc_bus_type {} *match *probe ... ...device{} *bus ... ...mmc_bus_matchmmc_bus_probe device_driver{} *bus ... ...mmc_driver {} .drv .probe... ...mmc_blk_data {} .queue *disk ... ... mmc_queue{}*card ... ...(前端)mmc_ios{} clock vdd bus_mode ... ...delayed_work{} ... ...mmc_sd_ops{} mmc_ops{} mmc_sdio_ops{}其中:*ops :表示对控制器的操作,由前端控制器驱动中设定;*bus_ops :表示对总线上卡的操作,不同类型总线对应的卡有不同的操作,其中mmc_sd_ops{}, 在mmc_sd_attach_bus_ops()函数中设置; mmc_sdio_ops{} 在mmc_attach_bus()函数中设置; mmc_ops{} 在mmc_attach_bus_ops 函数中设置;2.请求处理:mmc_command {} *data *mrq opcode arg resp[4] flags retries ... ...mmc_blk_data {}queue ... ...mmc_queue {} *data*queue *thread *issue_fn *sg *reqmmc_data {} *mrq *stop *sg blksz blocks flags ... ...request_queue {} queue_head *queuedata request {}queuelistmmc_blk_request {} mrq data cmd stopmmc_request {} *cmd *data *stop *done ... ...task_struct{} ... ...scatterlist{} ... ...(块层构建的请求)request {} queuelist 注1(当前取出的请求)(mmc 构建的请求)(mmc 构建的命令)注1:不论是块层下发的读写请求,还是源自mmc 核心层要发出的命令,都使用mmc_request {}进行发送 (原因:保存了所需资源的指针); 注2:有两种命令来源,一种是mmc 核心需要发的命令,一种是块层下发的请求也需要由mmc 为其生成命令。
platfrom设备驱动框架
platfrom设备驱动框架前⾯编写的设备驱动都⾮常的简单,都是对IO进⾏最简单的读写操作。
像I2C、SPI、LCD 等这些复杂外设的驱动就不能这么去写了,Linux 系统要考虑到驱动的可重⽤性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞⽣了我们将来最常打交道的platform 设备驱动,也叫做平台设备驱动。
1、Linux驱动的分离与分层1.1 驱动的分隔与分离对于 Linux 这样⼀个成熟、庞⼤、复杂的操作系统,代码的重⽤性⾮常重要,否则的话就会在 Linux 内核中存在⼤量⽆意义的重复代码。
尤其是驱动程序,因为驱动程序占⽤了 Linux内核代码量的⼤头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么⽤不了多久Linux 内核的⽂件数量就庞⼤到⽆法接受的地步。
假如现在有三个平台 A、B 和 C,这三个平台(这⾥的平台说的是 SOC)上都有 MPU6050 这个 I2C 接⼝的六轴传感器,按照我们写裸机 I2C 驱动的时候的思路,每个平台都有⼀个MPU6050的驱动,因此编写出来的最简单的驱动框架如图所⽰:每种平台下都有⼀个主机驱动和设备驱动,主机驱动肯定是必须要的,毕竟不同的平台其 I2C 控制器不同。
但是右侧的设备驱动就没必要每个平台都写⼀个,因为不管对于那个 SOC 来说,MPU6050 都是⼀样,通过 I2C 接⼝读写数据就⾏了,只需要⼀个 MPU6050 的驱动程序即可。
如果再来⼏个 I2C 设备,⽐如 AT24C02、FT5206(电容触摸屏)等,如果按照图中的写法,那么设备端的驱动将会重复的编写好⼏次。
显然在 Linux 驱动程序中这种写法是不推荐的,最好的做法就是每个平台的 I2C 控制器都提供⼀个统⼀的接⼝(也叫做主机驱动),每个设备的话也只提供⼀个驱动程序(设备驱动),每个设备通过统⼀的 I2C接⼝驱动来访问,这样就可以⼤⼤简化驱动⽂件,⽐如 54.1.1 中三种平台下的 MPU6050 驱动框架就可以简化为图所⽰:这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,⽐如 I2C、SPI 等等都会采⽤驱动分隔的⽅式来简化驱动的开发。
linux设备驱动spi详解5-应用到驱动的完整流程
linux设备驱动spi详解5-应⽤到驱动的完整流程所有的应⽤程序使⽤dev/⽬录下创建的设备,这些字符设备的操作函数集在⽂件spidev.c中实现。
1static const struct file_operations spidev_fops = {2 .owner = THIS_MODULE,3/* REVISIT switch to aio primitives, so that userspace4 * gets more complete API coverage. It'll simplify things5 * too, except for the locking.6*/7 .write = spidev_write,8 .read = spidev_read,9 .unlocked_ioctl = spidev_ioctl,10 .open = spidev_open,11 .release = spidev_release,12 };1 spidev_ioctl函数以上是所有应⽤程序所能够做的所有操作,由此开始追踪spi 驱动程序的完整执⾏流程其中,最重要的就是ioctl, 从这⾥开始先重点剖析ioctl函数1 spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)2 {3int err = 0;4int retval = 0;5struct spidev_data *spidev;6struct spi_device *spi;7 u32 tmp;8 unsigned n_ioc;9struct spi_ioc_transfer *ioc;10//ioctl cmd 检查11/* Check type and command number */12if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)13return -ENOTTY;1415/* Check access direction once here; don't repeat below.16 * IOC_DIR is from the user perspective, while access_ok is17 * from the kernel perspective; so they look reversed.18*/19if (_IOC_DIR(cmd) & _IOC_READ)20 err = !access_ok(VERIFY_WRITE,21 (void __user *)arg, _IOC_SIZE(cmd));22if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)23 err = !access_ok(VERIFY_READ,24 (void __user *)arg, _IOC_SIZE(cmd));25if (err)26return -EFAULT;2728/* guard against device removal before, or while,29 * we issue this ioctl.30*/31//通过以下⽅式获取spi_device-----spi(是之后操作的基础)32 spidev = filp->private_data;33 spin_lock_irq(&spidev->spi_lock);34 spi = spi_dev_get(spidev->spi);35 spin_unlock_irq(&spidev->spi_lock);3637if (spi == NULL)38return -ESHUTDOWN;3940/* use the buffer lock here for triple duty:41 * - prevent I/O (from us) so calling spi_setup() is safe;42 * - prevent concurrent SPI_IOC_WR_* from morphing43 * data fields while SPI_IOC_RD_* reads them;44 * - SPI_IOC_MESSAGE needs the buffer locked "normally".45*/46 mutex_lock(&spidev->buf_lock);47//以上是进⾏check,检查命令有效性,以及进⾏初始化数据,这⾥不在多做说明48switch (cmd) {49/* read requests */50case SPI_IOC_RD_MODE://获取模式信息,将信息发送给⽤户51 retval = __put_user(spi->mode & SPI_MODE_MASK,52 (__u8 __user *)arg);53break;54case SPI_IOC_RD_LSB_FIRST://获取spi最低有效位55 retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,56 (__u8 __user *)arg);57break;58case SPI_IOC_RD_BITS_PER_WORD:59 retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);61case SPI_IOC_RD_MAX_SPEED_HZ:62 retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);63break;6465/* write requests */66case SPI_IOC_WR_MODE://设置数据传输模式,这⾥只是把设置的数据保存在spi 中,但并没有对spi device做任何操作,对spi device的操作⼀并在最后进⾏ 67 retval = __get_user(tmp, (u8 __user *)arg);68if (retval == 0) {69 u8 save = spi->mode;7071if (tmp & ~SPI_MODE_MASK) {72 retval = -EINVAL;73break;74 }7576 tmp |= spi->mode & ~SPI_MODE_MASK;77 spi->mode = (u8)tmp;78 retval = spi_setup(spi);79if (retval < 0)80 spi->mode = save;81else82 dev_dbg(&spi->dev, "spi mode %02xn", tmp);83 }84break;85case SPI_IOC_WR_LSB_FIRST://设置设置spi写最低有效位,同上86 retval = __get_user(tmp, (__u8 __user *)arg);87if (retval == 0) {88 u8 save = spi->mode;8990if (tmp)91 spi->mode |= SPI_LSB_FIRST;92else93 spi->mode &= ~SPI_LSB_FIRST;94 retval = spi_setup(spi);95if (retval < 0)96 spi->mode = save;97else98 dev_dbg(&spi->dev, "%csb firstn",99 tmp ? 'l' : 'm');100 }101break;102case SPI_IOC_WR_BITS_PER_WORD://设置spi写每个字含多个个位,同上103 retval = __get_user(tmp, (__u8 __user *)arg);104if (retval == 0) {105 u8 save = spi->bits_per_word;106107 spi->bits_per_word = tmp;108 retval = spi_setup(spi);109if (retval < 0)110 spi->bits_per_word = save;111else112 dev_dbg(&spi->dev, "%d bits per wordn", tmp);113 }114break;115case SPI_IOC_WR_MAX_SPEED_HZ://设置spi写最⼤速率,同上116 retval = __get_user(tmp, (__u32 __user *)arg);117if (retval == 0) {118 u32 save = spi->max_speed_hz;119120 spi->max_speed_hz = tmp;121 retval = spi_setup(spi);122if (retval < 0)123 spi->max_speed_hz = save;124else125 dev_dbg(&spi->dev, "%d Hz (max)n", tmp);126 }127break;128129default:130/* segmented and/or full-duplex I/O request */131if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))//查看是否为数据write命令132 || _IOC_DIR(cmd) != _IOC_WRITE) {133 retval = -ENOTTY;134break;135 }136//pay more time on understanding below method137 tmp = _IOC_SIZE(cmd);//从命令参数中解析出⽤户数据⼤⼩138if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {//数据⼤⼩必须是struct spi_ioc_transfer的整数倍139 retval = -EINVAL;140break;141 }142 n_ioc = tmp / sizeof(struct spi_ioc_transfer);//将要传输的数据分成n个传输数据段143if (n_ioc == 0)145146/* copy into scratch area */147 ioc = kmalloc(tmp, GFP_KERNEL);//获取n个数据段的数据管理结构体的内存空间148if (!ioc) {149 retval = -ENOMEM;150break;151 }152if (__copy_from_user(ioc, (void __user *)arg, tmp)) {//从⽤户空间获取数据管理结构体的初始化值153 kfree(ioc);154 retval = -EFAULT;155break;156 }157158/* translate to spi_message, execute */159 retval = spidev_message(spidev, ioc, n_ioc);//数据传输,这是整个流程中的核⼼160 kfree(ioc);161break;162 }163164 mutex_unlock(&spidev->buf_lock);165 spi_dev_put(spi);166return retval;167 }通过调⽤函数spi->master->setup()来设置SPI模式。
linux内核SPI总线驱动分析
{
.modalias = "m25p10a",
.mode = SPI_MODE_0,
.max_speed_hz = 1000000,
.bus_num = 0,
.chip_select = 0,
.controller_data = &smdk_spi0_csi[SMDK_MMCSPI_CS], i_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
struct spi_driver {
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
.controller_data = &smdk_spi0_csi[0],
通常来说spi_device对应着SPI总线上某个特定的slave。并且spi_device封装了一个spi_master结构体。spi_device结构体包含了私有的特定的slave设备特性,包括它最大的频率,片选那个,输入输出模式等等
3 OMAP3630 SPI控制器
OMAP3630上SPI是一个主/从的同步串行总线,这边有4个独立的SPI模块(SPI1,SPI2,SPI3,SPI4),各个模块之间的区别在于SPI1支持多达4个SPI设备,SPI2和SPI3支持2个SPI设备,而SPI4只支持1个SPI设备。
SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要4根线,事实上3根也可以。也是所有基于SPI的设备共有的,它们是SDI(数据输入),SDO(数据输出),SCLK(时钟),CS(片选)。
linux mtd设备的创建流程
linux mtd设备的创建流程创建Linux MTD 设备的流程涉及以下几个主要步骤:1. 硬件平台初始化2. 设备驱动程序开发3. MTD 设备的注册和系统挂载4. MTD 分区的创建和管理5. 文件系统的格式化和挂载下面将一步一步详细回答每个主题。
1. 硬件平台初始化:MTD(Memory Technology Device)是一种用于与特定硬件闪存设备进行交互的通用接口。
要创建MTD 设备,首先需要对硬件平台进行初始化。
此过程可能涉及的操作包括配置系统引导加载程序(如U-Boot)、启用芯片选择引脚(Chip Select)、设置时钟和引脚(GPIO)控制器等。
2. 设备驱动程序开发:MTD 驱动程序是用于将Linux 内核与硬件平台之间的通信接口。
在开发MTD 驱动程序时,首先需要确定具体的硬件设备类型以及其芯片驱动程序。
MTD 驱动程序通常使用SPI(Serial Peripheral Interface)或NAND(Not AND)总线进行通信。
针对特定的硬件设备类型,可以使用MTD API(应用程序编程接口)提供的函数来编写设备驱动程序。
3. MTD 设备的注册和系统挂载:驱动程序开发完成后,需要将MTD 设备注册到Linux 子系统中的MTD 子系统。
这涉及使用`mtd_device_register()` 函数来注册设备,并为每个设备分配设备编号。
然后,需要使用`mtd_add_partition()` 函数将设备的不同分区添加到MTD 子系统中,以便在系统启动时进行挂载。
4. MTD 分区的创建和管理:MTD 分区是将硬件闪存设备划分为逻辑部分的过程。
可以使用工具(如MTD Utils)或手动编写分区信息表(`mtdparts`)来创建MTD 分区。
分区信息表定义了每个分区的起始地址、大小、名称和权限等。
在系统启动期间,内核将使用分区信息表来将MTD 设备的不同区域映射到合适的逻辑分区。
linux SPI 驱动框架源码分析
SPI协议是一种同步的串行数据连接标准,由摩托罗拉公司命名,可工作于全双工模式。
相关通讯设备可工作于m/s模式。
主设备发起数据帧,允许多个从设备的存在。
每个从设备有独立的片选信号,SPI一般来说是四线串行总线结构。
接口:SCLK——Serial Clock(output from master)时钟(主设备发出)MOSI/SIMO——Master Output, Slave Input(output from master)数据信号线mosi(主设备发出)MISO/SOMI——Master Input,Slave Outpu(output from slave)数据信号线(从设备) SS——Slave Select(active low;output from master)片选信号下面来看一下Linux中的SPI驱动。
在Linux设备驱动框架的设计中,有一个重要的主机,外设驱动框架分离的思想,如下图。
外设a,b,c的驱动与主机控制器A,B,C的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用的API进行数据的传输,主机和外设之间可以进行任意的组合。
如果我们不进行如图的主机和外设分离,外设a,b,c和主机A,B,C进行组合的时候,需要9种不同的驱动。
设想一共有个主机控制器,n个外设,分离的结构是需要m+n个驱动,不分离则需要m*n个驱动。
下面介绍spi子系统的数据结构:在Linux中,使用spi_master结构来描述一个SPI主机控制器的驱动。
view plain分配,注册和注销的SPI主机的API由SPI核心提供:view plain在Linux中用spi_driver来描述一个SPI外设驱动。
view plain可以看出,spi_driver结构体和platform_driver结构体有极大的相似性,都有probe(),remove(),suspend(),resume()这样的接口。
spi_driver
linux spi驱动分析整理1、SPI总线:SPI(同步外设接口)是由摩托罗拉公司开发的全双工同步串行总线,其接口由MISO(串行数据输入),MOSI(串行数据输出),SCK(串行移位时钟),SS/CS(从使能信号)四种信号构成(当然了,现在芯片技术日新月异,SPI 模块的结构也在变化中,象OMAP 系列中的SPI 模块还支持 5 线的一种模式),SS /CS决定了唯一的与主设备通信的从设备,主设备通过产生移位时钟来发起通讯。
通讯时,数据由MOSI 输出,MISO 输入,数据在时钟的上升或下降沿由MOSI 输出,在紧接着的下降或上升沿由MISO 读入,这样经过8/16 次时钟的改变,完成8/16 位数据的传输。
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。
如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。
如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。
2、LINUX驱动的分层与分离:在面向对象的程序设计中,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类中的函数。
Linux 内核中频繁使用到面向对象的设计思想。
在设备驱动方面,往往为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。
而且具体的设备不想使用核心层的函数,它可以重载之。
这就是我们所说的在驱动设计中的分层思想。
此外,在驱动的设计中,我们还会使用分离的思想。
如果一个设备的驱动和host 的驱动休戚相关,那么,这就意味着这个普通的设备如果用在不同的host 上,会采用n 个版本的驱动。
如果产品单一,也许感觉不到不使用分离思想来设计驱动的危害,但是我们想一下,这个世上被人们称道的多是什么?精品,艺术品!精品如何打造?注重细节,不只考虑单一需求!大家开发个东西不容易,怎么能随随便便就让它茫然众码矣呢,所以,何时何地,我们都要以打造精品的思想来要求自己,让自己的劳动力不浪费。
Linux内核SPI子系统驱动架构以及其数据传输
device_driver{} *name *p *bus ……
spi设备模型总图
spi_master_class ……
device_private{} knode_bus ……
driver_private{} knode_bus ……
注2: 显然板卡注册需先于控制器注册,这里稍稍提醒;
2
spi_master{} dev ……
spi_device{} *master dev modalias ……
spi_diver{} .driver *id_table *probe ……
mcu_probe()
device{} *parent *class ……
最终生成
注1: spi_register_master()执行到完成spi_master{}注册后,会调用scan_boardinfo(),查询匹配board_list链表上的外设数据结构;当确认某外 设使用当前被注册的控制器(即spi_master{}.bus_num==spi_board_info{}.bus_num成立)后,将调用 spi_new_device() ,构造该外设对 应的spi_device{}并注册;
irq;
u32
max_speed_hz;
u16
bus_num;
u16
chip_select;
u8
mode;
};
成员变量解说:
modalias spi_new_device()中将初始化 spi_device{}.modalias, 用来和 spi_driver{}.driver->name 等进行匹配;
linuxSDSDIOMCC理解和延伸分析(自我总结)
linuxSDSDIOMCC理解和延伸分析(⾃我总结)引⾃:LINUX SDIO的驱动架构⼀、硬件知识MMC:muti media card多媒体卡。
4位或者8位带宽,52MHZ频率,从⽽⽀持50MB/s的传输速度。
SD:secure digital memery card 。
在mmc的基础上发展⽽来,强调数据保全,可设置所存储数据权限,从⽽防⽌他⼈复制。
SDIO:在sd的基础上,定义了⼀种外设接⼝,增加低速标准。
⼆、内核MMC、SD驱动框架driver/mmc⽬录下有三个⼦⽬录:card 、core、host,分别表⽰区块层、核⼼层、主机控制层。
区块层:向⽂件系统、⽤户空间⽂件操作的接⼝,主要⽂件是card⽬录下的block.c,queue.c向它提供⼏个函数操作队列。
区块层的调⽤core⽬录下的core.c、sysfs.c提供的接⼝来识别存储卡的分区、读写存储卡等功能。
核⼼层:核⼼层代码在core⽬录下,它封装了mmc/sd命令,实现mmc/sd协议,它调⽤主机控制器层的接⼝完成存储卡的识别、设置、读写等。
core.c⽂件由sd.c和mmc.c两个⽂件来⽀撑,core.c把mmc和sd卡的共性抽象出来,它们的差别由mmc.c、mmc_ops.c和sd.c、sd_ops.c区分。
sysfs.c是mmc、sd卡驱动程序的sysfs⽂件系统的实现。
主机控制层:这层是相关的,它接受并处理核⼼层发送的mmc、sd命令,⾥⾯针对各款CPU提供⼀个⽂件。
⾸先进⾏⼀些底层设置,⽐如设置sdio控制器使⽤到的gpio管脚,使能控制器,注册中断处理函数,然后向核⼼层添加增加⼀个主机。
这样,核⼼层就可以使⽤控制器层提供的接⼝来识别和使⽤具体的sdio设备了。
三、重要的和注册流程sdhci_host->mmc_host来描述主机控制器。
作为⼀般规则,在模块的probe探测函数中(host/s3cmci.c的xxx_probe函数),调⽤mmc_alloc_host分配⼀个mmc_host,初始化后,调⽤mmc_add_host将主机控制器的描述结构添加到内核,从⽽核⼼层可以使⽤主机控制器来控制和读写sdio设备。
regmap的用法
regmap是 Linux 内核中的一个机制,用于将硬件寄存器映射到内核地址空间,以便在内核中访问这些硬件寄存器。
regmap提供了一种抽象层,使得驱动开发者能够以一种统一的方式访问寄存器,而无需关心底层的物理地址映射等细节。
以下是regmap的基本用法:
1. 初始化regmap:
regmap的初始化通常在驱动的probe函数中进行。
在设备树中配置的信息,例如
I2C 地址、SPI 设备、寄存器位宽等,都会在初始化时被传递给regmap。
2. 使用regmap读写寄存器:
regmap提供了一系列函数,用于读写寄存器的值。
这些函数包括regmap_read、regmap_write、regmap_bulk_read、regmap_bulk_write等。
以下是一个简单的例子:
3. 使用regmap进行批量读写:
有时,你可能需要一次性读写多个寄存器,regmap提供了相应的函数支持。
例如,regmap_bulk_read和regmap_bulk_write。
4. 处理器架构和总线的适配:
regmap可以根据处理器架构和总线类型进行适配。
上述例子中使用的是 I2C 总线,如果是 SPI 或其他总线,相应的初始化函数和操作也会有所不同。
以上是regmap的基本用法。
在实际驱动开发中,你可能需要参考设备的文档以及Linux 内核源代码中的相关驱动,以了解如何适配和使用regmap。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
#define SPI_CPOL 0x02
/* clock polarity */时钟极性
#define SPI_MODE_0 (0|0)
/* (original MicroWire) */四种传输模式
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
spi_transfer{} *tx_buf ... ...
transfers_list
注 1:通过 spi_register_board_ino(struct spi_board_info const *info, unsigned n)将板卡上的含 n 个 spi_board_info 结构的 数组复制给 boardinfo 的 board_info,然后将 boardinfo 添加到 board_list 链表 注 2:在注册完 spi master 的时候,会调用 scan_boardinfo()函数,该函数会扫描先前已经注册在 board_list 链表上的 board_info,然后再根据 spi_board_info->bus_num 是否与 master->bus_num 一致,若一致就就调 spi_new_device() 函数
图示如下:
3
spi 重要的数据结构
struct spi_board_info {//该结构主要用来匹配 spi master 和初始化 spi_device
char
modalias[SPI_NAME_SIZE];
const void *platform_data;
void
*controller_data;
int
spi_alloc_device(): dev.bus = spi_bus_type
spi_match_device(struct device *dev, struct device_driver *drv) 函数 根据 spi 设备以及驱动的名称或驱动所支 持的设备链表 id_table 进行适配,适 配原则就是 spi_device{}.modalias 字 段 是 否 和 spi_driver{}.driver->name 或 spi_driver{}.id_table->name 相同
irq;
u32
max_speed_hz;
u16
bus_num;
u16
chip_select;
u8
mode;
};
成员变量解说: modalias 将初始化 spi_device{}.modalias(三星的 spi 设备使用了“spidev”),用来和 spi_driver{}.driver->name 匹配 platform_data 将初始化 spi_device{}.dev.platform_data,存储驱动的特定数据 controller_data 将初始化 spi_device{}.controller_data,有些控制器需要有关硬件设置的提示,如 DMA irq 将初始化 spi_device{}.irq,由硬件原理图确定 max_speed_hz,最大的传输速率,取决于 spi 设备芯片的 datasheet 以及 spi master,如果 spi 设备最大速率大于 spi master 的最大
spi_register_driver(): drv.bus = spi_bus_type
spi_drv_prore() xxx_spi_prore()
当驱动和设备 match 成功后就会 调用 spi 驱动的 spi_drv_prore(), 该函数最终调用的是先前以及注 册的驱动的特定 Xxx_spi_prore(), 完成 spi 特定设备的注册。
SPI 主模块和与之通信的外设备时钟相位和极性应该一致。SPI 主模块和与之通 信的外设备时钟相位和极性应该一致。个人理解这句话有 2 层意思:其一,主设备 SPI 时钟和极性的配置应该由外设来决定;其二,二者的配置应该保持一致,即主 设备的 SDO 同从设备的 SDO 配置一致,主设备的 SDI 同从设备的 SDI 配置一 致。因为主从设备是在 SCLK 的控制下,同时发送和接收数据,并通过 2 个双向 移位寄存器来交换数据。
spi 的四种传输模式 LSB(Least Significant Bit),意为最低有效位;MSB(Most Significant Bit),意为最高有效位
5
struct boardinfo {
struct list_head list;
unsigned
n_board_info;
struct spi_board_info board_info[0];
#define SPI_READY 0x80
/* slave pulls low to pause * /
4
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟 极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
如果 CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同 步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传 输协议之一进行数据传输。如果 CPHA=0,在串行同步时钟的第一个跳变沿(上 升或下降)数据被采样;如果 CPHA=1,在串行同步时钟的第二个跳变沿(上升 或下降)数据被采样。
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04
/* chipselect active high? */片选电位为高
#define SPI_LSB_FIRST 0x08
/* per-word bits-on-wire */先输出低比特位
SPI 子系统驱动架构以及其数据传输
2013 年 1 月 24 日
目标:
分析整理 SPI 子系统初始化驱动架构;
本文要点:
1、spi 重要数据结构及关系图; 2、spi 子系统初始化驱动架构; 3、spi 数据的传输;
硬件框图:
Slave1
Slave2
……
SPI Master1
SCLK MOSI MISO SS1 SS2
.name[MAX]
... ...
SPI 设备模型总图
device{}
.name ... ... .bus = “spi_bus_type” ... ...
spi_match_device()
device_driver{}
.name ... ... .bus = “spi_bus_type” ... ...
spi_board_info 与 boardinfo 的关系: spi_board_info:每个 spi_board_info 代表一个设备,spi_board_info 以数组的形式组织 boardinfo : boardinfo 会 将 spi_board_info[] 的 内 容 拷 贝 过 来 填 充 其 board_info[] 结 构 , 相 关 函 数 为 spi_register_board_ino(struct spi_board_info const *info, unsigned n),其中 n 代表有多少个 spi_board_info 这 样的数组元素。系统里可能有多个 board_info,所有的 boardinfo 都会连接到一个全局 SPI 设备链表。
platform_data 和 controller_data 区别:platform_data 存储驱动相关的内容,controller_data 则是存储的与 spi 控制器相关的内容
spi_board_info{}.mode 定义
#define SPI_CPHA 0x01
/* clock phase */时钟相位
… … (片选信号可以有多个)
Slave1
Slave2
……
SPI Master2
SCLK
MOSI MISO SS1 S图
spi_board_info{} char modalias[MAX] *platform_data *controller_data irq bus_num chip_select ... ...
速率,max_speed_hz 等于 master 的最大速率,如果 spi 设备最大速率小于 spi master 的最小速率,那么 max_speed_hz 等于 master 的最小速率
mode 模式,包括属于哪种传输模式,是否是三线模式,片选信号激活时候电平高低等,spi_master{}.mode_bits 会和他适配。 bus_num 用于和 spi _master{}.bus_num 适配,硬件上该设备会连接到系统的某个 spi master 上 chip_select 将初始化 spi_device{}.chip_select,表示该 spi 设备在硬件上是连接到 spi 控制器的第几个片选脚。起始为 0,不能大 过 spi_master{}.num_chipselect(由硬件决定,即 spi 控制器的 SS 片选管脚的个数)
#define SPI_3WIRE 0x10
/* SI/SO signals shared */输入输出共享接口,此时只能够半双工
#define SPI_LOOP 0x20
/* loopback mode */回写/回显模式
#define SPI_NO_CS 0x40
/* 1 dev/bus, no chipselect */只有单个从设备
};
成员变量解说: list 链表,连接到一个名叫 board_list 的链表 n_board_info 有几个 spi_board_info 结构。spi_register_board_ino(struct spi_board_info const *info, unsigned n)