文章目录

  • 目的
  • 基础说明
  • 添加到内核中
    • Kconfig
    • Makefile
    • 驱动程序
  • 编译与测试
    • 模块方式
    • 编译到内核中
  • 总结

目的

在上一篇文章 《嵌入式Linux驱动开发 01:基础开发与使用》 中我们已经实现了最基础的驱动功能。在那篇文章中我们的驱动代码是独立于内核代码存放的,并且我们的驱动编译后也是一个独立的模块。在实际使用中将驱动代码放在内核代码中,并将驱动编译到内核中也是比较常见的选择,这篇文章将此进行介绍。

这篇文章中内容均在下面的开发板上进行测试:
《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》

这篇文章主要是在下面文章基础上进行的:
《新唐NUC980使用记录:访问以太网(LAN8720A) & 启用SSH》

基础说明

将驱动程序添加到内核中可以分为两层含义来理解:

  1. 将驱动程序源码等放到 Linux Kernel 源码目录下
    Linux Kernel 源码中通常将驱动放到 drivers/ 下,本文中也将驱动源码放到这个目录下;
  2. 在 Linux Kernel 配置管理工具中统一管理与编译
    这条主要指可以在 menuconfig 中进行配置管理来选择编译(这里先不讨论使用设备树的情况);
    menuconfig 界面中各个菜单和选项都是由 Kconfig 文件定义的,所以我们需要修改和编写相关文件;
    menuconfig 中配置最终改变的是 make 时 Makefile 文件中各个变量,我们的自己的驱动也需要 Makefile 文件来指定编译规则,并结合 Kconfig 文件中定义的变量来控制编译过程;

添加到内核中

本文中演示中涉及目录与文件结构组织如下:

其中 char_dev 就是本文中要添加的驱动。 user/ 目录用于统一存放自己编写的驱动,如果没有这个需求这一层可以去掉,这样结构上会更简单些,当然推荐还是留着。各目录下的 Kconfig 是一层层应用的, Makefile 同理。

进入源码目录并建立相关目录和文件:

cd ~/nuc980-sdk/NUC980-linux-4.4.y/mkdir -p drivers/user
touch drivers/user/Kconfig
touch drivers/user/Makefilemkdir -p drivers/user/char_dev
touch drivers/user/char_dev/char_dev.c
touch drivers/user/char_dev/Kconfig
touch drivers/user/char_dev/Makefile

Kconfig

首先修改drivers目录下Kconfig文件:

gedit drivers/Kconfig

在其中添加下面一行,用来引用drivers/user目录下的Kconfig文件:

source "drivers/user/Kconfig"

接着编辑drivers/user目录下的Kconfig文件:

gedit drivers/user/Kconfig

写入下面内容,用来引用drivers/user/char_dev目录下的Kconfig文件:

menu "User drivers"source "drivers/user/char_dev/Kconfig"endmenu

最后编辑drivers/user/char_dev目录下的Kconfig文件:

gedit drivers/user/char_dev/Kconfig

写入下面内容:

config USER_CHAR_DEVtristate "char_dev"default nhelpchar_dev driver test.

上面内容中 config USER_CHAR_DEV 表示设置一个可配置的变量,名称为 USER_CHAR_DEV (保存后实际的变量名会在头部添加 CONFIG_CONFIG_USER_CHAR_DEV ,这个变量可以在Makefile中使用)。 tristate 表示该变量可取值为 n/y/m ,后面的字符串为该条目在 menuconfig 中显示的文本。 default n 表示该变量默认值。 help 表示其下面的内容是可在 menuconfig 中查看帮助信息。

经过上面处理后就可以在 menuconfig 中看到相关选项并进行操作了:

Makefile

首先修改drivers目录下Makefile文件:

gedit drivers/Makefile

在其中添加下面一行,这样编译时会进入drivers/user目录下:

obj-y                += user/

接着编辑drivers/user目录下的Makefile文件:

gedit drivers/user/Makefile

写入下面内容,这样编译时会进入drivers/user/char_dev目录下:

obj-y                += char_dev/

最后编辑drivers/user/char_dev目录下的Makefile文件:

gedit drivers/user/char_dev/Makefile

写入下面内容:

obj-$(CONFIG_USER_CHAR_DEV) += char_dev.o

上面就是最终编译驱动程序过程了,这里的 CONFIG_USER_CHAR_DEV 变量就是由前面配置来产生的。根据变量的值,其最终可能产生 obj-nobj-yobj-m 几个结果,这几个是 Linux Kernel 源码总的Makefile中定义的变量,添加到 obj-y 的内容会编译到内核中,添加到 obj-m 的内容会编译成单独的模块。

驱动程序

最后编辑下进行测试用的驱动程序:

gedit drivers/user/char_dev/char_dev.c

直接使用上一篇文章的程序即可:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>static int major = 0;
static const char *char_dev_name = "char_dev";
static struct class *char_dev_class;
static struct device *char_dev_device;static char dev_buf[4096];#define MIN(a, b) ((a) < (b) ? (a) : (b))static int char_dev_open(struct inode *node, struct file *file)
{return 0;
}static int char_dev_close(struct inode *node, struct file *file)
{return 0;
}static ssize_t char_dev_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{int ret;ret = copy_to_user(buf, dev_buf, MIN(size, 4096)); // 从内核空间拷贝数据到用户空间return ret;
}static ssize_t char_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int ret;ret = copy_from_user(dev_buf, buf, MIN(size, 4096)); // 从用户空间拷贝数据到内核空间return ret;
}static const struct file_operations char_dev_fops = {.owner = THIS_MODULE,.open = char_dev_open,.release = char_dev_close,.read = char_dev_read,.write = char_dev_write,
};static int __init char_dev_init(void)
{printk("modlog: func %s, line %d.\n", __FUNCTION__, __LINE__);major = register_chrdev(0, char_dev_name, &char_dev_fops); // 注册字符设备,第一个参数0表示让内核自动分配主设备号char_dev_class = class_create(THIS_MODULE, "char_dev_class"); // if (IS_ERR(char_dev_class)){unregister_chrdev(major, char_dev_name);return -1;}char_dev_device = device_create(char_dev_class, NULL, MKDEV(major, 0), NULL, char_dev_name); // 创建设备节点创建设备节点,成功后就会出现/dev/char_dev_name的设备文件if (IS_ERR(char_dev_device)){device_destroy(char_dev_class, MKDEV(major, 0));unregister_chrdev(major, char_dev_name);return -1;}return 0;
}static void __exit char_dev_exit(void)
{printk("modlog: func %s, line %d.\n", __FUNCTION__, __LINE__);device_destroy(char_dev_class, MKDEV(major, 0)); // 销毁设备节点,销毁后/dev/下设备节点文件就会删除class_destroy(char_dev_class);unregister_chrdev(major, char_dev_name); // 注销字符设备
}module_init(char_dev_init); // 模块入口
module_exit(char_dev_exit); // 模块出口MODULE_LICENSE("GPL"); // 模块许可

这个驱动程序在安装和卸载时打印了一些消息,可以通过此判断驱动程序是否工作。

编译与测试

模块方式

menuconfig 将控制驱动的选项选择为模块后进行编译:

# make menuconfig
export PATH=$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin
# 可以使用make整体编译或使用make modules编译单独模块
# make
make modules


最后编译生成的模块默认在模块源码目录下,可以拷贝到开发板中进行测试:

# scp drivers/user/char_dev/char_dev.ko root@192.168.31.142:/root/

编译到内核中

menuconfig 将控制驱动的选项选择为y后进行编译:

# make menuconfig
# export PATH=$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin
make uImage

编译完成后拷贝内核文件到开发板boot分区:

# 在开发板中挂载启动分区
# mount /dev/mmcblk0p1 /mnt/# 在虚拟机中拷贝编译生成的内核到开发板
# scp ../image/980uimage root@192.168.31.142:/mnt/


