fmql之字符驱动设备(1)-寄存器操作

上一篇文章,记录了模块入口和出口函数的编写。

这一篇要继续编写字符驱动程序了。

仍然参考正点原子:

第3.3讲 我的第一个Linux驱动-字符设备驱动框架搭建实验_哔哩哔哩_bilibili

1-驱动注册和卸载

字符驱动设备的注册函数为register_chrdev        

                         卸载函数为unregister_chrdev        

linux/fs.h        :

static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}

static inline void unregister_chrdev(unsigned int major, const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}

目前的代码:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>

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

static int __init chrdevbase_init(void)
{
    int major;
    printk(KERN_EMERG "chrdevbase_init\r\n");
    /*character device: register*/
    major = register_chrdev(CHRDEVBASE_MAJOR,CHEDEVBASE_NAME,&chrdevbase_fops);
    return 0;
}

/*module exit*/
static void __exit chrdevbase_exit(void)
{
    printk(KERN_EMERG "chrdevbase_exit\r\n");
    /*character device: unregister*/
    unregister_chrdev(CHRDEVBASE_MAJOR,CHEDEVBASE_NAME);
}

/* 驱动模块入口和出口函数注册 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_AUTHOR("Skylar <skylar@33.com>");
MODULE_DESCRIPTION("FMQL Chrdevbase Driver");
MODULE_LICENSE("GPL");

module_init与module_exit为驱动模块的加载和卸载(入口和出口函数)。(将驱动编译成模块)

命令cat /proc/devices 可以查看当前已使用的设备号,主设备号不能重复

设备号

dev_t为无符号32位(u32),包括主设备号(高12位)和次设备号(低20位)

/linux/kdev_t.h        :

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

#define print_dev_t(buffer, dev)					\
	sprintf((buffer), "%u:%u\n", MAJOR(dev), MINOR(dev))

MAJORMINOR宏定义可以获取dev的主/次设备号,MKDEV可以把主/次设备号拼接成一个设备。

file_operations结构体

file_operations结构体为设备的操作函数的集合。添加需要的操作即可。

参考gpio.c的写法(其他文件都可):

static const struct file_operations gpio_fops = {
	.owner          = THIS_MODULE,
	.poll           = gpio_poll,
	.unlocked_ioctl = gpio_ioctl,
	.write          = gpio_write,
	.open           = gpio_open,
	.release        = gpio_release,
	.llseek		= noop_llseek,
};

.owner是必须写的,照着写就行。

open和release函数是成对出现;write和read也是。

因此,添加以下代码:

/* file_oprations*/
static const struct file_operations chrdevbase_fops = {
	.owner          = THIS_MODULE,
	.write          = chrdevbase_write,
	.open           = chrdevbase_open,
	.release        = chrdevbase_release,
    .read           = chrdevbase_read,
};

 之后,给出上述四个函数的定义:(继续参考)

#include <linux/fs.h>

static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    printk("chrdevbase_release\r\n");
    return 0;
}

static ssize_t chrdevbase_write(struct file *file, const char __user *buf,
	size_t count, loff_t *off)
{
    printk("chrdevbase_write\r\n");
    return 0;
}

static ssize_t chrdevbase_read(struct file *file, char __user *buf, size_t count,
		loff_t *offset)
{
    printk("chrdevbase_read\r\n");
    return 0;
}

makefile编译,生成可执行文件.ko

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名    : chrdevbase.c
作者      : 正点原子
版本      : V1.0
描述      : chrdevbase驱动文件。
其他      : 无
论坛      : www.openedv.com
日志      : 初版V1.0 2019/1/30 左忠凯创建
修改      :v3.0     2023/6/8  正点原子
***************************************************************/

#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!\r\n");
    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!\r\n");
    }else{
        printk("kernel senddata failed!\r\n");
    }

    //printk("chrdevbase read!\r\n");
    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\r\n", writebuf);
    }else{
        printk("kernel recevdata failed!\r\n");
    }

    //printk("chrdevbase write!\r\n");
    return 0;
}

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

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

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

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

不知道为什么,printk必须前面加上“KERN_EMERG”才能在控制台显示打印信息:

测试APP

 APP.c为Linux应用开发的程序。

 比如,想查看open函数如何调用:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名     : chrdevbaseApp.c
