电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【野火i.MX6ULL ARM Linux开发板连载】04 调试技巧-printk
分 享
扫描二维码分享
【野火i.MX6ULL ARM Linux开发板连载】04 调试技巧-printk
嵌入式开发
调试
嵌入式与Linux那些事
关注
发布时间: 2021-06-03
丨
阅读: 1915
## 1. printk简介 printk是在**内核中**运行的向控制台输出显示的函数。Linux内核首先在内核空间分配一个**静态缓冲区**,作为显示用的空间,然后调用sprintf,格式化显示字符串,最后调用tty_write向终端进行信息的显示。 printk可以使用在中断上下文, 进程上下文, 持有锁的任何地方。唯一不好的是,必须要等到终端初始化好, printk才能工作。在终端和控制台被初始化之前,printk的所有信息都被缓存在printk的环形缓冲区中。在终端和控制台被初始化之后,所有缓存信息才会被一并输出。 ## 2. EARLY_PRINTK 在终端未被初始化前,printk()还不可使用。此时,可以使用printk()的变体early_printk()函数。early_printk()函数在启动过程初期就具有在终端上打印的能力,功能与prink()类似,区别在于: - 函数名 - 可以更早的输出调试信息。 - 有自己的缓存(一般为512B)。 - 一次性输出到硬件设备,不再以ring buffer的形式保留信息。 - 该函数在一些构架上无法实现,所以,这种办法缺少可移植性。(大多数构架都可以,包括x86和arm)。 ## 3. printk的缺点 **效率低** 做字符拷贝时一次只拷贝一个字节,且去调用console输出可能还产生中断。所以,如果驱动在功能调试完成以后,做性能测试或者发布的时候要尽量减少printk输出,做到仅在出错时输出少量信息。 **缓存小** printk的临时缓存printk_buf只有1K,所以,printk函数只能输出1K的信息到log buffer。 ## 4. printk的消息等级 printk和c库的printf功能基本相同,但是printk多了一个调试等级。 ```c printk(LEVEL "debug string\n"); ``` 可选的debug level有 ```c #define KERN_MERG "0" #define KERN_ALERT "1" #define KERN_CRIT "2" #define KERN_ERR "3" #define KERN_WARNING "4" #define KERN_NOTICE "5" #define KERN_INFO "6" #define KERN_DEBUG "7" #define KERN_DEFAULT "d" #define KERN_CONT "" ``` 例如,printk(KERN_DEBUG “hello”); linux 默认等级一般为 KERN_WARNING, 当信息级别的数值小于控制台的级别时,printk要打印的信息才会在终端打印出来,否则不会显示在终端。 **如何改变默认的等级呢?, 有如下5种方法** 1. include/linux/kernel.h文件中, 修改DEFAULT_CONSOLE_LOGLEVEL 。 2. CONFIG_DEFAULT_MESSAGE_LOGLEVEL。在配置内核时按照make menuconfig-> Kernel Hacking -> Default message log level修改。 3. /proc/sys/kernel/printk 有四个数字:分别代表控制台级别,缺省消息级别,最低控制台级别,缺省控制台级别。可以使用`echo “1 4 1 7” > /proc/sys/kernel/printk`来修改控制台级别。 4. klogctl 系统调用(曾用名syslog)。 5. dmesg 命令 ,如 “# dmesg -n 5” 将等级调到5。 ## 5. printk的格式字符 常见的数据类型对应的printk的格式字符如下: ```c int %d or %x unsigned int %u, %x long %ld, %lx unsigned long %lu, %lx long long %lld, %llx unsigned long long %llu, %llx size_t %zu, %zx ssize_t %zd, %zx ``` ### 5.1 打印指针 使用 %p 来打印指针, 但是在printk里面对此进行了扩展。 - %pS, %pF 打印对应的符号名/函数名和偏移量 - %ps, %pf 打印对应的符号名/函数名 ### 5.2 打印受限的内核地址 使用 %pK来打印受限的内核地址 - %pK 且 /proc/sys/kernel/kptr_restrict = 0 时可直接打印 - %pK 且 /proc/sys/kernel/kptr_restrict = 1 时所有的地址打印为0(除非配置了CAP_SYSLOG)。 - %pK 且 /proc/sys/kernel/kptr_restrict = 2 时所有的地址打印为0。 > kptr_restrict = 0,所有用户都可以读取内核符号地址。 > > kptr_restrict = 1,普通用户无法读取内核符号地址, root用户可以查看。 > > kptr_restrict = 2,所有用户都无法读取内核符号地址。 ### 5.3 打印结构资源 - %pr 打印结构体资源 - %pR 打印结构体资源,包含一个解码标记 打印物理地址 - %pa ### 5.4 打印raw buffer(64字节以下,较大的buffer应使用print_hex_dump) - %*ph ,空格分割,如 00 01 02 - %*phC, 冒号分割,如 00:01:02 - %*phD ,横杠分割,如 00-01-02 - %*phN ,无分割符,如 000102 ### 5.5 打印MAC/FDDI - %pM,冒号分割, 如 00:01:02:03:04:05 - %pMR ,冒号分割,反序, 如 05:04;03;02:01:00 - %pMF, 横杠分割, 如 00-01-02-03-04-05 - %pm ,无分割, 如 000102030405 - %pmR ,无分割, 反序,如 0504030201 ### 5.6 打印ipv4地址 - %pI4, 1.2.3.4的形式 - %pi4, 001.002.003.004的形式 - [hnbl] 附加的’h’ ‘n’ ‘b’ ‘l’ 用于指定参数的字节序是主机字节序还是网络字节序, 或者是大端对齐还是小端对齐。 printk 默认地址是网络字节序的, 并且自动转换为主机字节序再打印, 不需要做额外的字节序转换 ### 5.7 打印ipv6地址 - %pI6 ,0001:0002:0003;0004;0005;0006;0007:0008 的形式 - %pi6, 001002003004005006007008 的形式 - %pI6C, 1;2;3:4;5;6:7:8 的形式 ### 5.8 打印 UUID/GUID - %pUb 小写字母大端序 - %pUB 大写字母大端序 - %pUl 小写字母小端序 - %pUL 大写字母小端序 ### 5.9 打印结构体 - %pV 打印结构的各个成员的名称和值 ## 6. 基于 printk 的宏 每次使用printk都要指定log level太过于麻烦, 内核定义了一组宏。 ```c #ifndef pr_fmt #define pr_fmt(fmt) fmt #endif #define pr_emerg(fmt, ...) \ printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__) #define pr_alert(fmt, ...) \ printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__) #define pr_crit(fmt, ...) \ printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__) #define pr_err(fmt, ...) \ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) #define pr_warning(fmt, ...) \ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) #define pr_warn pr_warning #define pr_notice(fmt, ...) \ printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__) #define pr_info(fmt, ...) \ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) #define pr_cont(fmt, ...) \ printk(KERN_CONT fmt, ##__VA_ARGS__) ``` ## 7. pr_debug, pr_devel 内核还定义了一组打印宏pr_devel在build debug版kernel时才会有效, 但是也可用DEBUG宏来开启。 ```c #ifdef DEBUG #define pr_devel(fmt, ...) \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #else #define pr_devel(fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif ``` 还有一组宏pr_debug, 与pr_devel相比,它还能用于dynamic debug。 ``` #if defined(DEBUG) #define pr_debug(fmt, ...) \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #elif defined(CONFIG_DYNAMIC_DEBUG) /@@* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */ #define pr_debug(fmt, ...) \ dynamic_pr_debug(fmt, ##__VA_ARGS__) #else #define pr_debug(fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif ``` ## 8. 防止printk刷屏 在频繁被执行的地方, 插入 printk 会导致输出大量消息, 刷掉其它的消息, 为了控制printk输出的次数,可以使用printk_ratelimited(...)。 需要 “#include
” printk_ratelimited(…)会保证每一条在5秒钟内输出次数**不大于10次**, 如果需要更精细的频率控制, 可以设置 DEFINE_RATELIMIT_STATE 宏并使用__ratelimit函数。 “/proc/sys/kernel/printk_ratelimit” 定义了消息之间的最小时间间隔, “/proc/sys/kernel/printk_ratelimit_burst”定义了消息的数量,即在 printk_ratelimit 秒内最多打印 printk_ratelimit_burst 条消息。 每一个level的printk还有 “pr_err_ratelimited”, “pr_debug_ratelimited”, “pr_info_ratelimited”这些对应的宏。 如果只需要打印一次的话, 可以使用printk_once(...) 。 ## 9. dev_xxx() 在开发设备驱动的过程中, 在输出消息时, 往往希望能够附带device相关的信息, 例如 device name等,可以使用 dev_printk()。 ```c int dev_printk(const char *level, const struct device *dev, const char *fmt, ...); ``` dev_printk() 能够额外输出设备的 driver name 或者 bus name, device name。如果对这些输出内容不满意的话, 可以基于__dev_printk()自己封装一个消息输出函数, dev_printk()正是基于该函数的封装。 如果希望在 DEBUG 版中才输出消息, 或者是dynamic debug, 则可以使用 dev_dbg()。 ```c #if defined(CONFIG_DYNAMIC_DEBUG) #define dev_dbg(dev, format, ...) \ do { \ dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \ } while (0) #elif defined(DEBUG) #define dev_dbg(dev, format, arg...) \ dev_printk(KERN_DEBUG, dev, format, ##arg) #else #define dev_dbg(dev, format, arg...) \ ({ \ if (0) \ dev_printk(KERN_DEBUG, dev, format, ##arg); \ 0; \ }) #endif ``` 同样, 有限制输出频率的不同消息等级的 dev_xxx() 宏。 ```c #define dev_emerg_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_emerg, dev, fmt, ##__VA_ARGS__) #define dev_alert_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_alert, dev, fmt, ##__VA_ARGS__) #define dev_crit_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_crit, dev, fmt, ##__VA_ARGS__) #define dev_err_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_err, dev, fmt, ##__VA_ARGS__) #define dev_warn_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_warn, dev, fmt, ##__VA_ARGS__) #define dev_notice_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_notice, dev, fmt, ##__VA_ARGS__) #define dev_info_ratelimited(dev, fmt, ...) \ dev_level_ratelimited(dev_info, dev, fmt, ##__VA_ARGS__) ``` DEBUG版 和 dynamic debug 版本也可以限制输出频率, dev_dbg_ratelimited() ```c #if defined(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG) #define dev_dbg_ratelimited(dev, fmt, ...) \ do { \ static DEFINE_RATELIMIT_STATE(_rs, \ DEFAULT_RATELIMIT_INTERVAL, \ DEFAULT_RATELIMIT_BURST); \ DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt); \ if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT) && \ __ratelimit(&_rs)) \ __dynamic_pr_debug(&descriptor, pr_fmt(fmt), \ ##__VA_ARGS__); \ } while (0) #else #define dev_dbg_ratelimited(dev, fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif ``` ## 10. 在用户空间打印内核消息 “/dev/kmsg“ 设备提供了在用户空间输出内核消息的途径,可以使用 mknod -m 600 /dev/kmsg c 1 11 命令来创建一个。 ```c # echo "Hello Kernel-World" > /dev/kmsg ``` 或者还可以使用前置数字来指明消息等级。 ```c # echo "2Writing critical printk messages from userspace" >/dev/kmsg ``` 数字2等于 KERN_CRIT 的等级。 使用 dmesg -u 可以看到所有从用户空间打印的的内核消息。 ## 11. 封装 printk 打造一个专属的打印函数 每次调试的时候都要加行号,加函数名调试,实在是太麻烦了,为什么不自己封装一个专属的 print log 的函数呢? ```c #define log(fmt, arg...) printk(KERN_INFO "[%s][%d] "fmt"", __func__, __LINE__, ##arg); ``` 其中 `_func_ _LINE_` 分别表示当前函数名和行号。 ## 12. 查看printk输出的消息 在用户空间, 可以使用如下几种方式查看 内核消息 - dmesg 命令 - cat /proc/kmsg (不会返回, 会一直等待并输出新的内核消息, 再次之前的消息不会输出) - cat //var/log/syslog dmesg是最常用的方式, 使用dmesg命令时可以加一些控制参数 - -C 清空存放内核消息的环形缓冲区 - -c 列出内核消息然后清空环形缓冲 - -k 仅仅打印从内核中输出的内核消息 - -u 仅仅打印从用户空间打印的内核消息 - -n 调整将被打印到控制台的消息的等级 - -s 设置环形缓冲区的大小 ## 13. 测试 本次测试是在野火第八节代码的基础上修改的。完整代码和测试结果如下所示。 rgb.led.c ```c #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define log(fmt, arg...) printk(KERN_INFO "[%s][%d] "fmt"\r\n", __func__, __LINE__, ##arg); /@@*------------------字符设备内容----------------------*/ #define DEV_NAME "rgb_led" #define DEV_CNT (1) /@@*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/ struct led_resource { struct device_node *device_node; //rgb_led_red的设备树节点 void __iomem *virtual_CCM_CCGR; void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD; void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD; void __iomem *virtual_DR; void __iomem *virtual_GDIR; }; static dev_t led_devno; //定义字符设备的设备号 static struct cdev led_chr_dev; //定义字符设备结构体chr_dev struct class *class_led; //保存创建的类 struct device *device; // 保存创建的设备 struct device_node *rgb_led_device_node; //rgb_led的设备树节点结构体 /@@*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/ struct led_resource led_red; struct led_resource led_green; struct led_resource led_blue; /@@*字符设备操作函数集,open函数*/ static int led_chr_dev_open(struct inode *inode, struct file *filp) { log("\n open form driver \n"); return 0; } /@@*字符设备操作函数集,write函数*/ static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { unsigned int register_data = 0; //暂存读取得到的寄存器数据 unsigned char write_data; //用于保存接收到的数据 int error = copy_from_user(&write_data, buf, cnt); if (error < 0) { return -1; } /@@*设置 GPIO1_04 输出电平*/ if (write_data & 0x04) { register_data = readl(led_red.virtual_DR); register_data &= ~(0x01 << 4); writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮 } else { register_data = readl(led_red.virtual_DR); register_data |= (0x01 << 4); writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭 } /@@*设置 GPIO4_20 输出电平*/ if (write_data & 0x02) { register_data = readl(led_green.virtual_DR); register_data &= ~(0x01 << 20); writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出低电平,绿灯亮 } else { register_data = readl(led_green.virtual_DR); register_data |= (0x01 << 20); writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出高电平,绿灯灭 } /@@*设置 GPIO4_19 输出电平*/ if (write_data & 0x01) { register_data = readl(led_blue.virtual_DR); register_data &= ~(0x01 << 19); writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出低电平,蓝灯亮 } else { register_data = readl(led_blue.virtual_DR); register_data |= (0x01 << 19); writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出高电平,蓝灯灭 } return 0; } /@@*字符设备操作函数集*/ static struct file_operations led_chr_dev_fops = { .owner = THIS_MODULE, .open = led_chr_dev_open, .write = led_chr_dev_write, }; /@@*----------------平台驱动函数集-----------------*/ static int led_probe(struct platform_device *pdv) { int ret = -1; //保存错误状态码 int i = 0; unsigned int register_data = 0; log(KERN_EMERG "\t match successed \n"); /@@*获取rgb_led的设备树节点*/ rgb_led_device_node = of_find_node_by_path("/rgb_led"); if (rgb_led_device_node == NULL) { log(KERN_ERR "\t get rgb_led failed! \n"); return -1; } /@@*获取rgb_led节点的红灯子节点*/ led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red"); if (led_red.device_node == NULL) { log(KERN_ERR "\n get rgb_led_red_device_node failed ! \n"); return -1; } /@@*获取 reg 属性并转化为虚拟地址*/ led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0); led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1); led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2); led_red.virtual_DR = of_iomap(led_red.device_node, 3); led_red.virtual_GDIR = of_iomap(led_red.device_node, 4); /@@*初始化红灯*/ register_data = readl(led_red.virtual_CCM_CCGR); register_data |= (0x03 << 26); writel(register_data, led_red.virtual_CCM_CCGR); //开启时钟 register_data = readl(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); register_data &= ~(0xf << 0); register_data |= (0x05 << 0); writel(register_data, led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能 register_data = readl(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); register_data = (0x10B0); writel(register_data, led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性 register_data = readl(led_red.virtual_GDIR); register_data |= (0x01 << 4); writel(register_data, led_red.virtual_GDIR); //设置GPIO1_04 为输出模式 register_data = readl(led_red.virtual_DR); register_data |= (0x01 << 4); writel(register_data, led_red.virtual_DR); //设置 GPIO1_04 默认输出高电平 /@@*获取rgb_led节点的绿灯子节点*/ led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green"); if (led_green.device_node == NULL) { log(KERN_ERR "\n get rgb_led_green_device_node failed ! \n"); return -1; } /@@*获取 reg 属性并转化为虚拟地址*/ led_green.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0); led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1); led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2); led_green.virtual_DR = of_iomap(led_green.device_node, 3); led_green.virtual_GDIR = of_iomap(led_green.device_node, 4); /@@*初始化绿灯*/ register_data = readl(led_green.virtual_CCM_CCGR); register_data |= (0x03 << 12); writel(register_data, led_green.virtual_CCM_CCGR); //开启时钟 register_data = readl(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); register_data &= ~(0xf << 0); register_data |= (0x05 << 0); writel(register_data, led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能 register_data = readl(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); register_data = (0x10B0); writel(register_data, led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性 register_data = readl(led_green.virtual_GDIR); register_data |= (0x01 << 20); writel(register_data, led_green.virtual_GDIR); //设置GPIO4_IO20 为输出模式 register_data = readl(led_green.virtual_DR); register_data |= (0x01 << 20); writel(register_data, led_green.virtual_DR); //设置 GPIO4_IO20 默认输出高电平 /@@*获取rgb_led节点的蓝灯子节点*/ led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue"); if (led_blue.device_node == NULL) { log(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n"); return -1; } /@@*获取 reg 属性并转化为虚拟地址*/ led_blue.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0); led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1); led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2); led_blue.virtual_DR = of_iomap(led_blue.device_node, 3); led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4); /@@*初始化蓝灯*/ register_data = readl(led_blue.virtual_CCM_CCGR); register_data |= (0x03 << 12); writel(register_data, led_blue.virtual_CCM_CCGR); //开启时钟 register_data = readl(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); register_data &= ~(0xf << 0); register_data |= (0x05 << 0); writel(register_data, led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能 register_data = readl(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); register_data = (0x10B0); writel(register_data, led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性 register_data = readl(led_blue.virtual_GDIR); register_data |= (0x01 << 19); writel(register_data, led_blue.virtual_GDIR); //设置GPIO4_IO19 为输出模式 register_data = readl(led_blue.virtual_DR); register_data |= (0x01 << 19); writel(register_data, led_blue.virtual_DR); //设置 GPIO4_IO19 默认输出高电平 /@@*---------------------注册 字符设备部分-----------------*/ //第一步 //采用动态分配的方式,获取设备编号,次设备号为0, //设备名称为rgb-leds,可通过命令cat /proc/devices查看 //DEV_CNT为1,当前只申请一个设备编号 ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME); if (ret < 0) { log("fail to alloc led_devno\n"); goto alloc_err; } //第二步 //关联字符设备结构体cdev与文件操作结构体file_operations led_chr_dev.owner = THIS_MODULE; cdev_init(&led_chr_dev, &led_chr_dev_fops); //第三步 //添加设备至cdev_map散列表中 ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT); if (ret < 0) { log("fail to add cdev\n"); goto add_err; } //第四步 /@@*创建类 */ class_led = class_create(THIS_MODULE, DEV_NAME); /@@*创建设备*/ device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME); /@@*测试printk*/ log("printk pF test:%pF\r\n",led_chr_dev_open); log("printk pR test:%pR\r\n",led_red); for( i = 0; i < 100; i++) { printk_ratelimited("\t>>> zhongyi <<<\n"); printk_once("\t>>> printk_once <<<\n"); } return 0; add_err: //添加设备失败时,需要注销设备号 unregister_chrdev_region(led_devno, DEV_CNT); log("\n error! \n"); alloc_err: return -1; } static const struct of_device_id rgb_led[] = { {.compatible = "fire,rgb_led"}, {/@@* sentinel */}}; /@@*定义平台设备结构体*/ struct platform_driver led_platform_driver = { .probe = led_probe, .driver = { .name = "rgb-leds-platform", .owner = THIS_MODULE, .of_match_table = rgb_led, }}; /@@* *驱动初始化函数 */ static int __init led_platform_driver_init(void) { int DriverState; DriverState = platform_driver_register(&led_platform_driver); log(KERN_EMERG "\tDriverState is %d\n", DriverState); return 0; } /@@* *驱动注销函数 */ static void __exit led_platform_driver_exit(void) { /@@*取消物理地址映射到虚拟地址*/ iounmap(led_green.virtual_CCM_CCGR); iounmap(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); iounmap(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); iounmap(led_green.virtual_DR); iounmap(led_green.virtual_GDIR); iounmap(led_red.virtual_CCM_CCGR); iounmap(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); iounmap(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); iounmap(led_red.virtual_DR); iounmap(led_red.virtual_GDIR); iounmap(led_blue.virtual_CCM_CCGR); iounmap(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); iounmap(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); iounmap(led_blue.virtual_DR); iounmap(led_blue.virtual_GDIR); /@@*删除设备*/ device_destroy(class_led, led_devno); //清除设备 class_destroy(class_led); //清除类 cdev_del(&led_chr_dev); //清除设备号 unregister_chrdev_region(led_devno, DEV_CNT); //取消注册字符设备 /@@*注销字符设备*/ platform_driver_unregister(&led_platform_driver); log(KERN_EMERG "HELLO WORLD exit!\n"); } module_init(led_platform_driver_init); module_exit(led_platform_driver_exit); MODULE_LICENSE("GPL"); /@@**/ ``` test_app.c ```c #include
#include
#include
#include
int main(int argc, char *argv[]) { printf("led_tiny test\n"); /@@*判断输入的命令是否合法*/ if(argc != 2) { printf(" commend error ! \n"); return -1; } /@@*打开文件*/ int fd = open("/dev/rgb_led", O_RDWR); if(fd < 0) { printf("open file : %s failed !\n", argv[0]); return -1; } unsigned char commend = atoi(argv[1]); //将受到的命令值转化为数字; /@@*写入命令*/ int error = write(fd,&commend,sizeof(commend)); if(error < 0) { printf("write file error! \n"); close(fd); /@@*判断是否关闭成功*/ } /@@*关闭文件*/ error = close(fd); if(error < 0) { printf("close file error! \n"); } return 0; } ``` makefile ```c KERNEL_DIR=../ebf-buster-linux/build_image/build ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := rgb_led.o out = test_app all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules $(CROSS_COMPILE)gcc -o $(out) test_app.c .PHONY:clean clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean rm $(out) ``` dts ```c /@@* * Copyright (C) 2016 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ /dts-v1/; #include
#include "imx6ull.dtsi" / { model = "Seeed i.MX6 ULL NPi Board"; compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; aliases { pwm0 = &pwm1; pwm1 = &pwm2; pwm2 = &pwm3; pwm3 = &pwm4; }; chosen { stdout-path = &uart1; }; memory { reg = <0x80000000 0x20000000>; }; reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; linux,cma { compatible = "shared-dma-pool"; reusable; size = <0x14000000>; linux,cma-default; }; }; regulators { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <0>; reg_sd1_vmmc: regulator@1 { compatible = "regulator-fixed"; regulator-name = "VSD_3V3"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; gpio = <&gpio1 9 GPIO_ACTIVE_HIGH>; off-on-delay = <20000>; enable-active-high; }; reg_gpio_dvfs: regulator-gpio { compatible = "regulator-gpio"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_dvfs>; regulator-min-microvolt = <1300000>; regulator-max-microvolt = <1400000>; regulator-name = "gpio_dvfs"; regulator-type = "voltage"; gpios = <&gpio5 3 GPIO_ACTIVE_HIGH>; states = <1300000 0x1 1400000 0x0>; }; }; /@@* External sound card */ sound: sound { status = "disabled"; }; /@@* *CCM_CCGR1 0x020C406C *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 0x020E006C *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 0x020E02F8 *GPIO1_GD 0x0209C000 *GPIO1_GDIR 0x0209C004 */ /@@* *CCM_CCGR3 0x020C4074 *IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC 0x020E01E0 *IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC 0x020E046C *GPIO4_GD 0x020A8000 *GPIO4_GDIR 0x020A8004 */ /@@* *CCM_CCGR3 0x020C4074 *IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC 0x020E01DC *IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC 0x020E0468 *GPIO4_GD 0x020A8000 *GPIO4_GDIR 0x020A8004 */ /@@*添加led节点*/ rgb_led{ #address-cells = <1>; #size-cells = <1>; compatible = "fire,rgb_led"; /@@*红灯节点*/ ranges; rgb_led_red@0x020C406C{ compatible = "fire,rgb_led_red"; reg = <0x020C406C 0x00000004 0x020E006C 0x00000004 0x020E02F8 0x00000004 0x0209C000 0x00000004 0x0209C004 0x00000004>; status = "okay"; }; /@@*绿灯节点*/ rgb_led_green@0x020C4074{ compatible = "fire,rgb_led_green"; reg = <0x020C4074 0x00000004 0x020E01E0 0x00000004 0x020E046C 0x00000004 0x020A8000 0x00000004 0x020A8004 0x00000004>; status = "okay"; }; /@@*蓝灯节点*/ rgb_led_blue@0x020C4074{ compatible = "fire,rgb_led_blue"; reg = <0x020C4074 0x00000004 0x020E01DC 0x00000004 0x020E0468 0x00000004 0x020A8000 0x00000004 0x020A8004 0x00000004>; status = "okay"; }; }; }; &cpu0 { dc-supply = <®_gpio_dvfs>; clock-frequency = <800000000>; }; &clks { assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>; assigned-clock-rates = <786432000>; }; &fec1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; status = "okay"; }; &fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2>; phy-mode = "rmii"; phy-handle = <ðphy1>; status = "okay"; mdio { #address-cells = <1>; #size-cells = <0>; ethphy0: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <2>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET_REF>; clock-names = "rmii-ref"; }; ethphy1: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <1>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET2_REF>; clock-names = "rmii-ref"; }; }; }; &gpc { fsl,cpu_pupscr_sw2iso = <0xf>; fsl,cpu_pupscr_sw = <0x0>; fsl,cpu_pdnscr_iso2sw = <0x1>; fsl,cpu_pdnscr_iso = <0x1>; fsl,ldo-bypass = <0>; /@@* DCDC, ldo-enable */ }; &iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; pinctrl_rgb_led:rgb_led{ fsl,pins = < MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1 /@@* remote control add by pan 20200307*/ MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1 /@@* remote control add by pan 20200307*/ MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1 /@@* remote control add by pan 20200307*/ >; }; pinctrl_gpmi_nand: gpmi-nand { fsl,pins = < MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1 MX6UL_PAD_NAND_ALE__RAWNAND_ALE 0xb0b1 MX6UL_PAD_NAND_WP_B__RAWNAND_WP_B 0xb0b1 MX6UL_PAD_NAND_READY_B__RAWNAND_READY_B 0xb000 MX6UL_PAD_NAND_CE0_B__RAWNAND_CE0_B 0xb0b1 MX6UL_PAD_NAND_CE1_B__RAWNAND_CE1_B 0xb0b1 MX6UL_PAD_NAND_RE_B__RAWNAND_RE_B 0xb0b1 MX6UL_PAD_NAND_WE_B__RAWNAND_WE_B 0xb0b1 MX6UL_PAD_NAND_DATA00__RAWNAND_DATA00 0xb0b1 MX6UL_PAD_NAND_DATA01__RAWNAND_DATA01 0xb0b1 MX6UL_PAD_NAND_DATA02__RAWNAND_DATA02 0xb0b1 MX6UL_PAD_NAND_DATA03__RAWNAND_DATA03 0xb0b1 MX6UL_PAD_NAND_DATA04__RAWNAND_DATA04 0xb0b1 MX6UL_PAD_NAND_DATA05__RAWNAND_DATA05 0xb0b1 MX6UL_PAD_NAND_DATA06__RAWNAND_DATA06 0xb0b1 MX6UL_PAD_NAND_DATA07__RAWNAND_DATA07 0xb0b1 >; }; pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /@@* SD1 CD */ MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /@@* SD1 VSELECT */ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /@@* SD1 RESET */ >; }; pinctrl_enet1: enet1grp { fsl,pins = < MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031 >; }; pinctrl_enet2: enet2grp { fsl,pins = < MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 >; }; pinctrl_uart1: uart1grp { fsl,pins = < MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1 MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1 >; }; pinctrl_usb_otg1_id: usbotg1idgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x17059 >; }; pinctrl_dvfs: dvfsgrp { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x79 >; }; pinctrl_usdhc1: usdhc1grp { fsl,pins = < MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x17059 MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x10071 MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x17059 MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x17059 MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x17059 MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x17059 >; }; pinctrl_usdhc1_100mhz: usdhc1grp100mhz { fsl,pins = < MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170b9 MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100b9 MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170b9 MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170b9 MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170b9 MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170b9 >; }; pinctrl_usdhc1_200mhz: usdhc1grp200mhz { fsl,pins = < MX6UL_PAD_SD1_CMD__USDHC1_CMD 0x170f9 MX6UL_PAD_SD1_CLK__USDHC1_CLK 0x100f9 MX6UL_PAD_SD1_DATA0__USDHC1_DATA0 0x170f9 MX6UL_PAD_SD1_DATA1__USDHC1_DATA1 0x170f9 MX6UL_PAD_SD1_DATA2__USDHC1_DATA2 0x170f9 MX6UL_PAD_SD1_DATA3__USDHC1_DATA3 0x170f9 >; }; pinctrl_usdhc2: usdhc2grp { fsl,pins = < MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x10069 MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x17059 MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x17059 MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x17059 MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x17059 MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x17059 >; }; pinctrl_usdhc2_8bit: usdhc2grp_8bit { fsl,pins = < MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x10069 MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x17059 MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x17059 MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x17059 MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x17059 MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x17059 MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x17059 MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x17059 MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x17059 MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x17059 >; }; pinctrl_usdhc2_8bit_100mhz: usdhc2grp_8bit_100mhz { fsl,pins = < MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x100b9 MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x170b9 MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x170b9 MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x170b9 MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x170b9 MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x170b9 MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x170b9 MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x170b9 MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x170b9 MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x170b9 >; }; pinctrl_usdhc2_8bit_200mhz: usdhc2grp_8bit_200mhz { fsl,pins = < MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x100f9 MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x170f9 MX6UL_PAD_NAND_DATA00__USDHC2_DATA0 0x170f9 MX6UL_PAD_NAND_DATA01__USDHC2_DATA1 0x170f9 MX6UL_PAD_NAND_DATA02__USDHC2_DATA2 0x170f9 MX6UL_PAD_NAND_DATA03__USDHC2_DATA3 0x170f9 MX6UL_PAD_NAND_DATA04__USDHC2_DATA4 0x170f9 MX6UL_PAD_NAND_DATA05__USDHC2_DATA5 0x170f9 MX6UL_PAD_NAND_DATA06__USDHC2_DATA6 0x170f9 MX6UL_PAD_NAND_DATA07__USDHC2_DATA7 0x170f9 >; }; }; &iomuxc_snvs { pinctrl-names = "default_snvs"; pinctrl-0 = <&pinctrl_hog_2>; pinctrl_hog_2: hoggrp-2 { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER0__GPIO5_IO00 0x80000000 >; }; }; &snvs_pwrkey { status = "disabled"; }; &pxp { status = "okay"; }; &uart1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart1>; status = "okay"; }; &usbotg1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usb_otg1_id>; dr_mode = "otg"; srp-disable; hnp-disable; adp-disable; status = "okay"; }; &usbotg2 { dr_mode = "host"; disable-over-current; status = "okay"; }; &usbphy1 { fsl,tx-d-cal = <106>; }; &usbphy2 { fsl,tx-d-cal = <106>; }; &usdhc1 { pinctrl-names = "default", "state_100mhz", "state_200mhz"; pinctrl-0 = <&pinctrl_usdhc1>; pinctrl-1 = <&pinctrl_usdhc1_100mhz>; pinctrl-2 = <&pinctrl_usdhc1_200mhz>; no-1-8-v; /@@*cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;*/ keep-power-in-suspend; /@@*non-removable;*/ enable-sdio-wakeup; vmmc-supply = <®_sd1_vmmc>; status = "okay"; }; &usdhc2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usdhc2_8bit>; non-removable; status = "okay"; }; &gpmi { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_gpmi_nand>; status = "okay"; //nand-on-flash-bbt; partition@0 { label = "uboot"; reg = <0x00000000 0x00800000>; }; partition@1 { label = "rootfs"; reg = <0x00800000 0x1F800000>; }; }; ``` ![](https://gitee.com/dongxingbo/Picture/raw/master//Wechat/Article/2021/%E4%BA%94%E6%9C%88//20210524194709.png) 扫描下方二维码关注我的公众号【**嵌入式与Linux那些事**】 回复【交流群】,扫码进入技术交流群,一起学习,一起进步! 回复【电子书】,领取10G电子书! 回复【网盘】,领取2000G学习资料! 回复【简历】领取简历模版 ![](https://gitee.com/dongxingbo/Picture/raw/master/Wechat/%E5%8A%A8%E6%80%81%E5%BC%95%E5%AF%BC%E5%85%B3%E6%B3%A8%E5%85%AC%E4%BC%97%E5%8F%B7%E5%8F%B7.gif) > 本文参考 > > http://doc.embedfire.com/linux/imx6/base/zh/latest/linux_driver/device_tree_rgb_led.html > > http://lxr.linux.no/#linux+v2.6.34/lib/vsprintf.c#L930 > > http://www.embedded-bits.co.uk/2010/printk-format-specifiers/ > > https://www.kernel.org/doc/Documentation/printk-formats.txt > > http://tools.ietf.org/html/rfc5952 > > https://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07 > >
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
嵌入式与Linux那些事
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交