前面一章介绍了应用程序的IPK包开发,今天简单的提供一个驱动程序的IPK包范例,并且提供用户侧的调用代码,实现对GPIO的驱动。主要看的还是这个过程,即如何编译一个IPK的驱动包,至于怎么开发更复杂的驱动程序
别问,问就是正在学习。
代码结构
首先来看一下代码结构,对照APP开发,简单多了,因为没有页面那些东西。
package/kernel/gpio_control_driver/
├── Makefile 生成IPK包的Makefile
└── src
├── gpio_control_driver.c 驱动模块主文件
├── gpio_control_driver.h 驱动模块头文件
└── Makefile 内部编译Makefile
各部分分解一下子
1.外部的Makefile
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
模块名称
PKG_NAME:=gpio_control_driver
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define KernelPackage/gpio_control_driver
SUBMENU:=Other modules 所属内核子目录,在menuconfig的时候的选项位置
DEPENDS:=@GPIO_SUPPORT 依赖模块,需要GPIO模块支持
TITLE:=Driver for JS9331/JS7628 gpios control 模块标题,在在menuconfig的时候的选项名称
FILES:=$(PKG_BUILD_DIR)/gpio_control_driver.ko 模块名称
KCONFIG:=
AUTOLOAD:=$(call AutoLoad,30,gpio_control_driver) 开启自动加载模块
endef
描述信息
define KernelPackage/gpio_control_driver/description
Kernel module to control gpios for JS9331 and JS7628
endef
EXTRA_KCONFIG:= \
CONFIG_GPIO_CONTROL_DRIVER=m
编译准备,包括了创建目录,拷贝文件,
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
编译过程
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \
CROSS_COMPILE="$(TARGET_CROSS)" \
ARCH="$(LINUX_KARCH)" \
SUBDIRS="$(PKG_BUILD_DIR)" \
EXTRA_CFLAGS="$(BUILDFLAGS)" \
$(EXTRA_KCONFIG)
endef
执行编译过程
$(eval $(call KernelPackage,gpio_control_driver))
这里只是简单介绍了一下各行的作用,细节的内容太多了,大部分参数都需要去这些文件
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
include $(INCLUDE_DIR)/package.mk
中寻找答案。
2.src中的Makefile
obj-${CONFIG_GPIO_CONTROL_DRIVER} += gpio_control_driver.o
这里只需要定义一下中间文件。其他过程会自动完成。
3.主函数文件
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/hrtimer.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <asm-generic/errno-base.h>
#include <linux/miscdevice.h>
#include "gpio_control_driver.h"
static int gpio_control_open(struct inode *pinode, struct file *pfile)
{
printk("***%s***\n",__func__);
return 0;
}
static int gpio_control_release(struct inode *pinode, struct file *pfile)
{
printk("***%s***\n",__func__);
return 0;
}
核心函数
static long gpio_control_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
int ret;
unsigned char gpio_number;
unsigned char gpio_value;
printk("***%s***\n",__func__);
printk("cmd:0x%02X arg:0x%04X\n",cmd, arg);
gpio_number = GET_GPIO_NUM(arg);
gpio_value = GET_GPIO_VALUE(arg);
printk("gpio number:%d\n", gpio_number);
printk("gpio value:0x%02X\n", gpio_value);
switch (cmd)
{
case GPIO_CONTROL_SET_OUT:
printk("command: GPIO_CONTROL_SET_OUT\n");
ret = gpio_direction_output(gpio_number, gpio_value);
if (ret < 0)
{
printk("###gpio_direction_output ERROR: can't set gpio %d output###\n", gpio_number);
return -1;
}
printk("command: GPIO_CONTROL_SET_OUT done\n");
break;
case GPIO_CONTROL_SET_IN:
ret = gpio_direction_input(gpio_number);
if (ret < 0)
{
printk("###gpio_direction_input ERROR: can't set gpio %d input###\n", gpio_number);
return -1;
}
printk("command: GPIO_CONTROL_SET_IN\n");
break;
#if 0
case GPIO_CONTROL_GET_DIRECTION:
printk("command: GPIO_CONTROL_GET_DIRECTION\n");
break;
#endif
case GPIO_CONTROL_SET_VALUE:
gpio_set_value(gpio_number, gpio_value);
printk("command: GPIO_CONTROL_SET_VALUE\n");
break;
case GPIO_CONTROL_GET_VALUE:
ret = gpio_get_value(gpio_number);
if (ret < 0){
printk("###gpio_get_value ERROR: can't get gpio %d value###\n", gpio_number);
return -1;
}
printk("command: GPIO_CONTROL_GET_VALUE\n");
break;
case GPIO_CONTROL_REQUEST_GPIO:
printk("command: GPIO_CONTROL_REQUEST_ONE\n");
if (0 > gpio_request(gpio_number, "gpio_ctrl")){
printk("###gpio_request ERROR: can't request %d pin for output###\n", gpio_number);
return -1;
}
printk("command: GPIO_CONTROL_REQUEST_GPIO done\n");
break;
case GPIO_CONTROL_FREE_GPIO:
gpio_free(gpio_number);
printk("command: GPIO_CONTROL_FREE_GPIO done\n");
break;
default:
printk("***Unknown command:0x%02X\n***\n", cmd);
break;
}
return 0;
}
static const struct file_operations gpio_control_ops = {
.owner = THIS_MODULE,
.open = gpio_control_open,
.release = gpio_control_release,
.unlocked_ioctl = gpio_control_ioctl,
};
static struct miscdevice s_gpio_control_dev = {
.minor = MISC_DYNAMIC_MINOR,
.fops = &gpio_control_ops,
.name = GPIO_CONTROL_DEV_NAME
};
模块加载
static int gpio_control_init(void)
{
int result;
result = misc_register(&s_gpio_control_dev);
if (result != 0)
{
printk("###misc_register error###\n");
return -1;
}
printk("**gpio_control module initiation OK**\n");
return result;
}
模块退出
void gpio_control_exit(void)
{
//unregister what we registered
misc_deregister(&s_gpio_control_dev);
printk("**gpio_control module exit**\n");
}
module_init(gpio_control_init);
module_exit(gpio_control_exit);
MODULE_VERSION("V1.0");
MODULE_AUTHOR("wurobinson <wurobinson@zhuotk.com>");
MODULE_LICENSE("Dual BSD/GPL");
这里我们选择使用的设备类型是 --混杂设备,因为它只是驱动一个GPIO,不像那些字符设备,块设备那么复杂,用户只是需要用ioctl即可控制,不需要读写操作。
在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同。 所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。 在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。miscdevice的API实现在drivers/char/misc.c中。
根据代码
static const struct file_operations gpio_control_ops = {
.owner = THIS_MODULE,
.open = gpio_control_open,
.release = gpio_control_release,
.unlocked_ioctl = gpio_control_ioctl,
};
其中可以看出 open和release都没做操作,其实也不需要操作。关键的函数就在于
gpio_control_ioctl中,里面负责实现了gpio方向及值的配置,主要涉及到了这几个函数,看名字也都能看出来是什么功能。
gpio_direction_output(gpio_number, gpio_value);
gpio_direction_input(gpio_number);
gpio_set_value(gpio_number, gpio_value);
4.头文件
#ifndef GPIO_CONTROL_DRIVER_H_
#define GPIO_CONTROL_DRIVER_H_
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/kfifo.h>
#define GET_GPIO_NUM(arg1) (unsigned char)((arg1 >> 24) & 0xff)
#define GET_GPIO_VALUE(arg1) (unsigned char)((arg1 >> 16) & 0xff)
#define GPIO_CONTROL_MAJOR 99//device major number
#define GPIO_CONTROL_DEV_NAME "gpio_control"
//IOCTRL CMDs
#define GPIO_CONTROL_SET_OUT 0x01
#define GPIO_CONTROL_SET_IN 0x02
//#define GPIO_CONTROL_GET_DIRECTION 0x03
#define GPIO_CONTROL_SET_VALUE 0x04
#define GPIO_CONTROL_GET_VALUE 0x05
#define GPIO_CONTROL_REQUEST_GPIO 0x06
#define GPIO_CONTROL_FREE_GPIO 0x07
#endif /* GPIO_CONTROL_DRIVER_H_ */
编译
整个目录以文件夹形式拷贝到/package/kernel/路径下,然后进行
make menuconfig
进入配置页面,选上如下配置
Kernel modules —>
Other modules —>
kmod-gpio_control_driver… Driver for JS9331/JS7628 gpios control
保存退出,执行
make V=s
重新编译源码,就可以得到安装包
bin/targets/ar71xx/generic/packages/kmod-gpio_control_driver_4.4.79-1_mips_24kc.ipk
拷贝至开发板,执行安装命令
opkg install kmod-gpio_control_driver_4.4.79-1_mips_24kc.ipk
即可完成安装。并且可以查看到内核模块挂载。
到这里内核驱动的部分就完成了。下面是试用过程。
用户侧程序
内核驱动加载之后,需要在用户侧进行设备操作。简单写了一个控制程序
int main(int argc,char **argv)
{
int fd = 0;
unsigned int cmd = 0;
unsigned int value = 0;
if( argc != 3 )
{
printf( "usage: %s cmd value \n", basename( argv[0] ) );
return 1;
}
cmd=atoi(argv[1]);
value = atoi(argv[2]);
printf("cmd :0x%04x value:0x%04x \n",cmd,value);
/*打开设备文件*/
fd = open("/dev/gpio_control", O_RDWR);
if(fd <0)
{
printf("open dev error \n");
}
else
{
printf("open dev success \n");
}
ioctl(fd,cmd,value);
/*关闭设备*/
close(fd);
return 0;
}
然后编译成目标板的可执行程序,编译过程可以参照前面的APP开发,也可以使用交叉编译工具直接编译出程序,拷贝到开发板中。
然后,如果我要控制GPIO22端口为输出高电平。则执行
root@ZhuoTK:/tmp# ./gpio_control_user 1 369164288
cmd :0x0001 value:0x16010000
open dev success
内核打印
[ 451.423441] ***gpio_control_open***
[ 451.426940] ***gpio_control_ioctl***
[ 451.430422] cmd:0x01 arg:0x16010000
[ 451.433993] gpio number:22
[ 451.436597] gpio value:0x01
[ 451.439358] command: GPIO_CONTROL_SET_OUT
[ 451.443382] command: GPIO_CONTROL_SET_OUT done
[ 451.448710] ***gpio_control_release***
通过使用万用表就可以测量到高电平的输出了,搞定。
结束语
昨天开始,上海又出现了3位新冠,还是从北京过去的,今天开始又要排查好多单位好多人了
听说国外又发现了更凶残的病毒变种
赶紧带口罩吧。