编译驱动前,需要先编译uboot和kernel
在我的环境中,已经有一个mk文件,先编译uboot,再编译kernel
需要指定kernel的源码位置,和交叉编译工具链的位置
# 定义了要编译的内核模块对象文件。obj-m 是一个 Makefile 变量,+= 表示追加内容。
# beepDriver.o 是要编译的目标对象文件,最终会被链接成名为 beepDriver.ko 的内核模块文件。obj-m 表示将目标编译为可加载的内核模块(.ko 文件)
obj-m += beepDriver.o
# 是用于编译内核模块所依赖的内核源码位置
KERNELDIR:=/home/ljh/MyStudy/6818KernelSys/kernel
# 指向了具体的交叉编译工具链的位置
CROSS_COMPILE:=/home/ljh/MyStudy/6818KernelSys/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-
# 当前工作目录定义
PWD:=$(shell pwd)
default:
$(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.order .*.cmd *.ko *.mod.c *.symvers *.tmp_versions
在Linux中,一切皆文件。
在 Linux 内核驱动开发中,struct file_operations
结构体是一个非常关键的结构体,它定义了内核与用户空间程序之间的接口,用于实现对设备文件的各种操作。
static const struct file_operations beep_6818_fops =
{
// THIS_MODULE 是一个宏,它会被展开为指向当前内核模块的指针。
// 作用:内核利用这个指针来记录模块的使用情况,确保在有进程正在使用该模块时,模块不会被卸载。
.owner = THIS_MODULE,
// 当用户空间程序调用 open() 系统调用打开与该驱动关联的设备文件时,内核会调用这个 beepOpen 函数。
.open = beepOpen,
.write = beepWrite,
.read = beepRead,
// 使用 unlocked_ioctl 可以让驱动开发者更方便地实现 ioctl 功能,同时也提高了内核驱动的性能和稳定性。但需要注意的是,在 unlocked_ioctl 函数内部,不能进行会导致睡眠或阻塞的操作,因为它是在一个无锁的环境中执行的。
.unlocked_ioctl = beepIoctl,
// 当用户空间程序调用 close() 系统调用关闭与该驱动关联的设备文件时,内核会调用这个 beepRelease 函数。
.release = beepRelease,
};
完整代码:
//头文件:/kernel/linux/*.h
//BEEP ---- GPIOC14
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cfg_type.h"
#define BEEP_ON 1
#define BEEP_OFF 0
#define GPIO_BEEP (PAD_GPIO_C + 14)
static dev_t beep_num; //设备号
static unsigned int beep_major = 0; //主设备号:0--动态分配,not 0 ---静态注册
static unsigned int beep_minor = 0;
//1.定义一个cdev
static struct cdev beep_dev;
static struct class *beepClass;
static struct device *beepDevice;
//3、定义一个文件操作集,并对其进行初始化
static int beepOpen(struct inode *inode, struct file *filp)
{
printk("beep driver is openning\n");
return 0;
}
static ssize_t beepWrite(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
char val;
if (count != 1)
{
return -EINVAL;
}
if (copy_from_user(&val, buf, 1))
{
return -EFAULT;
}
if (val)
{
gpio_set_value(GPIO_BEEP, BEEP_ON);
}
else
{
gpio_set_value(GPIO_BEEP, BEEP_OFF);
}
return 1;
}
static ssize_t beepRead(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int status = gpio_get_value(GPIO_BEEP);
char val = status? 1 : 0;
if (count != 1)
{
return -EINVAL;
}
if (copy_to_user(buf, &val, 1))
{
return -EFAULT;
}
return 1;
}
static long beepIoctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case BEEP_ON:
case BEEP_OFF:
gpio_set_value(GPIO_BEEP, cmd);
break;
default:
return -ENOIOCTLCMD;
}
return 0;
}
static int beepRelease(struct inode *inode, struct file *filp)
{
gpio_set_value(GPIO_BEEP, BEEP_OFF);
return 0;
}
static const struct file_operations beep_6818_fops =
{
.owner = THIS_MODULE,
.open = beepOpen,
.write = beepWrite,
.read = beepRead,
.unlocked_ioctl = beepIoctl,
.release = beepRelease,
};
//驱动的安装函数
static int __init beepInit(void)
{
int ret;
//2、申请设备号
if (beep_major == 0) //动态分配
{
ret = alloc_chrdev_region(&beep_num, beep_minor, 1, "2025LJHBeep");
}
else
{
//静态注册
beep_num = MKDEV(beep_major, beep_minor);
ret = register_chrdev_region(beep_num, 1, "2025LJHBeep");
}
if (ret < 0)
{
printk("can not get a device number\n");
return ret;
}
//4、字符设备初始化
cdev_init(&beep_dev, &beep_6818_fops);
//5、将cdev加入到内核
ret = cdev_add(&beep_dev, beep_num, 1);
if (ret < 0)
{
printk("cdev add faibeep\n");
goto cdev_add_err;
}
//6、创建class
beepClass = class_create(THIS_MODULE, "2025LJHBeep_class");
if (beepClass == NULL)
{
printk("class create error\n");
ret = -EBUSY;
goto class_create_err;
}
//7、创建device
beepDevice = device_create(beepClass, NULL, beep_num, NULL, "2025LJHBeep");
if (beepDevice == NULL)
{
printk("device create error\n");
ret = -EBUSY;
goto device_create_err;
}
//8、申请GPIO,并配置位输出
ret = gpio_request(GPIO_BEEP, "gpioc14_beep");
if (ret < 0)
{
printk("can not request gpio beep\n");
goto gpio_request_error;
}
gpio_direction_output(GPIO_BEEP, 0);
printk("6818 beep device driver init\n");
return 0;
gpio_request_error:
device_destroy(beepClass, beep_num);
device_create_err:
class_destroy(beepClass);
class_create_err:
cdev_del(&beep_dev);
cdev_add_err:
unregister_chrdev_region(beep_num, 1);
return ret;
}
//驱动的卸载函数
static void __exit beepExit(void)
{
unregister_chrdev_region(beep_num, 1); //释放设备号
cdev_del(&beep_dev); //删除cdev
device_destroy(beepClass, beep_num); //销毁device
class_destroy(beepClass); //删除class
gpio_free(GPIO_BEEP);
printk("6818 beep device driver exit\n");
}
//module的入口和出口
module_init(beepInit); //安装驱动:#insmod beep_drv.ko --->module_init()--->6818_beep_init( )
module_exit(beepExit); //卸载驱动:#rmmod beep_drv.ko --->module_exit()--->6818_beep_exit()
//module的描述, #modinfo *.ko
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("6818: 2025LJHBeep Device Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("V1.0");
直接make来编译,得到.ko文件
拷贝到ARM板上,使用insmod加载驱动
在/dev/目录可以找到,在这里的名字是在驱动程序中指定的,和ko文件名无关。
在我们上面这个,驱动名是 2025LJHBeep,路径是 /dev/2025LJHBeep
因为驱动的路径是/dev/2025LJHBeep,Linux中一切皆文件,打开这个文件描述符进行操作,就是操作驱动程序。
#include
#include
#include
#include
#include
#include
#include
#define DEVICE_FILE_PATH "/dev/2025LJHBeep" // 这里的路径要与驱动中创建设备节点的名称一致
const char BEEP_ON = 1;
const char BEEP_OFF = 0;
int main()
{
int fd;
char write_buf[1];
char read_buf[1];
// 打开设备文件
fd = open(DEVICE_FILE_PATH, O_RDWR);
if (fd < 0)
{
perror("Failed to open device file");
return 1;
}
// 尝试打开蜂鸣器
write_buf[0] = BEEP_ON;
if (write(fd, write_buf, 1) < 0)
{
perror("Failed to write to device");
close(fd);
return 1;
}
sleep(1);
// 读取蜂鸣器状态
if (read(fd, read_buf, 1) < 0)
{
perror("Failed to read from device");
close(fd);
return 1;
}
if (read_buf[0] == BEEP_ON)
{
printf("Beep status: ON\n");
}
else
{
printf("Beep status: OFF\n");
}
sleep(1);
printf("--------------------------\n");
// 尝试关闭蜂鸣器
write_buf[0] = BEEP_OFF;
if (write(fd, write_buf, 1) < 0)
{
perror("Failed to write to device");
close(fd);
return 1;
}
printf("Beep turned off.\n");
// 再次读取蜂鸣器状态
if (read(fd, read_buf, 1) < 0)
{
perror("Failed to read from device");
close(fd);
return 1;
}
if (read_buf[0] == BEEP_ON)
{
printf("Beep status: ON\n");
}
else
{
printf("Beep status: OFF\n");
}
sleep(2);
printf("-------------use ioctl------------\n");
// 使用ioctl
ioctl(fd, BEEP_ON);
sleep(2);
ioctl(fd, BEEP_OFF);
// 关闭设备文件
close(fd);
return 0;
}