0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

i.MX6ULL|字符设备驱动开发实践

玩转单片机 来源:玩转单片机 作者:玩转单片机 2022-10-31 11:27 次阅读

字符设备驱动开发的基本步骤可以看上一篇,本节就以 chrdevbase 这个虚拟设备为例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备,方便讲解字符设备的开发而引入的一个虚拟设备。chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。chrdevbase 这个虚拟设备的功能很简单,但是它包含了字符设备的最基本功能。

|需求目标

应用程序调用 open 函数打开 chrdevbase 这个设备,打开以后可以使用 write 函数向chrdevbase 的写缓冲区 writebuf 中写入数据(不超过 100 个字节),也可以使用 read 函数读取读缓冲区 readbuf 中的数据操作,操作完成以后应用程序使用 close 函数关闭 chrdevbase 设备。

|实现过程

1.新建文件一个nxp文件夹,然后把原厂内核文件复制过来

dd73bc60-56e2-11ed-a3b6-dac502259ad0.png

2、创建Linux_Drivers用于存放驱动文件,创建01_chrdevbase用于存放chrdevbase实验文件,chrdevbase.c是底层驱动代码,chrdevbaseApp.c是应用代码,Makefile编译底层驱动;

dd9605fe-56e2-11ed-a3b6-dac502259ad0.png

3、编写代码

chrdevbase.c文件

#include 
#include 
#include 
#include 
#include 
#include 


#define CHRDEVBASE_MAJOR  200        /* 主设备号 */
#define CHRDEVBASE_NAME    "chrdevbase"   /* 设备名     */


