上一篇:荔枝派zero驱动开发01:基本字符设备
下一篇:荔枝派zero驱动开发03:设备树基础
创建工程
参考上一章建立工程(编译时指定linux内核源码位置即可)
创建源码文件 newchardev.c(源码附在文后),然后编辑Makefile文件(只有obj-m有变化)
KERNELDIR := /root/licheepi/linux-zero-5.2.y
CURRENT_PATH := $(shell pwd)
obj-m := newchardev.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译内核驱动
在源码目录调用make进行编译,产物即newchardev.ko
建立测试APP
这里同样用C语言写一个简单的测试APP,运行于用户空间,用于与驱动交互
只需要一个源文件chardevApp.c(附在文后)
编译测试APP
编译很简单,使用一条gcc指令即可,由于程序要在开发板上运行,要使用arm-linux-gnueabihf-gcc编译,这里使用绝对路径指定编译器(建议与内核编译器使用相同版本)
/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc chardevApp.c -o chardevApp
生成可执行文件的属性:
root@d6788caa53d9:~/licheepi/driver_develop/02_newchardev# file chardevApp
chardevApp: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=65297ee92ce86105160cf3526aedff644c4e9970, not stripped
root@d6788caa53d9:~/licheepi/driver_develop/02_newchardev#
拷贝到开发板备用
测试
将newchardev.ko拷贝到开发板,在开发板执行驱动测试操作
//开发板环境
cd /lib/modules/5.2.0/
insmod newchardev.ko
//读取
./chardevApp /dev/newchardev
// cat /dev/newchardev
//写入
./chardevApp /dev/newchardev "write to kernel test!"
// echo "write to kernel test!" > /dev/newchardev
简要分析
本节在注册驱动入口函数chardev_init中加入了cdev和创建设备节点,无需再调用mknod手动创建节点,也在chardev_exit执行了反初始化
read/write接口通过copy_to_user与copy_from_user实现用户空间与内核空间交互数据,本节进行了完善,添加了一个内核数据存储区,实现对写入数据的存储和数据的读取
另外编写了一个用户空间的测试APP,实现对驱动数据的读写测试
源码
源码chardevApp.c
#include "stdio.h"
#include "unistd.h"
#include "stdlib.h"
#include "string.h"
#include "sys/types.h"
#include "sys/stat.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char val = 0;
char buf[8192];
if (argc != 2 && argc != 3)
{
printf("Usage !=2 && Usage !=3\r\n");
printf("eg: read: ./chardevApp /dev/newchardev \r\n");
printf(" write: ./chardevApp /dev/newchardev \"string\" \r\n");
return -1;
}
filename = argv[1]; // /dev/newchardev
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("cannot open file:%s\r\n", filename);
return -1;
}
if (argc == 2) // read
{
ret = read(fd, buf, sizeof(buf));
if (ret < 0)
printf("cannot read file:%s\r\n", filename);
else
printf("read size:%d,data:\"%s\"\r\n", ret, buf);
}
if (argc == 3) // write
{
char *write_buf=argv[2];
ret = write(fd, write_buf, strlen(write_buf));
if (ret < 0)
printf("cannot write file:%s\r\n", filename);
else
printf("write size:%d\r\n", ret);
}
close(fd);
return 0;
}
源码newchardev.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
// 定义字符设备大小,即单次允许读取/写入长度,设为256字节
#define DEV_SIZE 0x100
// 设备号个数
#define CHARDEV_CNT 1
#define CHARDEV_NAME "newchardev"
struct newchar_dev
{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
char kerneldata[DEV_SIZE];
};
struct newchar_dev newchardev = {
.major = 0,
.kerneldata = {"kernel data!"},
};
// fs.h
// static struct file_operations chardev_ops;
// 按ops实现open,read,write,release
/*
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
*/
static int chardev_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchardev;
return 0;
}
static ssize_t chardev_read(struct file *filp,
char __user *buf,
size_t cnt, loff_t *offsetp)
{
// read 从内核角度看,数据从内核拷贝到用户空间
int ret = 0;
unsigned long offset = *offsetp;
unsigned int count = cnt;
// 从buf的offset字节偏移处,读取cnt个字节,offset一般为0
if (offset > DEV_SIZE)
{
printk("offset(%ld) > DEV_SIZE(%d)\r\n", offset, DEV_SIZE);
return count ? -ENXIO : 0;
}
else if (offset == DEV_SIZE)
{
// printk("offset = DEV_SIZE\n");
return 0; // 防止测试cat /dev/newchardev 时出错
}
// 计算单次允许读取的长度,进行下一步处理
if (count > DEV_SIZE - offset)
{
// printk("count > DEV_SIZE - offset\n");
count = DEV_SIZE - offset;
}
// copy_to_user与copy_from_user对应使用
ret = copy_to_user(buf, (char *)newchardev.kerneldata, strlen(newchardev.kerneldata));
if (ret == 0)
{
// 偏移需要回传,故使用指针
*offsetp += count;
printk("kernel senddata ok,size:%d\r\n", count);
ret = count;
}
else
{
printk("kernel senddata failed\r\n");
ret = -EFAULT;
}
return ret;
}
static ssize_t chardev_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offsetp)
{
// write 从内核角度看,数据从用户空间拷贝到内核
int ret = 0;
unsigned long offset = *offsetp;
unsigned int count = cnt;
memset(newchardev.kerneldata, 0, sizeof(newchardev.kerneldata));
// 从buf的offset字节偏移处,写入cnt个字节,offset一般为0
if (offset > DEV_SIZE)
{
// printk("offset > DEV_SIZE\n");
return count ? -ENXIO : 0;
}
else if (offset == DEV_SIZE)
{
// printk("offset = DEV_SIZE\n");
return 0; // 防止测试echo /dev/newchardev 时 文件尾出现错误提示
}
// 单次最多写入DEV_SIZE-offset个字节
if (count > DEV_SIZE - offset)
{
count = DEV_SIZE - offset;
}
ret = copy_from_user(newchardev.kerneldata, buf, count);
if (ret == 0)
{
// 偏移需要回传,故使用指针
*offsetp += count;
printk("kernel recvdata, size:%d...\r\n", count);
ret = count;
}
else
{
printk("kernel recvdata failed\r\n");
ret = -EFAULT;
}
return ret;
}
static int chardev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations newchardev_fops =
{
.owner = THIS_MODULE,
.open = chardev_open,
.read = chardev_read,
.write = chardev_write,
.release = chardev_release,
};
// 入口与出口需使用__init和__exit修饰
static int __init chardev_init(void)
{
int ret = 0;
// char_major参数为0时为动态申请设备号
if (newchardev.major) // 定义了设备号,静态设备号
{
newchardev.devid = MKDEV(newchardev.major, 0);
ret = register_chrdev_region(newchardev.major, CHARDEV_CNT, CHARDEV_NAME);
if (ret < 0)
{
pr_err("cannot register %s char driver.ret:%d\r\n", CHARDEV_NAME, ret);
goto exit;
}
}
else // 没有定义设备号,动态申请设备号
{
ret = alloc_chrdev_region(&newchardev.devid, 0, CHARDEV_CNT, CHARDEV_NAME);
if (ret < 0)
{
pr_err("cannot alloc_chrdev_region,ret:%d\r\n", ret);
goto exit;
}
newchardev.major = MAJOR(newchardev.devid);
newchardev.minor = MINOR(newchardev.devid);
}
printk("newcharled major=%d,minor=%d\r\n", newchardev.major, newchardev.minor);
// 初始化和添加cdev
newchardev.cdev.owner = THIS_MODULE;
cdev_init(&newchardev.cdev, &newchardev_fops);
ret = cdev_add(&newchardev.cdev, newchardev.devid, CHARDEV_CNT);
if (ret < 0)
goto del_unregister;
// 创建设备类
newchardev.class = class_create(THIS_MODULE, CHARDEV_NAME);
if (IS_ERR(newchardev.class))
goto del_cdev;
// 创建设备,会在/dev下生成设备节点
newchardev.device = device_create(newchardev.class, NULL, newchardev.devid, NULL, CHARDEV_NAME);
if (IS_ERR(newchardev.device))
goto destroy_class;
return 0;
// 注意 goto后的标签没有return操作,将顺序执行多个label直至return,这里反向写,确保完整执行反初始化
destroy_class:
class_destroy(newchardev.class);
del_cdev:
cdev_del(&newchardev.cdev);
del_unregister:
unregister_chrdev_region(newchardev.devid, CHARDEV_CNT);
exit:
printk("chardev_init failed\r\n");
return -EIO;
}
static void __exit chardev_exit(void)
{
cdev_del(&newchardev.cdev);
unregister_chrdev_region(newchardev.devid, CHARDEV_CNT);
device_destroy(newchardev.class, newchardev.devid);
class_destroy(newchardev.class);
}
// 宏
module_init(chardev_init);
module_exit(chardev_exit);
// 固定
MODULE_LICENSE("GPL");
MODULE_AUTHOR("USER");
MODULE_INFO(intree, "Y");