开发板重启后可以看到驱动程序在内核启动时自动启动了,可以看到打印的信息以及 /dev/ 下的设备文件。

总结

将驱动程序添加到内核中还是比较简单的,按照内核源码本身的组织方式来进行就行了。更多的示例可以参考内核源码 drivers/ 目录下各个驱动。

嵌入式Linux驱动开发 02:将驱动程序添加到内核中相关推荐

  1. 嵌入式Linux驱动开发【学习小结】

    文章目录 前言 一.嵌入式Linux驱动程序和单片机裸奔有啥区别? 二.为什么需要嵌入式Linux驱动开发 三.驱动程序框架大致演变过程 总结 前言 随着去嵌入式设备资源不断丰富,主频不断升高,搭载操 ...

  2. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树

    文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...

  3. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型

    文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...

  4. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离

    文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...

  5. 嵌入式 Linux 驱动开发你想知道的都在这

    最近看到公众号上写的一篇文章,关于嵌入式 Linux 驱动开发的方方面面,感觉提供不错,此处特意贴出来供大家参考借鉴. 1.嵌入式驱动开发到底学什么 嵌入式大体分为以下四个方向: 嵌入式硬件开发:熟悉 ...

  6. 使用IDE(vs code)进行嵌入式linux驱动开发

    目录 背景 系统版本 vs code的安装和使用 1.安装 2.新建工程 3.设置vs code工程的头文件查找路径及编译器路径 4.Intelli Sense Engine Fallback设置为E ...

  7. 嵌入式linux驱动开发之移远4G模块EC800驱动移植指南

    回顾下移远4G模块移植过程, 还是蛮简单的.一通百通,无论是其他4G模块都是一样的.这里记录下过程,分享给有需要的人.环境使用正点原子的imax6ul开发板,板子默认支持中兴和移远EC20的驱动,这里 ...

  8. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

  9. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

    文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...

最新文章

  1. java有any类型吗_Java开发网 - 一个关于CORBA中any类型的问题
  2. 新闻文本内容知识图谱表示项目
  3. 3 | Spatial-based GNN/convolution模型之 NN4G
  4. linux suid 脚本,Linux使用suid vim.basic文件实现提权
  5. 基于spring多数据源动态调用及其事务处理
  6. POJ NOI MATH-7832 最接近的分数
  7. Linux多线程同步之相互排斥量和条件变量
  8. CPU 100% 异常排查实践与总结
  9. 2010 模板下载 罗斯文_利用模板建立Access 2010数据库的方法
  10. C# PDF转图片(JPG)
  11. Git遇到的问题:This is not a valid source path/URL
  12. ubuntu 18.04 review board安装及svn设置pre-commit 和post-cmmit
  13. 味美多网址导航php,味多美网址导航源码程序按来路自动显示 2010.0329
  14. 你不知道的“虚假需求”
  15. python笔记:太困了,读取并显示按行业分类的股票数据提提神
  16. ldc服务器销售,ldc云服务器源码
  17. python进行批量图片文字识别
  18. iOS wkWebview调整html文字大小以及文字两端对齐
  19. 全网第一 | Flink学习面试灵魂40问答案,文末有福利!
  20. 电脑使用技巧 (C盘空间扩容)

热门文章

  1. Oracle-19-like运算符转义操作符
  2. 2019最新Android算法相关面试大全,请查收
  3. 【excel】根据单元格公式随机生成人名、及按概率生成不同值的测试数据
  4. matlab三维作图教程
  5. opencv 去除孤立点以及findContours()和connectedComponentsWithStats()详解
  6. 【AI产品】拥有它,一秒成为艺术家,你还在等什么?
  7. android根据IP地址自动显示天气(之一)—自动获取IP地址
  8. 重磅!《命令与征服》和《红色警戒》源码公布了!
  9. LDAP和AD的关系 LDAP和Active Directory的区别
  10. Simple-SR(AAAI-2022):Best-Buddy GANs for Highly Detailed Image Super-Resolution论文浅析