首页
chatGPT
关于
友链
其它
统计
更多
壁纸
留言
Search
1
cgroup--(4)cgroup v1和cgroup v2的详细介绍
6,415 阅读
2
修改Linux Kernel defconfig的标准方法
6,382 阅读
3
Android系统之VINTF(1)manifests&compatibility matrices
5,979 阅读
4
使用git生成patch和应用patch
3,454 阅读
5
c语言的__attribute__
3,170 阅读
默认分类
文章收集
学习总结
算法
环境配置
知识点
入门系列
vim
shell
Git
Make
Android
Linux
Linux命令
内存管理
Linux驱动
Language
C++
C
工具
软件工具
Bug
COMPANY
登录
Search
标签搜索
shell
Linux
c
uboot
Vim
vintf
Linux驱动
Android
device_tree
git
DEBUG
arm64
链表
数据结构
IDR
内核
ELF
gcc
ARM
网址
adtxl
累计撰写
367
篇文章
累计收到
14
条评论
首页
栏目
默认分类
文章收集
学习总结
算法
环境配置
知识点
入门系列
vim
shell
Git
Make
Android
Linux
Linux命令
内存管理
Linux驱动
Language
C++
C
工具
软件工具
Bug
COMPANY
页面
chatGPT
关于
友链
其它
统计
壁纸
留言
搜索到
367
篇与
的结果
2022-10-22
[正点原子]Linux驱动学习笔记--6.pinctrl和gpio子系统实验
1. 实验目的Linux 内核提供了 pinctrl 和 gpio 子系统用于GPIO 驱动,本章我们就来学习一下如何借助 pinctrl 和 gpio 子系统来简化 GPIO 驱动开发。2. 知识点2.1 pinctrl子系统简介大多数 SOC 的 pin 都是支持复用的,比如 I.MX6ULL 的 GPIO1_IO03 既可以作为普通的GPIO 使用,也可以作为 I2C1 的 SDA 等等。此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。 pinctrl 子系统就是为了解决这个问题而引入的, pinctrl 子系统主要工作内容如下:获取设备树中 pin 信息。根据获取到的 pin 信息来设置 pin 的复用功能根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成, pinctrl 子系统源码目录为 drivers/pinctrl。2.1.1 IMX6ULL的pinctrl子系统驱动PIN配置信息详解要使用 pinctrl 子系统,我们需要在设备树里面设置 PIN 的配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息。打开 imx6ul.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示: iomuxc: iomuxc@20e0000 { compatible = "fsl,imx6ul-iomuxc"; reg = <0x020e0000 0x4000>; };iomuxc 节点就是 I.MX6ULL 的 IOMUXC 外设对应的节点,看起来内容很少,没看出什么跟 PIN 的配置有关的内容啊,别急!打开 imx6ull-alientek-emmc.dts,找到如下所示内容:\\ 示例代码 45.1.2.2 iomuxc 节点内容 2 311 &iomuxc { 312 pinctrl-names = "default"; 313 pinctrl-0 = <&pinctrl_hog_1>; 314 imx6ul-evk { 315 pinctrl_hog_1: hoggrp-1 { 316 fsl,pins = < 317 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 318 MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 319 MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 320 MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 321 >; 322 }; ...... 371 pinctrl_flexcan1: flexcan1grp{ 372 fsl,pins = < 373 MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020 374 MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020 375 >; 376 }; ...... 587 pinctrl_wdog: wdoggrp { 588 fsl,pins = < 589 MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0 590 >; 591 }; 592 }; 593 };示例代码 45.1.2.2 就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有 PIN 都组织在一个子节点里面。示例代码45.1.2.2 中 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。pinctrl_flexcan1 子节点是 flexcan1 这个外设所使用的 PIN, pinctrl_wdog 子节点是 wdog 外设所使用的 PIN。如果需要在 iomuxc 中添加我们自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。我们以pinctrl_hog_1这个子节点所使用的PIN配置信息为例,讲解如何添加PIN的配置信息。以UART1_RTS_B这个 PIN 为例,配置信息为:MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059首先说明一下, UART1_RTS_B 这个 PIN 是作为 SD 卡的检测引脚,也就是通过此 PIN 就可以检测到SD卡是否有插入。 UART1_RTS_B的配置信息分为两部分 :MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 和 0x17059,我们重点来看一下这两部分是什么含义,前面说了,对于一个 PIN 的配置主要包括两方面,一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以我们可以大胆的猜测 UART1_RTS_B 的这两部分配置信息一个是设置 UART1_RTS_B 的复用功能,一个是用来设置 UART1_RTS_B 的电气特性。首先来看一下 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19,这是一个宏定义,定义内容如下:// 示例代码 45.1.2.4 UART1_RTS_B 引脚定义 190 #define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3 191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0 192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0 193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1 194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1 195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0 196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0 197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2示例代码 45.1.2.4 中一共有 8 个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,大家仔细观察应该就能发现,这 8 个宏定义分别对应 UART1_RTS_B 这个 PIN 的 8 个复用 IO。查阅《I.MX6ULL 参考手册》可以知 UART1_RTS_B 的可选复用 IO 如图 45.1.2.1 所示:示例代码 196 行 的 宏 定 义 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 表 示 将UART1_RTS_B 这个 IO 复用为 GPIO1_IO19。此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,如下所示:0x0090 0x031C 0x0000 0x5 0x0这 5 个值的含义如下所示:<mux_reg conf_reg input_reg mux_mode input_val>综上所述可知:0x0090: mux_reg 寄存器偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点 ,根据其reg属性可知IOMUXC外设寄存器起始地址为0x020e0000 。 因 此0x020e0000+0x0090=0x020e0090, IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器地址正好是0x020e0090.因此可知, 0x020e0000+mux_reg 就是 PIN 的复用寄存器地址0x031C: conf_reg 寄存器偏移地址,和 mux_reg 一样, 0x020e0000+0x031c=0x020e031c,这个就是寄存器 IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B 的地址。0x0000: input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置, UART1_RTS_B 这个 PIN 在做GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。0x5 : mux_reg 寄 存 器 值 , 在 这 里 就 相 当 于 设 置IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B 寄存器为 0x5,也即是设置 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19。0x0: input_reg 寄存器值,在这里无效。这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 的含义,看的比较仔细的同学应该会发现并没有 conf_reg 寄存器的值, config_reg 寄存器是设置一个 PIN 的电气特性的,这么重要的寄存器怎么没有值呢?回到示例代码 45.1.2.3 中,MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 我们上面已经分析了,就剩下了一个 0x17059,0x17059 就是 conf_reg 寄存器值!此值由用户自行设置,通过此值来设置一个 IO 的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B的值为 0x17059。imx pinctrl 驱动程序详解所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。驱动文件为drivers/pinctrl/freescale/pinctrl-imx6ul.cprobe函数执行流程:在图 45.1.2.3 中函数 imx_pinctrl_parse_groups 负责获取设备树中关于 PIN 的配置信息,也就是我们前面分析的那 6 个 u32 类型的值。处理过程如下所示:示例代码 45.1.2.6 imx_pinctrl_parse_groups 函数代码段 488 /* 489 * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID 490 * and 1 u32 CONFIG, so 24 types in total for each pin. 491 */ 492 #define FSL_PIN_SIZE 24 493 #define SHARE_FSL_PIN_SIZE 20 494 495 static int imx_pinctrl_parse_groups(struct device_node *np, 496 struct imx_pin_group *grp, 497 struct imx_pinctrl_soc_info *info, 498 u32 index) 499 { 500 int size, pin_size; 501 const __be32 *list; 502 int i; 503 u32 config; ...... 537 538 for (i = 0; i < grp->npins; i++) { 539 u32 mux_reg = be32_to_cpu(*list++); 540 u32 conf_reg; 541 unsigned int pin_id; 542 struct imx_pin_reg *pin_reg; 543 struct imx_pin *pin = &grp->pins[i]; 544 ...... 555 556 pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4; 557 pin_reg = &info->pin_regs[pin_id]; 558 pin->pin = pin_id; 559 grp->pin_ids[i] = pin_id; 560 pin_reg->mux_reg = mux_reg; 561 pin_reg->conf_reg = conf_reg; 562 pin->input_reg = be32_to_cpu(*list++); 563 pin->mux_mode = be32_to_cpu(*list++); 564 pin->input_val = be32_to_cpu(*list++); 565 566 /* SION bit is in mux register */ 567 config = be32_to_cpu(*list++); 568 if (config & IMX_PAD_SION) 569 pin->mux_mode |= IOMUXC_CONFIG_SION; 570 pin->config = config & ~IMX_PAD_SION; ...... 574 } 575 576 return 0; 577 } 第 496 和 497 行,设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。第 560~564 行,获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。第 570 行,获取 config 值。接下来看一下函数 pinctrl_register,此函数用于向 Linux 内核注册一个 PIN 控制器,此函数原型如下:struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, struct device *dev, void *driver_data)参数 pctldesc 非常重要,因为此参数就是要注册的 PIN 控制器,PIN 控制器用于配置 SOC的 PIN 复用功能和电气特性。参数 pctldesc 是 pinctrl_desc 结构体类型指针, pinctrl_desc 结构体如下所示:// 示例代码 45.1.2.7 pinctrl_desc 结构体 128 struct pinctrl_desc { 129 const char *name; 130 struct pinctrl_pin_desc const *pins; 131 unsigned int npins; 132 const struct pinctrl_ops *pctlops; 133 const struct pinmux_ops *pmxops; 134 const struct pinconf_ops *confops; 135 struct module *owner; 136 #ifdef CONFIG_GENERIC_PINCONF 137 unsigned int num_custom_params; 138 const struct pinconf_generic_params *custom_params; 139 const struct pin_config_item *custom_conf_items; 140 #endif 141 };第 132~124 行,这三个_ops结构体指针非常重要!!!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN 的配置。 pinctrl_desc 结构体需要由soc厂商提供,比如在 imx_pinctrl_probe 函数中可以找到如下所示代码:示例代码 45.1.2.8 imx_pinctrl_probe 函数代码段 648 int imx_pinctrl_probe(struct platform_device *pdev, 649 struct imx_pinctrl_soc_info *info) 650 { 651 struct device_node *dev_np = pdev->dev.of_node; 652 struct device_node *np; 653 struct imx_pinctrl *ipctl; 654 struct resource *res; 655 struct pinctrl_desc *imx_pinctrl_desc; ...... 663 664 imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc), 665 GFP_KERNEL); 666 if (!imx_pinctrl_desc) 667 return -ENOMEM; ...... 705 706 imx_pinctrl_desc->name = dev_name(&pdev->dev); 707 imx_pinctrl_desc->pins = info->pins; 708 imx_pinctrl_desc->npins = info->npins; 709 imx_pinctrl_desc->pctlops = &imx_pctrl_ops; 710 imx_pinctrl_desc->pmxops = &imx_pmx_ops; 711 imx_pinctrl_desc->confops = &imx_pinconf_ops; 712 imx_pinctrl_desc->owner = THIS_MODULE; ...... 723 ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev, ipctl); ...... 732 }第 655 行,定义结构体指针变量 imx_pinctrl_desc。第 664 行,向指针变量 imx_pinctrl_desc 分配内存。第 706~712 行,初始化 imx_pinctrl_desc 结构体指针变量,重点是 pctlops、 pmxops 和 confops这三个成员变量,分别对应 imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体。第 723 行,调用函数 pinctrl_register 向 Linux 内核注册 imx_pinctrl_desc,注册以后 Linux 内核就有了对 I.MX6ULL 的 PIN 进行配置的工具。imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 这三个结构体定义如下:示例代码 45.1.2.9 imx_pctrl_ops、 imx_pmx_ops 和 imx_pinconf_ops 结构体 174 static const struct pinctrl_ops imx_pctrl_ops = { 175 .get_groups_count = imx_get_groups_count, 176 .get_group_name = imx_get_group_name, 177 .get_group_pins = imx_get_group_pins, 178 .pin_dbg_show = imx_pin_dbg_show, 179 .dt_node_to_map = imx_dt_node_to_map, 180 .dt_free_map = imx_dt_free_map, 181 182 }; ...... 374 static const struct pinmux_ops imx_pmx_ops = { 375 .get_functions_count = imx_pmx_get_funcs_count, 376 .get_function_name = imx_pmx_get_func_name, 377 .get_function_groups = imx_pmx_get_groups, 378 .set_mux = imx_pmx_set, 379 .gpio_request_enable = imx_pmx_gpio_request_enable, 380 .gpio_set_direction = imx_pmx_gpio_set_direction, 381 }; ...... 481 static const struct pinconf_ops imx_pinconf_ops = { 482 .pin_config_get = imx_pinconf_get, 483 .pin_config_set = imx_pinconf_set, 484 .pin_config_dbg_show = imx_pinconf_dbg_show, 485 .pin_config_group_dbg_show = imx_pinconf_group_dbg_show, 486 };2.2 gpio子系统pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。2.2.1 imx gpio子系统驱动设备树中的gpio信息I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B 做为 SD 卡的检测引脚, UART1_RTS_B 复用为 GPIO1_IO19,通过读取这个 GPIO 的高低电平就可以知道 SD 卡有没有插入。首先肯定是将 UART1_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,也就是上一小节讲的pinctrl 节点。打开 imx6ull-alientek-emmc.dts, UART1_RTS_B 这个 PIN 的 pincrtl 设置如下:// 示例代码 45.2.2.1 SD 卡 CD 引脚 PIN 配置参数 316 pinctrl_hog_1: hoggrp-1 { 317 fsl,pins = < 318 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */ ...... 322 >; 323 };第 318 行,设置 UART1_RTS_B 这个 PIN 为 GPIO1_IO19。pinctrl 配置好以后就是设置 gpio 了,在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚,SD卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。 SD 卡连接在I.MX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为“usdhc1”的节点,这个节点就是 SD 卡设备节点,如下所示:示例代码 45.2.2.2 设备树中 SD 卡节点 760 &usdhc1 { 761 pinctrl-names = "default", "state_100mhz", "state_20 762 pinctrl-0 = <&pinctrl_usdhc1>; 763 pinctrl-1 = <&pinctrl_usdhc1_100mhz>; 764 pinctrl-2 = <&pinctrl_usdhc1_200mhz>; 765 /* pinctrl-3 = <&pinctrl_hog_1>; */ 766 cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>; 767 keep-power-in-suspend; 768 enable-sdio-wakeup; 769 vmmc-supply = <®_sd1_vmmc>; 770 status = "okay"; 771 };第 765 行,此行本来没有,是作者添加的, usdhc1 节点作为 SD 卡设备总节点, usdhc1 节点需要描述 SD 卡所有的信息,因为驱动要使用。本行就是描述 SD 卡的 CD 引脚 pinctrl 信息所在的子节点,因为 SD 卡驱动需要根据 pincrtl 节点信息来设置 CD 引脚的复用功能等。762~764行的 pinctrl-0~2 都是 SD 卡其他 PIN 的 pincrtl 节点信息。但是大家会发现,其实在 usdhc1 节点中并没有pinctrl-3 = <&pinctrl_hog_1>这一行,也就是说并没有指定 CD 引脚的 pinctrl 信息,那么 SD 卡驱动就没法设置 CD 引脚的复用功能啊?这个不用担心,因为在“iomuxc”节点下引用了 pinctrl_hog_1 这个节点,所以 Linux 内核中的 iomuxc 驱动就会自动初始化 pinctrl_hog_1节点下的所有 PIN。第 766 行,属性“cd-gpios”描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个,我们来看一下这三个属性值的含义,&gpio1表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19”表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19这 GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。根据上面这些信息, SD 卡驱动程序就可以使用 GPIO1_IO19 来检测 SD 卡的 CD 信号了,打开 imx6ull.dtsi,在里面找到如下所示内容:// 示例代码 45.2.2.2 gpio1 节点 504 gpio1: gpio@0209c000 { 505 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio" 506 reg = <0x0209c000 0x4000>; 507 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, 508 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; 509 gpio-controller; 510 #gpio-cells = <2>; 511 interrupt-controller; 512 #interrupt-cells = <2>; 513 };gpio1 节点信息描述了 GPIO1 控制器的所有信息,重点就是 GPIO1 外设寄存器基地址以及兼 容 属 性 。 关 于 I.MX 系 列 SOC 的 GPIO 控 制 器 绑 定 信 息 请 查 看 文 档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。第 505 行,设置 gpio1 节点的 compatible 属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。第 506 行, reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000,大家可以打开《I.MX6ULL 参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第 28.5 小节,有如图 45.2.2.1 所示的寄存器地址表:从图 45.2.2.1 可以看出, GPIO1 控制器的基地址就是 0X0209C000。第 509 行,“gpio-controller”表示 gpio1 节点是个 GPIO 控制器。第 510 行,“#gpio-cells”属性和“#address-cells”类似, #gpio-cells 应该为 2,表示一共有两个 cell,第一个 cell 为 GPIO 编号,比如“&gpio1 3”就表示 GPIO1_IO03。第二个 cell 表示GPIO 极 性 , 如 果 为 0(GPIO_ACTIVE_HIGH) 的 话 表 示 高 电 平 有 效 , 如 果 为1(GPIO_ACTIVE_LOW)的话表示低电平有效。gpio驱动简介驱动文件为gpio-mxc.c 这个文件,在 gpio-mxc.c 文件中有如下所示内容://示例代码 45.2.2.4 mxc_gpio_driver 结构体 496 static struct platform_driver mxc_gpio_driver = { 497 .driver = { 498 .name = "gpio-mxc", 499 .of_match_table = mxc_gpio_dt_ids, 500 }, 501 .probe = mxc_gpio_probe, 502 .id_table = mxc_gpio_devtype, 503 };可以看出 GPIO 驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后 probe 函数就会执行,在这里就是 mxc_gpio_probe 函数,这个函数就是I.MX6ULL 的 GPIO 驱动入口函数。我们简单来分析一下 mxc_gpio_probe 这个函数,函数内容如下:示例代码 45.2.2.5 mxc_gpio_probe 函数 403 static int mxc_gpio_probe(struct platform_device *pdev) 404 { 405 struct device_node *np = pdev->dev.of_node; 406 struct mxc_gpio_port *port; 407 struct resource *iores; 408 int irq_base; 409 int err; 410 411 mxc_gpio_get_hw(pdev); 412 413 port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); 414 if (!port) 415 return -ENOMEM; 416 417 iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); 418 port->base = devm_ioremap_resource(&pdev->dev, iores); 419 if (IS_ERR(port->base)) 420 return PTR_ERR(port->base); 421 422 port->irq_high = platform_get_irq(pdev, 1); 423 port->irq = platform_get_irq(pdev, 0); 424 if (port->irq < 0) 425 return port->irq; 426 427 /* disable the interrupt and clear the status */ 428 writel(0, port->base + GPIO_IMR); 429 writel(~0, port->base + GPIO_ISR); 430 431 if (mxc_gpio_hwtype == IMX21_GPIO) { 432 /* 433 * Setup one handler for all GPIO interrupts. Actually 434 * setting the handler is needed only once, but doing it for 435 * every port is more robust and easier. 436 */ 437 irq_set_chained_handler(port->irq, mx2_gpio_irq_handler); 438 } else { 439 /* setup one handler for each entry */ 440 irq_set_chained_handler(port->irq, mx3_gpio_irq_handler); 441 irq_set_handler_data(port->irq, port); 442 if (port->irq_high > 0) { 443 /* setup handler for GPIO 16 to 31 */ 444 irq_set_chained_handler(port->irq_high, 445 mx3_gpio_irq_handler); 446 irq_set_handler_data(port->irq_high, port); 447 } 448 } 449 450 err = bgpio_init(&port->bgc, &pdev->dev, 4, 451 port->base + GPIO_PSR, 452 port->base + GPIO_DR, NULL, 453 port->base + GPIO_GDIR, NULL, 0); 454 if (err) 455 goto out_bgio; 456 457 port->bgc.gc.to_irq = mxc_gpio_to_irq; 458 port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") 459 * 32 : pdev->id * 32; 460 461 err = gpiochip_add(&port->bgc.gc); 462 if (err) 463 goto out_bgpio_remove; 464 465 irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id()); 466 if (irq_base < 0) { 467 err = irq_base; 468 goto out_gpiochip_remove; 469 } 470 471 port->domain = irq_domain_add_legacy(np, 32, irq_base, 0, 472 &irq_domain_simple_ops, NULL); 473 if (!port->domain) { 474 err = -ENODEV; 475 goto out_irqdesc_free; 476 } 477 478 /* gpio-mxc can be a generic irq chip */ 479 mxc_gpio_init_gc(port, irq_base); 480 481 list_add_tail(&port->node, &mxc_gpio_ports); 482 483 return 0; ...... 494 }第 405 行,设备树节点指针。第 406 行,定义一个结构体指针 port,结构体类型为 mxc_gpio_port。 gpio-mxc.c 的重点工作就是维护 mxc_gpio_port, mxc_gpio_port 就是对 I.MX6ULL GPIO 的抽象。 mxc_gpio_port 结构体定义如下:// 示例代码 45.2.2.6 mxc_gpio_port 结构体 61 struct mxc_gpio_port { 62 struct list_head node; 63 void __iomem *base; 64 int irq; 65 int irq_high; 66 struct irq_domain *domain; 67 struct bgpio_chip bgc; 68 u32 both_edges; 69 };mxc_gpio_port 的 bgc 成员变量很重要,因为稍后的重点就是初始化 bgc。继续回到 mxc_gpio_probe 函数函数,第 411 行调用 mxc_gpio_get_hw 函数获取 gpio 的硬件相关数据,其实就是 gpio 的寄存器组,函数 mxc_gpio_get_hw 里面有如下代码:示例代码 45.2.2.7 mxc_gpio_get_hw 函数 364 static void mxc_gpio_get_hw(struct platform_device *pdev) 365 { 366 const struct of_device_id *of_id = 367 of_match_device(mxc_gpio_dt_ids, &pdev->dev); 368 enum mxc_gpio_hwtype hwtype; ...... 383 384 if (hwtype == IMX35_GPIO) 385 mxc_gpio_hwdata = &imx35_gpio_hwdata; 386 else if (hwtype == IMX31_GPIO) 387 mxc_gpio_hwdata = &imx31_gpio_hwdata; 388 else 389 mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata; 390 391 mxc_gpio_hwtype = hwtype; 392 }注意第 385 行, mxc_gpio_hwdata 是个全局变量,如果硬件类型是 IMX35_GPIO 的话设置mxc_gpio_hwdat 为 imx35_gpio_hwdata。对于 I.MX6ULL 而言,硬件类型就是 IMX35_GPIO,imx35_gpio_hwdata 是个结构体变量,描述了 GPIO 寄存器组,内容如下:// 示例代码 45.2.2.8 imx35_gpio_hwdata 结构体 101 static struct mxc_gpio_hwdata imx35_gpio_hwdata = { 102 .dr_reg = 0x00, 103 .gdir_reg = 0x04, 104 .psr_reg = 0x08, 105 .icr1_reg = 0x0c, 106 .icr2_reg = 0x10, 107 .imr_reg = 0x14, 108 .isr_reg = 0x18, 109 .edge_sel_reg = 0x1c, 110 .low_level = 0x00, 111 .high_level = 0x01, 112 .rise_edge = 0x02, 113 .fall_edge = 0x03, 114 };大家将 imx35_gpio_hwdata 中的各个成员变量和图 45.2.2.1 中的 GPIO 寄存器表对比就会发现,imx35_gpio_hwdata 结构体就是 GPIO 寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata 这个全局变量来访问 GPIO 的相应寄存器了。继 续 回 到 示 例 代 码 45.2.2.5 的 mxc_gpio_probe 函 数 中 , 第 417 行 , 调 用 函 数platform_get_resource 获取设备树中内存资源信息,也就是 reg 属性值。前面说了 reg 属性指定了 GPIO1 控制器的寄存器基地址为 0X0209C000,在配合前面已经得到的 mxc_gpio_hwdata,这样 Linux 内核就可以访问 gpio1 的所有寄存器了。第 418 行,调用 devm_ioremap_resource 函数进行内存映射,得到 0x0209C000 在 Linux 内核中的虚拟地址。第 422、 423 行,通过 platform_get_irq 函数获取中断号,第 422 行获取高 16 位 GPIO 的中断号,第 423 行获取底 16 位 GPIO 中断号。第 428、 429 行,操作 GPIO1 的 IMR 和 ISR 这两个寄存器,关闭 GPIO1 所有 IO 中断,并且清除状态寄存器。第 438~448 行,设置对应 GPIO 的中断服务函数,不管是高 16 位还是低 16 位,中断服务函数都是 mx3_gpio_irq_handler。第 450~453 行, bgpio_init 函数第一个参数为 bgc,是 bgpio_chip 结构体指针。 bgpio_chip结构体有个 gc 成员变量, gc 是个 gpio_chip 结构体类型的变量。 gpio_chip 结构体是抽象出来的GPIO 控制器, gpio_chip 结构体如下所示(有缩减):示例代码 45.2.2.9 gpio_chip 结构体 74 struct gpio_chip { 75 const char *label; 76 struct device *dev; 77 struct module *owner; 78 struct list_head list; 79 80 int (*request)(struct gpio_chip *chip, 81 unsigned offset); 82 void (*free)(struct gpio_chip *chip, 83 unsigned offset); 84 int (*get_direction)(struct gpio_chip *chip, 85 unsigned offset); 86 int (*direction_input)(struct gpio_chip *chip, 87 unsigned offset); 88 int (*direction_output)(struct gpio_chip *chip, 89 unsigned offset, int value); 90 int (*get)(struct gpio_chip *chip, 91 unsigned offset); 92 void (*set)(struct gpio_chip *chip, 93 unsigned offset, int value); ...... 145 };可以看出, gpio_chip 大量的成员都是函数,这些函数就是 GPIO 操作函数。 bgpio_init 函数主 要 任 务 就 是 初 始 化 bgc->gc 。 bgpio_init 里 面 有 三 个 setup 函 数 : bgpio_setup_io 、bgpio_setup_accessors 和 bgpio_setup_direction。这三个函数就是初始化 bgc->gc 中的各种有关GPIO 的操作,比如输出,输入等等。第 451~453 行的 GPIO_PSR、 GPIO_DR 和 GPIO_GDIR 都是 I.MX6ULL 的 GPIO 寄存器。这些寄存器地址会赋值给 bgc 参数的 reg_dat、 reg_set、 reg_clr和 reg_dir 这些成员变量。至此, bgc 既有了对 GPIO 的操作函数,又有了 I.MX6ULL 有关 GPIO的寄存器,那么只要得到 bgc 就可以对 I.MX6ULL 的 GPIO 进行操作。继续回到mxc_gpio_probe 函数,第461行调用函数gpiochip_add向Linux内核注册gpio_chip,也就是 port->bgc.gc。注册完成以后我们就可以在驱动中使用 gpiolib 提供的各个 API 函数。2.2.2 gpio子系统API函数对于驱动开发人员,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO, gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。 gpio 子系统提供的常用的 API 函数有下面几个:gpio_request 函数gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_request进行申请,函数原型如下:int gpio_request(unsigned gpio, const char *label)函数参数和返回值含义如下:gpio:要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。label:给 gpio 设置个名字。返回值: 0,申请成功;其他值,申请失败。gpio_free 函数如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。函数原型如下:void gpio_free(unsigned gpio)函数参数和返回值含义如下:gpio:要释放的 gpio 标号。返回值:无。gpio_direction_input 函数此函数用于设置某个 GPIO 为输入,函数原型如下所示:int gpio_direction_input(unsigned gpio)函数参数和返回值含义如下:gpio:要设置为输入的 GPIO 标号。返回值:0,设置成功;负值,设置失败。gpio_direction_output 函数此函数用于设置某个 GPIO 为输出,并且设置默认输出值,函数原型如下:int gpio_direction_output(unsigned gpio, int value)函数参数和返回值含义如下:gpio:要设置为输出的 GPIO 标号。value: GPIO 默认输出值。返回值: 0,设置成功;负值,设置失败。gpio_get_value 函数此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏,定义所示:#define gpio_get_value __gpio_get_value int __gpio_get_value(unsigned gpio)函数参数和返回值含义如下:gpio:要获取的 GPIO 标号。返回值: 非负值,得到的 GPIO 值;负值,获取失败。gpio_set_value 函数此函数用于设置某个 GPIO 的值,此函数是个宏,定义如下#define gpio_set_value __gpio_set_value void __gpio_set_value(unsigned gpio, int value)函数参数和返回值含义如下:gpio:要设置的 GPIO 标号。value: 要设置的值。返回值: 无关于 gpio 子系统常用的 API 函数就讲这些,这些是我们用的最多的。2.2.3 与gpio相关的OF函数在驱动程序中需要读取 gpio 属性内容, Linux 内核提供了几个与 GPIO 有关的 OF 函数,常用的几个 OF 函数如下所示:of_gpio_named_count 函数of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意的是空的 GPIO 信息也会被统计到,比如:gpios = <0 &gpio1 1 2 0 & gpio2 3 4>;上述代码的“gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:int of_gpio_named_count(struct device_node *np, const char *propname)函数参数和返回值含义如下:np:设备节点。propname:要统计的 GPIO 属性。返回值: 正值,统计到的 GPIO 数量;负值,失败。of_gpio_count 函数和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型如下所示:int of_gpio_count(struct device_node *np)函数参数和返回值含义如下:np:设备节点。返回值: 正值,统计到的 GPIO 数量;负值,失败。of_get_named_gpio 函数此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:int of_get_named_gpio(struct device_node *np, const char *propname, int index)函数参数和返回值含义如下:np:设备节点。propname:包含要获取 GPIO 信息的属性名。index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0。返回值: 正值,获取到的 GPIO 编号;负值,失败。3. 实验程序&测试1、添加pinctrl节点I.MX6U-ALPHA 开发板上的 LED 灯使用了 GPIO1_IO03 这个 PIN,添加一个子节点,如下所示 pinctrl_gpioled: ledgrp{ fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0 >; };将 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03,电气属性值为 0X10B0。2、添加LED设备节点在根节点下创建LED灯节点,节点名为gpioled, 节点内存如下: gpioled{ compatible = "alientek,gpioled"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpioled>; led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; status = "okay"; }; pinctrl-0 属性设置 LED 灯所使用的 PIN 对应的 pinctrl 节点。led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO1 的 IO03,低电平有效。稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系统的 API 操作函数需要 GPIO 编号。3、检查PIN是否被其它外设使用一个引脚一次只能实现一个功能,如果A引脚在设备树中配置为了I2C的SDA信号,那么A引脚就不能再配置为GPIO,否则的话驱动程序在申请GPIO的时候就会失败。检查PIN有没有被其他外设使用包括两个方面:检查pinctrl设置本实验LED灯使用的是GPIO03,因此先检查GPIO_IO03有没有被其它的pinctrl节点使用,可能我用的是正点原子已经修改过的了,所以这里没有看到冲突的。如果这个PIN被配置为GPIO的话,检查这个GPIO有没有被别的外设使用。因为本实验我们将 GPIO1_IO03 这个 PIN 配置为了 GPIO,所以还需要查找一下有没有其他的外设使用了 GPIO1_IO03,在dts 中搜索“gpio1 3”,找到如下内容:这里可能是正点原子的呼吸灯配置,做这个实验的时候可以先注释掉这个。 dtsleds { compatible = "gpio-leds"; led0 { label = "red"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpioled>; gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; default-state = "on"; linux,default-trigger = "heartbeat"; }; }; 实验程序:https://github.com/ADTXL/imx_linux/commit/cf466c43bc129304dd1173501517b346c40d31f7测试OK.
2022年10月22日
864 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--5.设备树下的LED驱动实验
0. 实验说明在dts中添加对应的设备节点在驱动程序中,获取设备树中节点的属性值根据获取的有关属性值初始化led灯实验程序:https://github.com/ADTXL/imx_linux/commit/f72972b43bcecac7a2c686f652b4b2ee22df87af测试OK.
2022年10月22日
340 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--4.设备树详解
暂无简介
2022年10月22日
613 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--3.Linux新字符设备驱动实验
暂无简介
2022年10月22日
486 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--2.LED驱动实验(直接操作寄存器)
暂无简介
2022年10月22日
404 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--1.第一个Linux驱动(字符设备驱动)
暂无简介
2022年10月22日
501 阅读
0 评论
0 点赞
2022-10-22
[正点原子]Linux驱动学习笔记--0.基本环境准备
1. 代码准备为了使用、学习方便,在github上新建了两个仓库,将正点原子提供的linux和uboot code放到了上面。https://github.com/ADTXL/imx_uboothttps://github.com/ADTXL/imx_linux2. uboot编译编译命令如下// 安装ncurses库 sudo apt-get install libncurses5-dev // 512MB+8GB 的 EMMC 核心板 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_alientek_emmc_defconfig make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16或者直接运行正点原子提供的脚本./mx6ull_alientek_emmc.sh编译完成后如下所示:其中,u-boot.bin就是编译出来的uboot二进制文件。uboot是个裸机程序,因此需要在其前面加上头部(IVT、DCD等数据)才能在I.MX6U上执行。u-boot.imx文件就是添加头部以后的u-boot.bin,u-boot.imx就是我们最终要烧写到开发板中的uboot镜像文件。3. linux编译直接运行脚本./mx6ull_alientek_emmc.sh编译完成后,有两个以后烧写需要用到的文件,即kernel image(arch/arm/boot)和dtb(arch/arm/boot/dts目录)文件.4. 烧写image4.1 使用tftp烧写uboot是支持网络的,在使用uboot的网络功能之前先用网线将开发板的的ENET2接口(ALPHA开发板靠近vga线的那个)和电脑或者路由器连接起来。然后设置下面几个环境变量环境变量描述ipaddr开发板ip地址,可以不设置,使用dhcp命令来从路由器获取ip地址ethaddr开发板的mac地址,一定要设置gatewayip网关地址netmask子网掩码serverip服务器ip地址,也就是ubuntu主机地址,用于调试代码我的开发板是直连windows的,没有使用路由器,使用了这个博客里的方法超全详细,解决校园网没有路由器实现开发板和虚拟机直连,亲测可用.setenv ipaddr 192.168.10.101 setenv ethaddr 00:04:9f:04:d2:35 setenv gatewayip 192.168.10.1 setenv netmask 255.255.255.0 setenv serverip 192.168.10.100 saveenv使用ping命令测试下,是ok的=> ping 192.168.10.100 Using FEC1 device host 192.168.10.100 is alive tftp使用tftp协议,将ubuntu主机作为tftp服务器,通过网络把镜像烧写到dram中。因此,需要在ubuntu上搭建TFTP服务器,需要安装tftp-hpa和tftpd-hpa,命令如下:sudo apt-get install tftp-hpa tftpd-hpa sudo apt-get install xinetd然后,需要创建一个目录存放镜像文件/home/user/linux/tftpboot,需要注意,需要给此目录读写权限,否则的话uboot不能从tftpboot文件夹里下载文件。最后配置 tftp,安装完成以后新建文件/etc/xinetd.d/tftp,如果没有/etc/xinetd.d 目录的话自行创建,然后在里面输入如下内容:server tftp { socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s /home/user/linux/tftpboot 9 disable = no per_source = 11 cps = 100 2 flags = IPv4 }完了以后启动 tftp 服务,命令如下:sudo service tftpd-hpa start打开/etc/default/tftpd-hpa 文件,将其修改为如下所示内容:# /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/home/user/linux/tftpboot" TFTP_ADDRESS="0.0.0.0:69" TFTP_OPTIONS="-l -c -s" 最后输入如下命令, 重启 tftp 服务器:sudo service tftpd-hpa restart最后,我们测试下能否烧写成功,tftp命令格式如下tftpboot [loadAddress] [[hostIPaddr:]bootfilename]例如下面的命令,我们通过tftp命令把kernel镜像Image文件,烧写到dram中的80800000地址。=> tftp 80800000 Image Using FEC1 device TFTP from server 192.168.10.100; our IP address is 192.168.10.101 Filename 'Image'. Load address: 0x80800000 Loading: ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ################################################################# ####################################################### 2.2 MiB/s done Bytes transferred = 11302188 (ac752c hex)除了tftp,还有其他的烧写方法,这里就先不研究了~5. 搭建NFS环境NFS(Network File System),网络文件系统,是由SUN公司研制的UNIX表示层协议,能使使用者访问网络上别处的文件就像在使用自己的计算机一样。我们可以使用NFS加载文件系统到开发板上,方便开发。5.1 配置NFS服务安装NFS服务在 Ubuntu 终端执行以下指令安装 NFS。sudo apt-get install nfs-kernel-serverNFS共享目录新建NFS共享目录,并给予rwx权限,本地新建目录为/home/user/linux/nfs配置NFS服务执行以下指令打开 etc/exports 文件sudo vi /etc/exports进入 etc/exports 文件,在最后添加如下内容/home/user/linux/nfs *(rw,sync,no_root_squash)/home/user/linux/nfs 表示 NFS 共享的目录*表示允许所有的网络段访问rw 表示访问者具有可读写权限sync 表示将缓存写入设备中,可以说是同步缓存的意思no_root_squash 表示访问者具有 root 权限。修改完如下图所示。# /etc/exports: the access control list for filesystems which may be exported # to NFS clients. See exports(5). # # Example for NFSv2 and NFSv3: # /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check) # # Example for NFSv4: # /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check) # /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check) # /home/user/linux/nfs *(rw,sync,no_root_squash) 修改完以后保存退出。执行以下指令重启 NFS 服务器。sudo /etc/init.d/nfs-kernel-server restart执行以下指令查看 NFS 共享目录。showmount -e,如下所示user@ubuntu:~/linux/nfs$ showmount -e Export list for ubuntu: /home/user/linux/nfs *5.2 开发板测试确保网络环境正常,Ubuntu、Windows 和开发板能相互 ping 通。这里结合 4.1 小节的配置来验证,即:setenv ipaddr 192.168.10.101 setenv ethaddr 00:04:9f:04:d2:35 setenv gatewayip 192.168.10.1 setenv netmask 255.255.255.0 setenv serverip 192.168.10.100 saveenv开发板 IP:192.168.10.101windows 有线IP: 192.168.10.99虚拟机 IP:192.168.10.100开发板开机后,需要手动设置ip,ifconfig eth0 up ifconfig eth0 192.168.10.101 ifconfig查看设置结果,root@ATK-IMX6U:~# ifconfig eth0 Link encap:Ethernet HWaddr 88:36:fd:16:f1:cc inet addr:192.168.10.101 Bcast:192.168.10.255 Mask:255.255.255.0 inet6 addr: fe80::8a36:fdff:fe16:f1cc/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:152 errors:0 dropped:0 overruns:0 frame:0 TX packets:56 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:12135 (11.8 KiB) TX bytes:15504 (15.1 KiB) eth1 Link encap:Ethernet HWaddr 88:af:c1:a1:1c:7d UP BROADCAST MULTICAST DYNAMIC MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:158 errors:0 dropped:0 overruns:0 frame:0 TX packets:158 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:10652 (10.4 KiB) TX bytes:10652 (10.4 KiB) 在/home/user/linux/nfs 目录下创建一个 mytest.c 文件,在里面写入 hello world!执行以下指令设置开发板 IP,创建一个 get 目录,将虚拟机(192.168.10.100)NFS 共享目录挂载到到开发板的 get 目录中。mkdir get mount -t nfs -o nolock,nfsvers=3 192.168.10.100:/home/user/linux/nfs get/使用df命令查看挂载的 NFS 目录:root@ATK-IMX6U:~/get# df Filesystem 1K-blocks Used Available Use% Mounted on /dev/root 7342568 645076 6301460 10% / devtmpfs 187632 120 187512 1% /dev tmpfs 40 0 40 0% /mnt/.psplash tmpfs 253432 192 253240 1% /run tmpfs 253432 144 253288 1% /var/volatile /dev/mmcblk1p1 32248 6902 25347 22% /run/media/mmcblk1p1 192.168.10.100:/home/user/linux/nfs 205312000 24284160 170525696 13% /home/root/get root@ATK-IMX6U:~# mount -t nfs -o nolock,nfsvers=3 192.168.10.100:/home/user/linux/nfs get/ root@ATK-IMX6U:~# cd get/ root@ATK-IMX6U:~/get# ls -al total 11M drwxrwxr-x 2 1000 tracing 4.0K Nov 12 2022 . drwx------ 5 root root 4.0K Jul 21 15:12 .. -rwxrwxr-x 1 1000 tracing 11M Nov 12 2022 Image -rw-rw-r-- 1 1000 tracing 13 Nov 12 2022 mytest.c root@ATK-IMX6U:~/get# cat mytest.c hello world! 卸载 NFS 目录:umount getroot@ATK-IMX6U:~# umount get root@ATK-IMX6U:~# df Filesystem 1K-blocks Used Available Use% Mounted on /dev/root 7342568 645076 6301460 10% / devtmpfs 187632 120 187512 1% /dev tmpfs 40 0 40 0% /mnt/.psplash tmpfs 253432 188 253244 1% /run tmpfs 253432 144 253288 1% /var/volatile /dev/mmcblk1p1 32248 6902 25347 22% /run/media/mmcblk1p1 可以看到 192.168.10.100:/home/user/linux/nfs 已经卸载了。6. 使用NFS挂载rootfs6.1 网络环境准备如前所述,搭建好tftf和nfs网络环境,6.2 内核文件准备将光盘中提供的内核镜像和dtb文件服务到tftp工作目录,并给予rwx权限。6.3 文件系统准备在nfs共享目录创建一个rootfs目录用于存放文件系统。将光盘里的busybox文件系统拷贝、解压缩到上面创建的rootfs文件夹中6.4 tftp烧写内核、设备树进入到uboot命令行,设置开发板ip信息setenv ipaddr 192.168.10.101 setenv ethaddr 00:04:9f:04:d2:35 setenv gatewayip 192.168.10.1 setenv netmask 255.255.255.0 setenv serverip 192.168.10.100 saveenv可以ping下开发机地址,看能否ping通设置环境变量 bootcmd 来烧写 tftp 目录下的内核、设备树文件到开发板内存中,setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000' saveenv这样每次开机时将自动烧写kernel镜像和dtb。一般没必要每次都重烧,所以上面的命令也可以手动执行6.5 NFS挂载文件系统设置环境变量bootargs来挂载nfs目录下的rootfs,setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.10.100:/home/user/linux/nfs/rootfs,proto=tcp rw ip=192.168.10.101:192.168.10.100:192.168.10.1:255.255.255.0::eth0:off' saveenv如果使用的时ubuntu18或更高的版本,uboot无法通过nfs启动ubuntu系统内的共享目录。需要在/etc/default/nfs-kernel-server 文件进行修改,改好了保存退出,然后重启一下 nfs 就可以了,或者报错 Loading:*ww ERROR:File lookup fail 的也是按照下面的方法来解决。按照上面截图改完后保存退出,再执行以下指令重启 nfs 服务。sudo service nfs-kernel-server restart(这里遇到个问题,不知道是不是我上传zIamge的时候有问题,换了个出厂系统自带的zImage才可以)经测试OK,
2022年10月22日
720 阅读
0 评论
0 点赞
2022-09-01
Arm64体系结构-- 指令集
暂无简介
2022年09月01日
942 阅读
0 评论
0 点赞
2022-08-30
crash工具的常用命令
暂无简介
2022年08月30日
2,182 阅读
0 评论
0 点赞
2022-08-29
在Android中使用kdump
1. 简介内核核心转储又成为 core dump,在 Unix/Linux 中,将主内存 “Main Memory” 称为核心 core, 这是因为在半导体作为内存材料前,便使用核心 “core” 表示内存。另外核心镜像 “core image” 就是内核作为一个内核线程执行时在内存中的内容。当内核线程发生错误或者收到特定信号而终止执行时,系统在借助某些工具的情况下,可以将核心镜像写入一个文件,以便后期调试问题之用,这个过程就是所谓的核心转储 “core dump”.目前主流的方法获得 core dump 是通过 kexec 工具,该工具当内核核心转储时,其他 CPU 夯住,kexec 工具会在一个预留 CPU 上重启一个精简的内核。当精简内核启动之后,可以通过一定的命令将 core 写入到指定的文件,该文件就是核心转储文件。当获得核心转储文件之后,可以使用 CRASH 等工具进行问题分析。1.当第一内核(生产内核)启动时预留一定大小的内存空间(reserved memory),第一内核启动完成后使用kexec工具将第二内核(捕获内核)和一个简易的initrd rootfs加载到预留的内存中,如下图。2.当生产内核发生crash时,会自动引导捕获内核启动,捕获内核只占用预留的内存,并且在捕获内核中可以通过/proc/vmcore文件获取到发生crash之前的所有内存数据,此时只需要拷贝vmcore到磁盘,便可完成内存转储工作。目前已知Kdump可以覆盖到的情况:系统发生crash系统发生Soft lockup注:由硬件导致的系统hang住,kdump目前没法覆盖到。2. 实现步骤下面主要记录一些流程,不会写的很细2.1 下载并编译kexec-tools在调试过程中,经常需要重启内核以还原现场,进而复现某些问题予以追踪解决. 由于每一次的内核启动,都会伴随着一次的 boot 自检. 但是,对于已经启动过的同一内核,重复的 boot 自检完全没有必要,且造成了资源浪费. 此外,有时候需要使用一个小内核来启动一个大内核. 在这两种需求下,kexec 应运而生,kexec 是一款可以让您重新启动到一个新 Linux 内核的快速重新引导功能部件,不再必须通过固件和引导装载程序阶段,从而跳过序列中最长的部分,大大减少了重启时间. 对企业级系统而言,Kexec 大大减少了重启引起的系统宕机时间. 对内核和系统软件开发者而言,Kexec 帮助您在开发和测试成果时可以迅速重新启动系统,而不必每次都要再经历耗时的固件阶段. 通用 linux 系统中可用通过源码编译的方式安装 kexec-tools 工具,开发者可以参考如下步骤:源码下载wget http://kernel.org/pub/linux/utils/kernel/kexec/kexec-tools.tar.gz编译(以ARM64为例)cd kexec-tools-VERSION ./configure ARCH=arm64 --build=x86_64-unknown-linux-gnu --host=aarch64-linux LDFLAGS=-static make make install2.2 修改当前内核配置正常运行的kernel,需要下面的config配置CONFIG_KEXEC=y CONFIG_SYSFS=y CONFIG_DEBUG_INFO=Y2.3 制作捕获内核和initramfs2.3.1 编译最小文件系统编译busybox,注意编译的时候进入menuconfig配置成静态编译,如何下载编译从网上找个就好了。编译完成后,在busybox根目录下会有一个_install的目录,该目录是编译好的文件系统需要的一些命令的集合。进入_install目录,先创建etc、dev等目录。#mkdir etc #mkdir dev #mkdir mnt #mkdir -p etc/init.d在_install/etc/init.d目录下新创建一个rcS文件,并写入如下内容:#! /bin/sh mkdir -p /proc mkdir -p /tmp mkdir -p /sys mkdir -p /mnt /bin/mount -a echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s将rcS文件的权限改为可执行权限,比如:chmod +x _install/etc/init.d/rcS在_install/etc目录新创建一个fstab文件,并写入以下内容:proc /proc proc defaults 0 0 tmpfs /tmp tmpfs nodev,nosuid 0 0 sysfs /sys sysfs defaults 0 0 debugfs /sys/kernel/debug debugfs defaults 0 0 在_install/etc目录新创建一个inittab文件,并写入以下内容:::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::askfirst:-/bin/sh ::ctrlaltdel:/bin/umount -a -r在_install/dev目录下创建如下设备节点,需要root权限:$cd _install/dev $sudo mknod console c 5 1 $sudo mknod null c 1 32.3.2 编译kernel将上面的_install目录复制到kernel源码目录下面。通过menuconfig,配置initramfs,在initramfs source file中填入_install。制作捕获kernel可以裁掉一些不必要的配置,以减少内存的占用,但最好保留usb driver,以方便拷贝vmcore文件。必须包含下面两个配置:CONFIG_CRASH_DUMP=y CONFIG_PROC_VMCORE=y注意:1.关闭这两个configCONFIG_DMA_CMA=y CONFIG_CMA_SIZE_MBYTES=32 2.确保usb driver是编译进kernel的2.4 在uboot中添加bootargs使用下面的命令或者修改kernel command line,在kernel启动时为捕获内核预留内存,预留太少可能导致捕获内核启动有问题,根据自己的实际情况预留即可。可以通过开机log来判断预留内存是否成功。setenv bootargs $bootargs 'crashkernel=128M' saveenv2.5 加载捕获内核到预留内存使用kexec工具,首先要通过adb push,将编译好的kexec,Image,dtb文件push 到开发板中,然后使用下面的命令 kexec -p /vendor/etc/Image --dtb="/vendor/etc/xxx8421.dtb" --append="console=ttyS0,115200n8 earlycon init=/init rootfstype=ramfs rootwait 1 maxcpus=1 reset_devices"2.6 验证与测试使用 echo c > /proc/sysrq-trigger 命令触发kernel panic,打印完Call trace信息之后会引导捕获kernel启动.如果启动没成功,就根据log来解决就好了。ps:本人遇到的一个问题是,当时捕获内核有个默认的CMA配置,会reserve 32M内存,导致的启动失败。解决办法:在config把这个配置去掉就好了启动后,生成的vmcore文件在/proc/vmcore,将/proc/vmcore拷贝保存到其他地方以供后期分析,也可压缩后再进行拷贝。例如,拷贝到u盘/ # fdisk -l Disk /dev/mmcblk0: 7456 MB, 7818182656 bytes, 15269888 sectors 946 cylinders, 256 heads, 63 sectors/track Units: sectors of 1 * 512 = 512 bytes Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type /dev/mmcblk0p1 0,0,2 1023,255,63 1 4294967295 4294967295 2047G ee EFI GPT Partition 1 has different physical/logical end: phys=(1023,255,63) logical=(266305,4,4) Disk /dev/sda: 29 GB, 30943995904 bytes, 60437492 sectors 3762 cylinders, 255 heads, 63 sectors/track Units: sectors of 1 * 512 = 512 bytes Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size Id Type /dev/sda1 * 0,32,33 1023,254,63 2048 60436479 60434432 28.8G c Win95 FAT32 (LBA) / # mount /dev/sda1 /mnt/ / # cp /proc/vmcore /mnt/ / # umount /mnt/关于分析该文件可以使用crash工具,这个后面还需要继续研究。参考:内核核心转储: Kdump with kexec and crash
2022年08月29日
987 阅读
0 评论
0 点赞
1
...
10
11
12
...
37