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

这篇具有很好参考价值的文章主要介绍了嵌入式Linux驱动开发 02:将驱动程序添加到内核中。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目的

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

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

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

基础说明

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

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

添加到内核中

本文中演示中涉及目录与文件结构组织如下:
嵌入式Linux驱动开发 02:将驱动程序添加到内核中
其中 char_dev 就是本文中要添加的驱动。 user/ 目录用于统一存放自己编写的驱动,如果没有这个需求这一层可以去掉,这样结构上会更简单些,当然推荐还是留着。各目录下的 Kconfig 是一层层应用的, Makefile 同理。

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

cd ~/nuc980-sdk/NUC980-linux-4.4.y/

mkdir -p drivers/user
touch drivers/user/Kconfig
touch drivers/user/Makefile

mkdir -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_DEV
	tristate "char_dev"
	default n
	help
	char_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 中看到相关选项并进行操作了:
嵌入式Linux驱动开发 02:将驱动程序添加到内核中

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

嵌入式Linux驱动开发 02:将驱动程序添加到内核中
最后编译生成的模块默认在模块源码目录下,可以拷贝到开发板中进行测试:

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

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

编译到内核中

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

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

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

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

# 在开发板中挂载启动分区
# mount /dev/mmcblk0p1 /mnt/

# 在虚拟机中拷贝编译生成的内核到开发板
# scp ../image/980uimage root@192.168.31.142:/mnt/

嵌入式Linux驱动开发 02:将驱动程序添加到内核中
开发板重启后可以看到驱动程序在内核启动时自动启动了,可以看到打印的信息以及 /dev/ 下的设备文件。

总结

将驱动程序添加到内核中还是比较简单的,按照内核源码本身的组织方式来进行就行了。更多的示例可以参考内核源码 drivers/ 目录下各个驱动。文章来源地址https://www.toymoban.com/news/detail-408227.html

到了这里,关于嵌入式Linux驱动开发 02:将驱动程序添加到内核中的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式Linux驱动开发——常见框架梳理

    本文主要介绍了Linux驱动开发中一些常用的驱动框架,platform、input、iic、spi等,硬件平台使用的是正点原子的imx6ull开发板。 不管什么框架最后都是要追溯到配置IO的电气属性和复用功能 如果要使用外部中断,设备树节点中还需添加相关信息,什么边沿触发 1:module_init和mod

    2024年02月15日
    浏览(67)
  • 嵌入式Linux驱动开发 04:基于设备树的驱动开发

    前面文章 《嵌入式Linux驱动开发 03:平台(platform)总线驱动模型》 引入了资源和驱动分离的概念,这篇文章将在前面基础上更进一步,引入设备树的概念。 在平台总线驱动模型中资源和驱动已经从逻辑上和代码组织上进行了分离,但每次调整资源还是会涉及到内核,所以现

    2024年02月16日
    浏览(69)
  • 正点原子嵌入式linux驱动开发——Linux 网络设备驱动

    网络驱动是linux里面驱动三巨头之一 ,linux下的网络功能非常强大,嵌入式linux中也常常用到网络功能。前面已经讲过了字符设备驱动和块设备驱动,本章就来学习一下linux里面的 网络设备驱动 。 本次笔记中讨论的都是有线网络! 提起网络,一般想到的硬件就是“网卡”。在

    2024年01月17日
    浏览(71)
  • 【嵌入式Linux驱动】驱动开发调试相关的关系记录

    https://www.processon.com/mindmap/64537772b546c76a2f37bd2f

    2024年02月02日
    浏览(56)
  • 嵌入式Linux驱动开发系列六:Makefile

    Makefile是什么? gcc hello.c -o hello gcc aa.c bb.c cc.c dd.c ... make工具和Makefile make和Makefile是什么关系? make工具:找出修改过的文件,根据依赖关系,找出受影响的相关文件,最后按照规则单独编译这些文件。 Makefile文件:记录依赖关系和编译规则。 必须要学精Makefile吗? 怎么学习Makefi

    2024年02月13日
    浏览(59)
  • 嵌入式Linux驱动开发(LCD屏幕专题)(三)

    1. 硬件相关的操作 LCD驱动程序的核心就是: 分配fb_info 设置fb_info 注册fb_info 硬件相关的设置 硬件相关的设置又可以分为3部分: 引脚设置 时钟设置 LCD控制器设置 2. 在设备树里指定LCD参数 3. 编程 3.1 从设备树获得参数 时序参数、引脚极性等信息,都被保存在一个display_timi

    2024年02月09日
    浏览(62)
  • 嵌入式Linux驱动开发(LCD屏幕专题)(一)

    总的分辨率是 yres*xres。 以下三种方式表示颜色 每个屏幕都有一个内存(framebuffer)如下图,内存中每块数据对用屏幕上的一个像素点,设置好LCD后,只需把颜色数据写入framebuffer即可。 Framebuffer驱动属于字符设备驱动,我们先说字符设备驱动框架如下图: 驱动主设备号 构造

    2024年02月09日
    浏览(61)
  • 嵌入式linux驱动开发篇之设备树

    设备树(Device Tree)是一种用于描述嵌入式系统硬件组件及其连接关系的数据结构。它被广泛用于嵌入式 Linux 系统,尤其是针对使用多种不同架构和平台的嵌入式系统。它是一种与硬件描述相关的中间表示形式,将硬件信息抽象成一种可移植的格式,使得操作系统和引导加载

    2024年02月22日
    浏览(69)
  • 嵌入式Linux驱动开发系列五:Linux系统和HelloWorld

    三个问题 了解Hello World程序的执行过程有什么用? 编译和执行:Hello World程序的执行分为两个主要步骤:编译和执行。编译器将源代码转换为可执行文件,然后计算机执行该文件并输出相应的结果。了解这个过程可以帮助我们理解如何将代码转化为可运行的程序。 语法和语义

    2024年02月13日
    浏览(56)
  • 嵌入式linux驱动开发之移远4G模块EC800驱动移植指南

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

    2024年02月17日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包