本文以建立一个能够读写寄存器数值的内核驱动模块来介绍如何给OpenWr添加内核驱动模块。
package/kernel/
路径下新建一个文件夹,文件夹以驱动名称命名。例:package/kernel/drv_regopt
。src
文件夹用于存放驱动源码,例:package/kernel/drv_regopt/src
在package/kernel/drv_regopt
文件夹下新建一个Makefile
文件,内容参考如下
# 引入rules.mk和kernel.mk文件
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
# 定义变量
PKG_NAME:=drv_regopt
PKG_RELEASE:=1
# 引入package.mk文件
include $(INCLUDE_DIR)/package.mk
# 定义内核包drv_regopt
define KernelPackage/drv_regopt
SUBMENU:=Other modules # 加入到“Other modules”的子菜单中
DEPENDS:=@!LINUX_3_3 # 不依赖于LINUX_3_3
TITLE:=option the register # 内核模块的名字
FILES:=$(PKG_BUILD_DIR)/drv_regopt.ko # 编译生成的内核模块路径
AUTOLOAD:=$(call AutoLoad,30,drv_regopt,1) # 该模块在自动加载时的设置
# KCONFIG:=
endef
# 定义内核包drv_regopt的描述信息
define KernelPackage/drv_regopt/description
This is a replacement for the following in-kernel drivers:
1)
2)
Instead of generating input events (like in-kernel drivers do) it generates
uevent-s and broadcasts them. This allows disabling input subsystem which is
an overkill for OpenWrt simple needs.
endef
# 定义编译选项
MAKE_OPTS:= \
ARCH="$(LINUX_KARCH)" \
CROSS_COMPILE="$(TARGET_CROSS)" \
SUBDIRS="$(PKG_BUILD_DIR)"
# 定义Build/Prepare规则
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR) # 创建编译目录
$(CP) ./src/* $(PKG_BUILD_DIR)/ # 复制源代码到编译目录
endef
# 定义Build/Compile规则
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \ # 在LINUX_DIR目录下进行编译
$(MAKE_OPTS) \ # 使用上述MAKE_OPTS选项
$(KERNEL_MAKE_FLAGS) \ # 内核编译选项
M="$(PKG_BUILD_DIR)" \ # 模块输出目录
EXTRA_CFLAGS="$(BUILDFLAGS)" \ # 额外编译选项
modules # 编译内核模块
endef
# 执行KernelPackage宏函数,生成对应的Makefile代码
$(eval $(call KernelPackage,drv_regopt))
其中需要注意的是AUTOLOAD
参数的设置,它定义了该内核驱动模块在系统启动时驱动自动加载的动作,$(call AutoLoad,30,drv_regopt,1)
,表示当系统启动时名叫drv_regopt
的内核驱动会第30位加载到系统中,而不用在系统启动后手动使用insmod来加载驱动。
在package/kernel/drv_regopt/src
文件夹下新建一个drv_regopt.c
文件,内容参考如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DEV_NAME "regopt" //定义设备名称
//定义设备类和设备节点
static struct class *reg_opt_class;
static struct device *reg_opt_class_dev;
struct s_reg_param{
volatile unsigned long reg_add; //寄存器物理地址
volatile unsigned long reg_val; //寄存器值
volatile unsigned long *vm_add; //寄存器映射地址
};
/*user 和kenel交互数据的结构体,
* reg_add 寄存器地址
* reg_val 寄存器的值
* vm_add 映射到虚拟地址
*/
static struct s_reg_param reg_param = {
.reg_add = 0,
.reg_val = 0,
.vm_add = NULL,
};
static int reg_opt_open(struct inode *inode, struct file *file)
{
return 0;
}
static int reg_opt_close(struct inode *inode, struct file *file)
{
return 0;
}
//读操作函数
static int reg_opt_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
//一定要先设置用户区的buff(即应用层read函数到第二个参数),就是要修改到物理寄存器地址
copy_from_user(®_param, buff, 4); //物理地址4字节
reg_param.vm_add = (volatile unsigned long *)ioremap(reg_param.reg_add, 0x4); //映射寄存器
reg_param.reg_val = *(reg_param.vm_add); //取出寄存器
copy_to_user(buff,®_param, 8); //物理地址和值,2个4字节
iounmap(reg_param.vm_add); //取消映射
return 0;
}
//写操作函数
static ssize_t reg_opt_write(struct file *filp, const char *buff, size_t count, loff_t *offp)
{
//把要修改的参数复制到内核reg_param中
copy_from_user(®_param, buff, 8); //物理地址和值,2个4字节
reg_param.vm_add = (volatile unsigned long *)ioremap(reg_param.reg_add, 0x4); //映射寄存器
*(reg_param.vm_add) = reg_param.reg_val;
iounmap(reg_param.vm_add); //取消映射
return 0;
}
//驱动操作函数集合
static struct file_operations reg_opt_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = reg_opt_open,
.release = reg_opt_close,
.read = reg_opt_read,
.write = reg_opt_write,
};
static int major;
// 模块加载函数
static int reg_opt_init(void)
{
major = register_chrdev(0, DEV_NAME, ®_opt_fops); // 注册驱动到内核
reg_opt_class = class_create(THIS_MODULE, DEV_NAME);
reg_opt_class_dev = device_create(reg_opt_class, NULL, MKDEV(major, 0), NULL, DEV_NAME); /* /dev/regopt */
printk("reg_opt_init\n");
return 0;
}
// 模块卸载函数
static void reg_opt_exit(void)
{
device_destroy(reg_opt_class, MKDEV(major, 0)); //删除设备节点
class_destroy(reg_opt_class); //删除类
unregister_chrdev(major, DEV_NAME); // 注销设备
printk("reg_opt_exit\n");
}
module_init(reg_opt_init);
module_exit(reg_opt_exit);
MODULE_LICENSE("GPL");
在package/kernel/drv_regopt/src
文件夹下新建一个Makefile
文件,内容参考如下:
obj-m += drv_regopt.o
命令行输入make menuconfig,选中新建立的驱动模块:
$ make menuconfig
Kernel modules —>
Other modules —>
kmod-drv_regopt
如果路径下找不到新的驱动模块选项,则在命令行中先清除编译缓存再输入make menuconfig进入配置页面即可:
$ make clean
$ rm -r tmp
$ make menuconfig
随后便可以进行驱动编译或者直接把驱动模块编译到系统固件中:
##只编译这个驱动模块
$ make package/kernel/drv_regopt
##重新编译系统固件
$ make -j 8 V=s
简介:这个模块用来读写指定物理地址的值
用法:
打开设备: fd = open(“/dev/regopt”, O_RDWR);
传参: unsigned long val[2];
val[0]填入需要操作的地址
val[1]读取时会保存返回值,写入时将写入的数值存在这里
读取寄存器值: read(fd, val, 8);
写寄存器值:write(fd, val, 8);