作者       : 正点原子
版本       : V1.0
描述       : chrdevbase驱测试APP。
其他       : 使用方法:./chrdevbaseApp /dev/chrdevbase <1>|<2>
             argv[2] 1:读文件
             argv[2] 2:写文件
论坛       : www.openedv.com
日志 : 初版 V1.0 2019/1/30 正点原子创建
修改 :     V3.0 2023/6/8  正点原子
***************************************************************/

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!\r\n");
        return -1;
    }

    filename = argv[1];

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

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

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

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

    return 0;
}

open函数返回的是文件描述符。

write函数:将userdata传给驱动程序;

read函数:将kerneldata传给

编译APP.c        :

以下内容也可参考:

韦东山嵌入式入门笔记之——应用开发基础篇(三)_韦东山 c应用开发-CSDN博客

韦东山嵌入式入门笔记之——应用开发基础篇(四)_framebuffer 应用编程视频-CSDN博客

韦东山嵌入式入门笔记之——开发板上的第一个APP和驱动程序_韦东山 程序自动运行-CSDN博客

手动添加设备

  1. 执行chrdevbase.ko                lsmod检查一下
  2. 输入命令:mknod /dev/chedevbase c 200 0       创建设备节点

c:字符设备

200:主设备号

0:次设备号

        检查一下        ls /dev/chrdevbase -l

  1.  执行APP:./chedevbaseAPP /dev/chrdevbase 1(或2)

FATAL: Module xxx.ko not found in directory /lib/modules/4.1.15-2.1.0+ Modprobe无法装载模块问题_modules no found in directory-CSDN博客

修改modprobe命令:

运行APP

 but APP运行还是不行:

Linux下-bash: Permission denied 或者 sudo: command not found 错误 - VVingerfly - 博客园

嵌入式linux运行程序 -sh ./xxx: not found 解决办法_51CTO博客_嵌入式linux应用程序

./APP: Command not found找到解决方法了:

chmod修改APP的权限即可。

read和write都没问题。

2-led程序

通过寄存器去控制led的亮灭,所以需要:

  1. 查看fmql芯片的GPIO寄存器地址(DATA和DIR)
  2. 将物理地址映射到虚拟地址指针(因为linux是访问虚拟地址的)
  3. 通过readlwritel函数访问虚拟地址指针(不建议自己直接访问地址)(l代表的是32位)
  4. register_chrdev占用了一个主设备号下的所有次设备号,太多了,因此,让linux自行分配设备号,需要用到函数:register_chrdev_region 或alloc_chrdev_region(在__init led_init)
  5. 代替mknod:自动创建设备节点class_create(在__init led_init)

目前只做了前三个。

led.c       

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/ide.h>

#define LED_MAJOR   200
#define LED_NAME    "led_1"         /* cat /proc/devices */

#define LEDOFF      0       /* led off */
#define LEDON       1       /* led on  */

/*  物理地址  */
#define GPIO_C_DR_BASE       (0xE0003200)    // data
#define GPIO_C_DDR_BASE      (0xE0003204)    // direction
#define GPIO_C_INTEN_BASE    (0xE0003230)    // interrup enable

/* 地址映射后的虚拟指针 */
static void __iomem *FMQL_GPIO_C_DR;
static void __iomem *FMQL_GPIO_C_DDR;
static void __iomem *FMQL_GPIO_C_INTEN;

/* LED - ON / OFF*/
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON){
        val  = readl(FMQL_GPIO_C_DR);
        val |= (1 << 5);
        writel(val, FMQL_GPIO_C_DR);
        printk(KERN_EMERG "led on\r\n");
    } else if(sta == LEDOFF)
    {
        val  = readl(FMQL_GPIO_C_DR);
        val &= ~(1 << 5);
        writel(val, FMQL_GPIO_C_DR);
        printk(KERN_EMERG "led off\r\n");
    }
}

static int led_open(struct inode *inode, struct file *flip)
{
    printk(KERN_EMERG "led_open\r\n");
    return 0;
}

static int led_release(struct inode *inode, struct file *flip)
{
    printk(KERN_EMERG "led_release\r\n");
    return 0;
}

