linux驱动开发(一)

1.Linux设备驱动种类及层次

 Linux设备驱动的种类:字符设备驱动,块设备驱动,网络设备驱动

字符设备驱动:按照字节流来访问,只能顺序访问,不能无序访问的设备。
块设备驱动:按照block(512字节)来访问,可以顺序访问,也可以无序访问的设备

网络设备驱动:网络设备驱动没有设备文件,网络设备驱动主要是用来实现网络数据的收发工作

linux驱动开发(一)_第1张图片

2.Linux内核模块

2.1内核模块三要素

入口:安装驱动的时候执行(insmod),资源申请

出口:卸载驱动的时候执行(rmmod),资源释放

许可证:编写的内核模块要遵从GPL协议

2.2内核模块的编写

#include 
#include 

// 入口
// static:限定作用域,只能在当前文件被使用
// int:当前函数的返回值类型
// __init:告诉编译器将驱动的入口函数放在.init.text段中
// #define __init __section(".init.text") 
// demo_init:驱动入口函数的名字,例如led_init,adc_init,uart_init
// (void):当前函数没有参数
static int __init demo_init(){
    return 0;
}
// 出口
// static:限定作用域,只能在当前文件被使用
// void:没有返回值
// __exit:告诉编译器将驱动的出口函数放在.exit.text段中
// #define __exit __section(".exit.text") 
// demo_exit:驱动出口函数的名字,例如led_exit,adc_exit,uart_exit
// (void):当前函数没有参数
static void __exit demo_exit(){

}

module_init(demo_init);//告诉内核入口函数的地址
module_exit(demo_exit);//告诉内核出口函数的地址
MODULE_LICENSE("GPL");// 许可证

2.3内核模块的编译

  1. 内部编译:在内核源码目录下编译就是内部编译(适合产品节点)

    Kconfig

    .config

    Makefile

     2.外部编译:在内核源码目录之外编译就是外部编译(适合开发阶段)

KERNELDIR:= /home/linux/linux-5.10.61
#在Makefile定义一个KERNELDIR的变量,赋值内核的路径
PWD := $(shell pwd)
#Makefile中的变量,在Makefile中期一个shell终端,执行pwd命令,将结果赋值给PWD变量

all:
 make -C $(KERNELDIR) M=$(PWD) modules
 @#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
 @#在内核执行make modules,表示要进行模块化编译,将内核中配置为m编译生成xxx.ko
 @#M=$(PWD):M是Makefile中的一个变量,表示编译模块的路径,是当前目录
clean: 
 make -C $(KERNELDIR) M=$(PWD) clean
 @#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
 @#在内核执行make clean,清除编译生成的中间文件
 @#M=$(PWD):M是Makefile中的一个变量,表示清除当前目录的文件
obj-m:= demo.o #编译编译的模块是demo

编译ARM格式:make arch=arm modname=demo

编译X86格式 : make arch=x86 modname=demo

arch ?= arm
modname ?= demo
ifeq ($(arch),arm)
 KERNELDIR:= /home/linux/linux-5.10.61
else
 KERNELDIR := /lib/modules/$(shell uname -r)/build/
endif
#在Makefile定义一个KERNELDIR的变量,赋值内核的路径
PWD := $(shell pwd)
#Makefile中的变量,在Makefile中期一个shell终端,执行pwd命令,将结果赋值给PWD变量

all:
 make -C $(KERNELDIR) M=$(PWD) modules
 @#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
 @#在内核执行make modules,表示要进行模块化编译,将内核中配置为m编译生成xxx.ko
 @#M=$(PWD):M是Makefile中的一个变量,表示编译模块的路径,是当前目录
clean: 
 make -C $(KERNELDIR) M=$(PWD) clean
 @#make -C $(KERNELDIR)切换路径到内核顶层目录下,对着内核顶层目录执行make
 @#在内核执行make clean,清除编译生成的中间文件
 @#M=$(PWD):M是Makefile中的一个变量,表示清除当前目录的文件
