荔枝派zero驱动开发02:完善字符设备

上一篇:荔枝派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");
  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值