static char readbuf[100];    /* 读缓冲区 */
static char writebuf[100];    /* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};


/*
 * @description    : 打开设备
 * @param - inode   : 传递给驱动的inode
 * @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量
 *             一般在open的时候将private_data指向设备结构体。
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
  //printk("chrdevbase open!
");
  return 0;
}


/*
 * @description    : 从设备读取数据 
 * @param - filp   : 要打开的设备文件(文件描述符)
 * @param - buf   : 返回给用户空间的数据缓冲区
 * @param - cnt   : 要读取的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
  int retvalue = 0;
  
  /* 向用户空间发送数据 */
  memcpy(readbuf, kerneldata, sizeof(kerneldata));
  retvalue = copy_to_user(buf, readbuf, cnt);
  if(retvalue == 0){
    printk("kernel senddata ok!
");
  }else{
    printk("kernel senddata failed!
");
  }
  
  //printk("chrdevbase read!
");
  return 0;
}


/*
 * @description    : 向设备写数据 
 * @param - filp   : 设备文件,表示打开的文件描述符
 * @param - buf   : 要写给设备写入的数据
 * @param - cnt   : 要写入的数据长度
 * @param - offt   : 相对于文件首地址的偏移
 * @return       : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
  int retvalue = 0;
  /* 接收用户空间传递给内核的数据并且打印出来 */
  retvalue = copy_from_user(writebuf, buf, cnt);
  if(retvalue == 0){
    printk("kernel recevdata:%s
", writebuf);
  }else{
    printk("kernel recevdata failed!
");
  }
  
  //printk("chrdevbase write!
");
  return 0;
}


/*
 * @description    : 关闭/释放设备
 * @param - filp   : 要关闭的设备文件(文件描述符)
 * @return       : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
  //printk("chrdevbase release!
");
  return 0;
}


/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
  .owner = THIS_MODULE,  
  .open = chrdevbase_open,
  .read = chrdevbase_read,
  .write = chrdevbase_write,
  .release = chrdevbase_release,
};


/*
 * @description  : 驱动入口函数 
 * @param     : 无
 * @return     : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
  int retvalue = 0;


  /* 注册字符设备驱动 */
  retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
  if(retvalue < 0){
    printk("chrdevbase driver register failed
");
  }
  printk("chrdevbase init!
");
  return 0;
}


/*
 * @description  : 驱动出口函数
 * @param     : 无
 * @return     : 无
 */
static void __exit chrdevbase_exit(void)
{
  /* 注销字符设备驱动 */
  unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
  printk("chrdevbase exit!
");
}


/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);


/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

chrdevbaseApp.c文件

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"


static char usrdata[] = {"usr data!"};


/*
 * @description    : main主程序
 * @param - argc   : argv数组元素个数
 * @param - argv   : 具体参数
 * @return       : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
  int fd, retvalue;
  char *filename;
  char readbuf[100], writebuf[100];


  if(argc != 3){
    printf("Error Usage!
");
    return -1;
  }


  filename = argv[1];


  /* 打开驱动文件 */
  fd  = open(filename, O_RDWR);
  if(fd < 0){
    printf("Can't open file %s
", filename);
    return -1;
  }


  if(atoi(argv[2]) == 1){ 
    /* 从驱动文件读取数据 */
    retvalue = read(fd, readbuf, 50);
    if(retvalue < 0){
      printf("read file %s failed!
", filename);
    }else{
      /*  读取成功,打印出读取成功的数据 */
      printf("read data:%s
",readbuf);
    }
  }


  if(atoi(argv[2]) == 2){
    /* 向设备驱动写数据 */
    memcpy(writebuf, usrdata, sizeof(usrdata));
    retvalue = write(fd, writebuf, 50);
    if(retvalue < 0){
      printf("write file %s failed!
", filename);
    }
  }


  /* 关闭设备 */
  retvalue = close(fd);
  if(retvalue < 0){
    printf("Can't close file %s
", filename);
    return -1;
  }


  return 0;
}

Makefile文件

KERNELDIR := /home/noah/linux/nxp/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o


build: kernel_modules


kernel_modules:
  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
  $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

相关解析:

第 1 行,KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。
第2行,CURRENT_PATH表示当前路径,直接通过运行“pwd”命令来获取当前所处路径。
第 3 行,obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块。
第 8 行,具体的编译命令,后面的 modules 表示编译模块,-C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。

4、编译驱动

直接使用make命令会报错的,因为kernel中没有指定编译器和架构,使用了默认的x86平台编译报错。

dda9deee-56e2-11ed-a3b6-dac502259ad0.png

5、修改内核的Makefile文件

直接定义ARCH和CROSS_COMPILE 这两个的变量值为 arm 和 arm-linux-gnueabihf-

ddcd3074-56e2-11ed-a3b6-dac502259ad0.png

ddf54550-56e2-11ed-a3b6-dac502259ad0.png

6、再次编译驱动

编译通过,会生成不少编译文件;

de235ab2-56e2-11ed-a3b6-dac502259ad0.png

7、编译应用程序

应用程序只有一个文件,在ubuntu对应文件夹,直接输入指令进行编译:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

8、SD卡启动系统

插入SD卡,把拨码开关调到SD卡启动,然后ping一下网关,确认网络通畅才能继续的进行,如果ping不通就请看看前几节;

de5d4ae2-56e2-11ed-a3b6-dac502259ad0.png

9、启动内核

在uboot界面输入下面指令启动系统,

tftp80800000zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000

10、创建目录并复制驱动文件

检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。

将 chrdevbase.ko 和 chrdevbaseAPP 复制到 rootfs/lib/modules/4.1.15 目录中:

de71797c-56e2-11ed-a3b6-dac502259ad0.png

11、加载设备驱动

自制的根文件系统,有些命令是不支持的;

//加载驱动
insmodchrdevbase.ko
// 查看驱动
lsmod
// 指令查看devices信息
cat/proc/devices

效果如下图:

de90d736-56e2-11ed-a3b6-dac502259ad0.png

12、创建设备节点文件

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件:

mknod /dev/chrdevbase c 200 0
其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看,结果如图:

decc0f18-56e2-11ed-a3b6-dac502259ad0.png

13、验证读写

使用 chrdevbaseApp 软件操作 chrdevbase 这个设备,看看读写是否正常,首先进行读操作,输入如下命令:

//读
./chrdevbaseApp /dev/chrdevbase 1
// 写
./chrdevbaseApp /dev/chrdevbase 2

相关解析:

三个参数“./chrdevbaseApp”、“/dev/chrdevbase”和“1”,这三个参数分别对应 argv[0]、argv[1]和 argv[2]。

第一个参数表示运行 chrdevbaseAPP 这个软件,

第二个参数表示测试APP要打开/dev/chrdevbase这个设备。

第三个参数就是要执行的操作,1表示从chrdevbase中读取数据,2 表示向 chrdevbase 写数据。

deee90ec-56e2-11ed-a3b6-dac502259ad0.png

效果如下:

df11f686-56e2-11ed-a3b6-dac502259ad0.png

14、卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,命令如下:

// 卸载chrdevbase.ko
rmmod chrdevbase.ko
// 查看驱动
lsmod

至此,chrdevbase 这个设备的整个驱动就验证完成了,驱动工作正常。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10991

    浏览量

    206735
  • 函数
    +关注

    关注

    3

    文章

    3868

    浏览量

    61309
  • 驱动开发
    +关注

    关注

    0

    文章

    129

    浏览量

    12010

原文标题:i.MX6ULL|字符设备驱动开发实践

文章出处:【微信号:玩转单片机,微信公众号:玩转单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    i.MX6UL/i.MX6ULL开发常见问题】单独编译内核,uboot生成很多文件,具体用哪一个?

    i.MX6UL/i.MX6ULL开发常见问题》基于米尔电子 i.MX6UL/i.MX6ULL产品(V.10)2.3单独编译内核,uboot
    发表于 07-01 17:50

    I.MX6ULL终结者开发板支持JTAG调试

    的硬件环境1、i.MX6ULL终结者开发板一块 2、JLNK V9下载器一个 3、JLINK V9转换板一个(2.54mm转2.0mm) 1.2 搭建开发环境1.2.1 安装JLINK V9
    发表于 04-26 15:11

    迅为I.MX6ULL终结者开发板支持JTAG调试

    的硬件环境1、迅为-i.MX6ULL终结者开发板一块2、JLNK V9下载器一个3、JLINK V9转换板一个(2.54mm转2.0mm)1.2 搭建开发环境1.2.1 安装JLINK V9
    发表于 05-06 14:09

    移植5.4内核到迅为I.MX6ULL开发

    ),选择5.4.3内核编译生成的对应镜像和设备树文件(关于i.MX6ULL终结者开发板镜像的烧写,大家可以参考开发板使用手册的:第六章 I.MX6U
    发表于 06-29 10:13

    I.MX6ULL终结者开发板裸机仿真jlink调试

    I.MX6ULL‘终结者’开发板预留了JTAG仿真接口,并给出了开发文档,可以实现在JLINK仿真器条件下的单步跟踪、断点调试等功能,使得开发研究i
    发表于 07-07 10:56

    i.MX6ULL开发板硬件资源

    迅为i.MX6ULL 终结者开发板硬件资源非常丰富,几乎将 i.MX6ULL 芯片的所有资源都扩展引出到底板上了,底板提供了丰富的外设接口,开发板的尺寸是 190mm*125mm,充分
    发表于 12-29 06:18

    i.MX6ULL核心板资源

    `STAMP-IMX6ULL-CM是浙江启扬智能科技有限公司推出的基于 NXP i.MX6ULL 处理器的产品;i.MX 6ULL 是 NXP 推出的 ARM Cortex-A7 系列
    发表于 07-12 17:50

    stm32f0717bt6i.mx6ull启动方式的区别是什么?

    stm32f0717bt6i.mx6ull启动方式的区别是什么?
    发表于 11-29 07:51

    初识 i.MX6ULL 寄存器

    裸机开发_L1_汇编LED实验0. 本节目标1. 硬件层电路2. 初识 i.MX6ULL 寄存器2.1 i.MX6ULL 时钟控制寄存器2.2 i.MX6ULL IO复用寄存器2.3
    发表于 12-20 07:13

    ARM裸机篇之i.MX6ULL处理器资料分享

    1、i.MX6ULL处理器启动过程i.MX6ULL是NXP基于ARM Cortex-A7内核的单核处理器家族,主频可以高900MHz。i.MX6ULL应用处理器包含了电源管理模块,可以降低外部电源
    发表于 04-14 16:42

    关于i.MX6ULL配置GPIO

    处理器,它的GPIO外设应该如何配置呢?今天小编就将通过飞凌嵌入式的OKMX6ULL-S开发板来为大家详细介绍。一、i.MX6ULL处理器的GPIO配置i.MX6ULL运行的是Linu
    发表于 08-05 10:37

    I.MX6ULL无法枚举USB2514是为什么?

    你好目前,I.MX6ULL开发存在一些问题。其中之一是OTG USB2无法正常挂载USB2514,无法正确枚举下游设备,只显示设备id。usb设计要注意什么。
    发表于 04-03 06:55

    I.MX6ULL UART传输问题求解

    I.MX6ULL UART传输问题
    发表于 04-21 08:09

    如何使用Linux版本在i.mx6ull上启用USB网络共享?

    我从 lf-5.10-y 分支为 i.mx6ull evk 构建了 Linux。我将我的 android 手机连接到 usb otg 端口并在我的手机上启用网络共享。但是我没有看到 USB0 接口
    发表于 05-09 08:06

    如何在i.MX6ULL上为PF1510配置i2c?

    我们计划将 PF1510 与 i.MX6ULL 处理器一起使用。我看到这个设备树示例: https://github.com/Freescale/linux-fslc/blob
    发表于 05-17 14:02