obj-m:= $(modname).o #编译编译的模块是demo

2.4模块操作相关命令

  1. 安装

    sudo insmod xxx.ko

  2. 卸载

    sudo rmmod xxx

  3. 查看命令

    lsmod

3.内核模块中的打印语句

3.1printk的语法格式

printk(内核打印级别 "控制格式",变量);

printk("控制格式",变量);

3.2printk的打印级别

printk打印语句的打印级别一个有8种类,数值越小打印级别越高,

打印级别可以用于过滤打印信息。

#define KERN_EMERG   "0"    /* system is unusable */
#define KERN_ALERT   "1"    /* action must be taken immediately */
#define KERN_CRIT    "2"    /* critical conditions */
#define KERN_ERR     "3"    /* error conditions */
#define KERN_WARNING     "4"    /* warning conditions */
#define KERN_NOTICE  "5"    /* normal but significant condition */
#define KERN_INFO    "6"    /* informational */                                   
#define KERN_DEBUG   "7"    /* debug-level messages */

3.3过滤信息的使用方法

只有当消息的级别高于终端级别的时候消息才会在终端上显示

cat /proc/sys/kernel/printk
4 4 1 7

4:终端级别

4:默认消息级别

1:终端的最大级别

7:终端的最小级别

3.4printk使用实例

#include 
#include 