static ssize_t led_write(struct file *file, const char __user *buf,
		size_t count, loff_t *offset)
{
    int retvalue = 0;
    unsigned char databuf[1];

    printk(KERN_EMERG "led_write\r\n");

    retvalue = copy_from_user(databuf, buf, count);
    if(retvalue < 0){
        printk(KERN_EMERG "write led failed\r\n");
        return -EFAULT;
    } else {
        printk(KERN_EMERG "write led success\r\n");
    }
    
//    if(databuf[0] == LEDON){    // Light on
//
//    }
    led_switch(databuf[0]);
    return 0;
}

/* file_operations */
static const struct file_operations led_fops = {
    .owner      = THIS_MODULE,
    .open       = led_open,
    .release    = led_release,
    .write      = led_write,
};

/* module_init */
static int __init led_init(void)
{
    int ret = 0;
    unsigned int val = 0;
    printk(KERN_EMERG "led_init\r\n");

    /* 1- 地址映射 */
    FMQL_GPIO_C_DR      = ioremap(GPIO_C_DR_BASE, 4);   // 32bit
    FMQL_GPIO_C_DDR     = ioremap(GPIO_C_DDR_BASE, 4);
    FMQL_GPIO_C_INTEN   = ioremap(GPIO_C_INTEN_BASE, 4);

    /* 2- 初始化 */     // read, change val, write
    val  = readl(FMQL_GPIO_C_DDR);
    val &= ~(1 << 5);       // 0x1 << 5 : clear bit5
    val |= 1 << 5;          // bit5 set 1 : output
    writel(val, FMQL_GPIO_C_DDR);
    /* 3- 点灯 */
    val  = readl(FMQL_GPIO_C_DR);
    val |= (1 << 5);
    writel(val, FMQL_GPIO_C_DR);    // Light LED

    ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if (ret < 0){
        //fail
        printk(KERN_EMERG "register_chrdev led: fail\r\n");
        return -EIO;
    } else {
        printk(KERN_EMERG "register_chrdev led: succeed\r\n");
    }

    return 0;
}
/* module_exit */
static void __exit led_exit(void)
{
    unsigned int val = 0;
    printk(KERN_EMERG "led_exit");
    /* 3- 灭灯 */
    val  = readl(FMQL_GPIO_C_DR);
    val &= ~(1 << 5);
    writel(val, FMQL_GPIO_C_DR);

    /* 1- 取消地址映射*/
    iounmap(FMQL_GPIO_C_DR);
    iounmap(FMQL_GPIO_C_DDR);
    iounmap(FMQL_GPIO_C_INTEN);

    unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);
/* info */
MODULE_AUTHOR("Skylar");
MODULE_DESCRIPTION("FMQL LED Driver");
MODULE_LICENSE("GPL");

ledAPP.c        

/*
 * led
 * 测试APP
 * 使用方法:./ledAPP /dev/led <0>|<1>
 *          argv[2] 0: LED off
 *          argv[2] 1: LED on
 */

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

#define LEDOFF  0
#define LEDON   1

int main(int argc, char *argv[])
{
    int ret, fd;
    char databuf[0];
    char* filename = argv[1];

    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }
    
    /* 1- open */
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("APP open %s error\r\n", filename);
        return -1;
    } else{
        printf("APP open %s success\r\n", filename);
    }

    /* 2- led on/off */
    databuf[0] = atoi(argv[2]);    /* char --> int */
    ret = write(fd,databuf[0], sizeof(databuf));
    if(ret < 0){
        printf("APP Turn on/off LED failed\r\n");
        close(fd);
        return -1;
    } else{
        printf("APP Turn on/off LED success\r\n");
    }

    /* 1- close */
    ret = close(fd);
    if(ret < 0){
        printf("APP close %s error\r\n", filename);
        return -1;
    } else{
        printf("APP close %s success\r\n", filename);
    }

    return 0;
}

运行

一次是0,一次是1,但是右边显示的都是led off。

而且ledAPP中的printf没有打印出来。

分析:能控制灭,说明寄存器是对的,GPIO的位也是对的。

不能控制亮:程序写错了(逻辑),或者是参数传递错误?

修改代码

ledAPP.c        :      

databuf原来定义的是databuf[0],修改成databuf[1]试试。

  

好使了!

Makefile

为了不需要每次打开终端都配置source,所以修改makefile:

这样就自动配置了。(但是echo $PETALINUX是空白的,所以可能并不好用)

这样写会报错:

手动输入命令就可以:

(未完待续。。。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值