OpenWrt开发(4)-- 添加内核驱动模块 -- 读写寄存器

本文以建立一个能够读写寄存器数值的内核驱动模块来介绍如何给OpenWr添加内核驱动模块。

建立驱动源码目录

  1. 在OpenWrt代码库中的package/kernel/路径下新建一个文件夹,文件夹以驱动名称命名。例:package/kernel/drv_regopt
  2. 在新建的文件夹下新建一个src文件夹用于存放驱动源码,例:package/kernel/drv_regopt/src

添加第一层Makefile

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来加载驱动。

编写驱动C代码及对应的Makefile文件

编写驱动C代码

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(&reg_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,&reg_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(&reg_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, &reg_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");

编写二级Makefile

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);

你可能感兴趣的:(mtk路由器网关开发,OpenWrt开发,linux,openwrt,嵌入式,mtk)