static int __init demo_init(void)
{
    // 入口函数中的打印语句
    printk(KERN_ERR "this is test first driver demo...\n");//级别和字符串之间没有逗号!!!
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
static void __exit demo_exit(void)
{
    // 出口函数中的打印语句
    printk(KERN_ERR "good bye first driver demo...\n");
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

对于ubuntu的终端不管任何级别的消息都不会主动回显,可以使用虚拟终端验证

上述的实例,进入和退出虚拟终端的方法如下:

进入虚拟终端:fn+ctrl+alt+[F2~F6]

退出虚拟终端:fn+ctrl+alt+F1

注:修改默认消息的级别

su root

echo 4 3 1 7 > /proc/sys/kernel/printk

3.5printk打印信息查看命令

dmesg :打开内核打印信息(红色级别高于终端,白色级别低于终端)
dmesg --level=err,warn:只查看err,warn级别的信息

sudo dmesg -C/-c :清除打印信息

我一般采用

make arch=x86 modename=demo

sudo dmesg -C

sudo insmod demo.ko

dmesg

就可以查看到打印的内容

linux驱动开发(一)_第2张图片

4.内核模块传参

4.1内核模块传参的API

module_param(name, type, perm)
功能:接收命令传递的参数
参数:
    @name:变量名
 @type:变量类型
         /* Standard types are:
         *	byte, hexint, short, ushort, int, uint, long, ulong
         *	charp: a character pointer
         *	bool: a bool, values 0/1, y/n, Y/N.
         *	invbool: the above, only sense-reversed (N = true).
         */
    @perm:变量的权限
返回值:无
        
MODULE_PARM_DESC(_parm, desc) 
功能:对传参变量进行描述,可以通过modinfo命令查看描述
参数:
   @_parm:变量名
   @desc:描述的字符串

4.2内核模块传参的实例

#include 
#include 
int a = 10;
module_param(a,int,0664);
MODULE_PARM_DESC(a,"this is int type var");
static int __init demo_init(void)
{
    printk("init:a = %d\n",a);
    return 0;
}
static void __exit demo_exit(void)
{
    printk("exit: a = %d\n",a);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

4.3内核模块传参测试

  1. 查看传参变量  modinfo demo.ko

    linux驱动开发(一)_第3张图片

  2. 安装驱动的时候传参

    sudo insmod demo.ko a=123

  3. 通过属性文件传参(模块安装了)

    cd /sys/module/驱动模块名/parameters/a

    su root

    echo 255 > a #修改a变量的值

    cat a #查看a变量的值

linux驱动开发(一)_第4张图片

4.4内核模块传参练习

  1. 通过内核模块传参给char ch变量传参;
  2. 通过内核模块传参给char *p变量传参;
#include 
#include 

char ch;
char *p="hello";
module_param(ch,byte,0664);
MODULE_PARM_DESC(ch,"this is char type var");

module_param(p,charp,0664);
MODULE_PARM_DESC(ch,"this is charp type var");
static int __init demo_init(){
    printk("init:ch = %c\n",ch);
    return 0;
}
static void __exit demo_exit(){
    printk("exit:p = %s\n",p);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

出现错误

linux驱动开发(一)_第5张图片

解决方法:在函数参数括号内,填写void,即使没有参数也要写void 

#include 
#include 

char ch='a';
char *p="hello";
module_param(ch,byte,0664);
MODULE_PARM_DESC(ch,"this is a char type var\n");

module_param(p,charp,0664);
MODULE_PARM_DESC(p,"this is a charp type var\n");
static int __init demo_init(void){
    printk("init:ch=%c\n",ch);
    printk("init:p=%s\n",p);
    return 0;
}
static void __exit demo_exit(void){
    printk("exit:ch=%c\n",ch);
    printk("exit:p=%s\n",p);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

注:

1.byte类型传递的时候不能够传参字符,只能够传递一个字节的整数

2.charp类型传递的时候字符串间不能有空格

3.属性文件的最大权限是0664

4.模块安装的命令如下:

sudo insmod demo.ko backlight=222 ch=65 p=hello_DC23041_everyone!

查看传参变量linux驱动开发(一)_第6张图片

直接安装驱动

linux驱动开发(一)_第7张图片

5.Linux内核导出符号表

5.1什么是导出符号表

因为内核模块都是运行在3-4G的内核空间中,假如demoA模块中有一个add,

需要将add函数的符号表导出,在编译demoB模块的时候使用这个符号表,此时

在运行demoB模块的时候就可以调用demoA模块中的add函数。

5.2导出符号表的API

EXPORT_SYMBOL_GPL(sym) 
功能:导出符号表
参数:
    @sym:被导出函数的名字
返回值:无

5.3导出符号表的实例

demoA.c

#include 
#include 
int add(int a,int b){
    return a+b;
}
EXPORT_SYMBOL_GPL(add);
static int __init demoA_init(void){
    return 0;
}
static void __exit demoA_exit(void){

}
module_init(demoA_init);
module_exit(demoA_exit);
MODULE_LICENSE("GPL");

demoB.c

#include 
#include 
extern int add(int a,int b);
static int __init demoB_init(void){
    
    printk("%d\n",add(100,200));
    return 0;
}
static void __exit demoB_exit(void){

}
module_init(demoB_init);
module_exit(demoB_exit);
MODULE_LICENSE("GPL");

5.4导出符号表的模块编译

先编译demoA模块,会生成符号表文件Module.symvers

0x72f367e8 add /home/linux/work/day1/04demo_export/demoA/demoA EXPORT_SYMBOL_GPL

在编译demoB模块钱需要将这个符号表文件拷贝到demoB目录下

编译才能通过,否则会提示add undefined!

在5.10内核版本中,符号表不支持拷贝的方式,可以在demoB模块的Makefile中

指定demoA模块下的符号表路径。

KBUILD_EXTRA_SYMBOLS += /home/linux/work/day1/04demo_export/demoA/Module.symvers

5.5导出符号表模块的安装

先安装demoA模块,在安装demoB模块,因为demoB模块依赖demoA模块

5.6导出符号表模块的卸载

先卸载demoB模块,在卸载demoA模块

6.字符设备驱动

6.1字符设备驱动的框架结构

linux驱动开发(一)_第8张图片

6.2字符设备驱动相关API

#include 

int register_chrdev(unsigned int major, const char *name,
      const struct file_operations *fops)
功能:创建(注册)字符设备驱动
参数:
    @major:主设备号   //次设备号[0-255]
        major = 0:系统会自动分配主设备号
        major > 0:静态指定设备号(但有可能失败)
 @name:驱动的名字  cat /proc/devices
        linux@ubuntu:/dev/input$ cat /proc/devices 
        Character devices:
          1   mem
          4   /dev/vc/0
          4   tty
          |          |
         主设备号 设备的名字
 @fops:操作方法结构体
  struct file_operations {
            int (*open) (struct inode *, struct file *);
            ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
            int (*release) (struct inode *, struct file *);
        }
返回值:
    major=0:成功返回主设备号,失败返回错误码
 major>0:成功返回0,失败返回错误码

void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备驱动
参数:
 @major:主设备号
 @name:设备名字
返回值:无

6.3字符设备驱动的实例

#include 
#include 
#include 
#include 
int mycdev_open (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
int mycdev_release (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
#define CNAME "mycdev"
int major;
const struct file_operations fops={
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_release,
};
static int __init mycdev_init(void){
    //1.注册字符设备驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chrdev error\n");
        return -EAGAIN;
    }
    printk("register chrdev success,major = %d\n", major);//不加这句话,dmesg看不见
    return 0;
}
static void __exit mycdev_exit(void){
    //2.注销字符设备驱动
    unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

6.4字符设备驱动的测试流程

  1. 编译驱动

    make arch=x86 modname=mycdev

  2. 安装驱动

    sudo insmod mycdev.ko

  3. 查看驱动

    dmesg

    cat /proc/devices

  4. 创建设备文件

    sudo mknod /dev/mycdev c 235 0

    mknod :创建设备文件的命令

    /dev/mycdev:设备文件的路径及名字(路径和名字是任意的)

    c : c字符设备 b块设备

    235 :主设备号

    0 :次设备号[0-255]

  5. 编写应用程序

    #include 
    int main(int argc, char const *argv[])
    {
        int fd;
        char buf[128]={0};
        if (fd=open("/dev/mycdev",O_RDWR)){
            ERRLOG("open error");
        }
    
        write(fd,buf,sizeof(buf));
        read(fd,buf,sizeof(buf));
        close(fd);
        return 0;
    }
  6. 执行应用程序看现象

    sudo chmod 0777 /dev/mycdev

linux驱动开发(一)_第9张图片

7.用户空间和内核空间数据传递

7.1用户空间和内核空间数据传递思想

内核不能够通过直接指向用户空间内存地址,如果通过这个方式访问用户空间的数据,

此时如果进程意外终止内核也会崩溃。用户空间也不能够通过指针直接修改内核空间的

数据,不安全,用户空间进入内核空间的方法只有一种系统调用。如果用户和内核需要

进行数据传输使用copy_to_user/copy_from_user函数完成。

7.2数据传输的API

#include 

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
功能:将内核空间的数据拷贝到用户空间(read)
参数:
    @to:用户空间的地址
 @from:内核空间的地址
 @n:大小(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
功能:将用户空间的数据拷贝到内核空间(write)
参数:
    @to:内核空间的地址
 @from:用户空间的地址
 @n:大小(字节)
返回值:成功返回0,失败返回未拷贝的字节的个数

7.3用户空间和内核空间数据传输实例

linux驱动开发(一)_第10张图片

mycdev.c

#include 
#include 
#include 
#include 
#define CNAME "mycdev"
int major;
int ret;
char kbuf[128]={0};
int mycdev_open (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_to_user(ubuf,kbuf,size);
    if(ret){
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret){
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
    return 0;
}
int mycdev_release (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops={
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_release,
};
static int __init mycdev_init(void){
    //1.注册字符设备驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chrdev error\n");
        return -EAGAIN;
    }
    return 0;
}
static void __exit mycdev_exit(void){
    //2.注销字符设备驱动
    unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

 test.c

#include 
int main(int argc, char const *argv[])
{
    int fd;
    char buf[128]="快去看封神!!!";
    if (-1==(fd=open("/dev/mycdev",O_RDWR))){
        ERRLOG("open error");
    }

    write(fd,buf,sizeof(buf));
    memset(buf,0,sizeof(buf));
    read(fd,buf,sizeof(buf));
    printf("buf = %s\n",buf);
    close(fd);
    return 0;
}

7.4用户空间和内核空间数据传输练习

  1. 传输unsigned int类型的整数

  2. 传输img_t结构体变量

    typedef struct{
     int width;
        int high;
    }img_t;
    

驱动和上述2.3标题中的一样,应用程序如下:

#include 
typedef struct{
    int width;
    int high;
}img_t;
int main(int argc, char const *argv[])
{
    int fd;

    unsigned int unum=10;
    img_t uimg={.high=100,
                .width=200};
    char buf[128]="快去看封神!!!";
    if (-1==(fd=open("/dev/mycdev",O_RDWR))){
        ERRLOG("open error");
    }
   
    write(fd,buf,sizeof(buf));
    memset(buf,0,sizeof(buf));
    read(fd,buf,sizeof(buf));
    printf("buf = %s\n",buf);

    
    write(fd,&unum,sizeof(unum));
    unum=0;
    read(fd,&unum,sizeof(unsigned int));
    printf("num=%d\n",unum);
  
    write(fd,&uimg,sizeof(uimg));
    memset(&uimg,0,sizeof(uimg));
    read(fd,&uimg,sizeof(uimg));
    printf("uimg=%d %d\n",uimg.high,uimg.width);
    
    close(fd);
    return 0;
}

8.在内核空间操作LED寄存器

8.1内核空间能否直接操作寄存器?

对应LED操作是通过操作寄存器完成的,寄存器的地址是物理地址。但是

在Linux内核启动之后在程序中使用地址是虚拟地址,不能够在内核空间直接

操作物理地址。如果想要在内核空间操作LED灯,就需要将LED灯的物理地址

映射到内核空间对应的虚拟地址,在内核空间操作这个虚拟地址就相当于在到

LED的寄存器的物理地址。

8.2地址映射的API

#include 

void  *ioremap(phys_addr_t offset, size_t size)
功能:地址映射
参数:
    @offset:物理地址
 @size:映射内存大小
返回值:成功返回虚拟地址,失败返回NULL
        
void iounmap(volatile void  *cookie)
功能:取消映射
参数:
 @cookie:虚拟地址
返回值:无

8.3LED灯对应的物理地址

RCC_MP_AHB4ENSETR 0x50000a28 [4] 1 GPIOE时钟使能

GPIOx_MODER 0x50006000 [21:20] 01 输出

GPIOx_ODR 0x50006014 [10] 1 LED1亮 0 LED1灭

9.LED的字符设备驱动

9.1LED1亮灭相关驱动

 注:映射可以放在open中也可以放在init中,调用频率高放在init中,可以避免重复的映射取消映射;调用频率低放在open中,避免内存一直被占用。

myled.h

#ifndef _MYLED_H
#define _MYLED_H


#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE_MODER 0x50006000
#define GPIOE_ODR 0x50006014



#endif /*_MYLED_H*/

myled.c

#include 
#include 
#include 
#include 
#include 
#include "myled.h"
#define CNAME "myled"
int major;
int ret;
char kbuf[128]={0};
unsigned int *rcc,*moder,*odr;//定义映射指针
int myled_open (struct inode *inode, struct file *file){
    
    //1.映射
    if(NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))){
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    if(NULL==(moder=ioremap(GPIOE_MODER,4))){
        printk("ioremap moder error\n");
        return -ENOMEM;
    }
    if(NULL==(odr=ioremap(GPIOE_ODR,4))){
        printk("ioremap odr error\n");
        return -ENOMEM;
    }
    //2.初始化led
    //使能时钟
    *rcc |=(1<<4);
    //输出模式
    *moder &=~(3<<20);
    *moder |=(1<<20);
    //默认灯灭
    *odr &=~(1<<10);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_to_user(ubuf,kbuf,size);
    if(ret){
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret){
        printk("copy from user error\n");
        return -EIO;
    }
    //3.控制灯亮灭
    // kbuf[0]=1 LED1亮   kbuf[0]=0 LED1熄灭
    if(kbuf[0]==1){
        *odr |= (1<<10);
    }else{
        *odr &=~(1<<10);
    }
    return size;
}
int myled_release (struct inode *inode, struct file *file){
    //4.取消映射
    iounmap(rcc);
    iounmap(moder);
    iounmap(odr);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops={
    .open=myled_open,
    .read=myled_read,
    .write=myled_write,
    .release=myled_release,
};
static int __init myled_init(void){
    //1.注册字符设备驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chrdev error\n");
        return -EAGAIN;
    }
    return 0;
}
static void __exit myled_exit(void){
    //2.注销字符设备驱动
    unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include 
typedef struct{
    int width;
    int high;
}img_t;
int main(int argc, char const *argv[])
{
    int fd;

    char buf[1]={0};
    if (-1==(fd=open("/dev/myled",O_RDWR))){
        ERRLOG("open error");
    }
    while(1){
        buf[0] = !buf[0];
        write(fd,buf,sizeof(buf));
        sleep(1);
    }
    
    
    close(fd);
    return 0;
}

练习:编写字符设备驱动控制三盏亮灭

myled.h

#ifndef _MYLED_H
#define _MYLED_H


#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE_MODER 0x50006000
#define GPIOE_ODR 0x50006014

#define GPIOF_MODER 0x50007000
#define GPIOF_ODR 0x50007014

#endif /*_MYLED_H*/

myled.c

#include 
#include 
#include 
#include 
#include 
#include "myled.h"
#define CNAME "myled"
int major;
int ret;
char kbuf[128]={0};
unsigned int *rcc,*modere,*odre,*moderf,*odrf;//定义映射指针
int myled_open (struct inode *inode, struct file *file){
    
    //1.映射
    if(NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))){
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    if(NULL==(modere=ioremap(GPIOE_MODER,4))){
        printk("ioremap moder error\n");
        return -ENOMEM;
    }
    if(NULL==(odre=ioremap(GPIOE_ODR,4))){
        printk("ioremap odr error\n");
        return -ENOMEM;
    }
    if(NULL==(moderf=ioremap(GPIOF_MODER,4))){
        printk("ioremap moder error\n");
        return -ENOMEM;
    }
    if(NULL==(odrf=ioremap(GPIOF_ODR,4))){
        printk("ioremap odr error\n");
        return -ENOMEM;
    }
    //2.初始化led
    //使能GPIOE GPIOF时钟
    *rcc |=(3<<4);
    //输出模式
    //PE10
    *modere &=~(3<<20);
    *modere |=(1<<20);
    //PF10
    *moderf &=~(3<<20);
    *moderf |=(1<<20);
    //PE8
    *modere &=~(3<<16);
    *modere |=(1<<16);
    
    //默认灯灭
    *odre &=~(1<<10);//PE10
    *odrf &=~(1<<10);//PF10
    *odre &=~(1<<8);//PE8
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_to_user(ubuf,kbuf,size);
    if(ret){
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret){
        printk("copy from user error\n");
        return -EIO;
    }
    //3.控制灯亮灭
    // kbuf[0]=1 LED1亮   kbuf[0]=0 LED1熄灭 PE10
    // kbuf[1]=1 LED2亮   kbuf[1]=0 LED2熄灭 PF10
    // kbuf[2]=1 LED3亮   kbuf[2]=0 LED3熄灭 PE8
    if(kbuf[0]==1){
        *odre |= (1<<10);
    }else if(kbuf[0]==0){
        *odre &=~(1<<10);
    }
    if(kbuf[1]==1){
        *odrf |= (1<<10);
    }else if(kbuf[1]==0){
        *odrf &=~(1<<10);
    }
    if(kbuf[2]==1){
        *odre |= (1<<8);
    }else if(kbuf[2]==0){
        *odre &=~(1<<8);
    }
    return size;
}
int myled_release (struct inode *inode, struct file *file){
    //4.取消映射
    iounmap(rcc);
    iounmap(modere);
    iounmap(odre);
    iounmap(moderf);
    iounmap(odrf);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops={
    .open=myled_open,
    .read=myled_read,
    .write=myled_write,
    .release=myled_release,
};
static int __init myled_init(void){
    //1.注册字符设备驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chrdev error\n");
        return -EAGAIN;
    }
    printk("register chrdev success,major = %d\n", major);
    return 0;
}
static void __exit myled_exit(void){
    //2.注销字符设备驱动
    unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include 

int main(int argc, char const *argv[])
{
    int fd;

    char buf[3]={0};

    if (-1==(fd=open("/dev/myled",O_RDWR))){
        ERRLOG("open error");
    }
    while(1){
        //led1
        buf[0] =1;
        write(fd,buf,sizeof(buf));
        sleep(1);

        buf[0] =0;
        write(fd,buf,sizeof(buf));
        sleep(1);
        //led2
        buf[1] =1;
        write(fd,buf,sizeof(buf));
        sleep(1);

        buf[1] =0;
        write(fd,buf,sizeof(buf));
        sleep(1);
        //led3
        buf[2] =1;
        write(fd,buf,sizeof(buf));
        sleep(1);

        buf[2] =0;
        write(fd,buf,sizeof(buf));
        sleep(1);
    }
    
    
    close(fd);
    return 0;
}

10.开发板启动参数设置

10.1tftpboot和nfs服务器

tftpboot服务器:tftp服务器它是通过UDP实现的,它的端口号是69。开发板在uboot启动后,uboot

通过tftp客户端从ubuntu的tftp服务器下载uImage(内核)和stm32mp157a-fsmp1a.dtb(设备树)。

就可以启动内核,当内核启动之后uboot的任务就完成了。

nfs服务器:nfs是网络文件系统的简称(根文件通过网络的形式共享给开发板),在ubuntu上nfs的

服务器目录是/home/linux/rootfs(把这个目录下的所有内容共享给开发板)。nfs服务器是通过TCP

实现的,它的端口号是2049。

10.2设置NAT模式端口转发

桥接模式:相当于windows和ubuntu各自都有自己的IP地址,外部可以通过它们各自的IP地址访问到对应的系统

NAT模式:ubuntu和window只有一个IP,这个IP地址就是windows的IP地址,外部如果想要访问ubuntu,必须首先

​ 访问windows的ip地址,然后借助端口转发访问ubuntu。

10.3开发板和ubuntu的连接方式

linux驱动开发(一)_第11张图片

开发板:

setenv ipaddr 192.168.2.10
setenv serverip 192.168.2.210
setenv netmask 255.255.255.0
setenv gatewayip 192.168.2.1
setenv bootcmd tftp 0xc2000000 uImage\;tftp 0xc4000000 stm32mp157a-fsmp1a.dtb\;bootm 0xc2000000 - 0xc4000000
setenv bootargs root=/dev/nfs nfsroot=192.168.2.210:/home/linux/rootfs,tcp,v4 rw console=ttySTM0,115200  init=/linuxrc ip=192.168.2.10
saveenv

windows:

将windows的有线网卡或者usb转网卡设置为静态IP

linux驱动开发(一)_第12张图片

VMware:

设置VMware的NAT模式的端口转发

linux驱动开发(一)_第13张图片

你可能感兴趣的:(linux,驱动开发,运维)