Skip to content

驱动第一天---驱动基础

目录:

  1. 开发环境搭建
  2. 利用source insight创建Linux源码工程
  3. 驱动模块开发基础
  4. 字符设备驱动基础

开发环境搭建

一、tftp 服务器搭建(ubuntu版)

1.安装tftp-server

sudo apt-get install tftpd-hpa

2.配置TFTP服务器

sudo vim /etc/default/tftpd-hpa

将原来的内容改为:

TFTP_USERNAME="tftp"

TFTP_ADDRESS="0.0.0.0:69"

TFTP_DIRECTORY="tftp根目录" #服务器目录,需要设置权限为777,chomd 777

TFTP_OPTIONS="-l -c -s"

3.重新启动TFTP服务

sudo service tftpd-hpa restart

4.测试

sudo netstat -a | grep tftp

可以看到:

udp0  0 0.0.0.0:tftp0.0.0.0:*  
udp6   0  0 [::]:tftp   [::]:*

则表示tftpserver搭建成功了。

二、开发板的不同启动方式

1、通过网络加载内核和文件系统

a)将 镜像文件/uImage拷贝到ubuntu的 /tftpboot下

b)将 镜像文件/rootfs.tar.xz拷贝到ubuntu的 /source下并解压

c)将 镜像文件/exynos4412-fs4412.dtb拷贝到ubuntu的 /tftpboot下

d)修改虚拟机nfs配置文件/etc/exports,添加如下内容并重启nfs服务

/source/rootfs  *(rw,sync,no_subtree_check,no_root_squash)

e)重新驱动nfs服务

$ sudo /etc/init.d/nfs-kernel-server restart

f)设置启动参数

# setenv serverip 192.168.9.120
# setenv ipaddr 192.168.9.233
# setenv bootcmd tftp 41000000 uImage;tftp 42000000 exynos4412-
fs4412.dtb;bootm 41000000-42000000
#setenv bootargs root=/dev/nfs nfsroot=192.168.9.120:/source/rootfs
rw console=ttySAC2,115200  init=/linuxrc  ip=192.168.9.233
# saveenv

注意:这两个ip应该根据自己的实际情况适当修改

192.168.9.120 对应Ubuntu的ip;

192.168.9.233 对应板子的ip

2、从EMMC加载内核和文件系统

a)拷贝镜像文件/ramdisk.img拷贝到虚拟机/tftpboot目录下 b)烧写内核镜像到EMMC上

tftp  41000000  uImage
movi  write  kernel  41000000

c)烧写设备树文件到EMMC上

tftp  41000000  exynos4412-fs4412.dtb
movi  write  dtb  41000000

d)烧写文件系统镜像到EMMC上

tftp  41000000  ramdisk.img
movi  write  rootfs  41000000  300000

e)设置启动参数

setenv bootcmd movi read kernel 41000000\;movi read dtb 42000000\;
movi read rootfs 43000000 300000\;bootm 41000000 43000000 42000000
saveenv

f)重新启动开发板,u-boot自动加载、执行内核

三、开发板挂载NFS服务器

首先,在ubuntu上安装:

sudo apt-get install nfs-kernel-server

修改虚拟机nfs配置文件/etc/exports,添加如下内容并重启nfs服务

/xx/xx  *(rw,sync,no_subtree_check,no_root_squash)
注意:xx/xx是你要做为nfs服务器的目录

重新驱动nfs服务

$ sudo /etc/init.d/nfs-kernel-server restart

在开发板上,输入以下命令:

mount -t nfs -o nolock 192.168.10.10:/home/ubuntu/share/nfs  /mnt/

利用source insight创建Linux源码工程

参考链接:http://bbs.elecfans.com/forum.php?mod=viewthread&tid=1114963&extra=

驱动模块开发基础

内核模块是什么? Kconfig Makefile 的含义? 以任意一个选项的字符串去内核 里面查找相对的源码:

在内核工程里面直接使用如下命令可以生成

mkdir -p module_install     #创建一个文件夹用于存放编译生成的模块的安装目录
make INSTALL_MOD_PATH=module_install modules  #编译模块
make INSTALL_MOD_PATH=module_install modules_install   #安装模块到指定目录

驱动模块开发:

驱动开发四步走

1,头文件

2,驱动模块装载和卸载函数入口到声明

3,实现模块装载和卸载函数入口

4,GPL声明

最简单的驱动Demo:

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


static int __init hello_drv_init(void)
{

    //一般做系统申请资源
    printk("-------%s-------------\n", __FUNCTION__);

    return 0;
}

static void __exit hello_drv_exit(void)
{
    //一般做系统释放资源
    printk("-------%s-------------\n", __FUNCTION__);

}


module_init(hello_drv_init);
module_exit(hello_drv_exit);

MODULE_LICENSE("GPL");

驱动的makefile

ROOTFS_DIR = /home/ubuntu/share/nfs

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/ubuntu/work/linux-3.14-fs4412
CUR_DIR = $(shell pwd)

all :
    make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules

clean :
    make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean

install:
    cp -raf *.ko   $(ROOTFS_DIR)


else

obj-m += hello_drv.o
#obj-m += math.o
endif

PS:如果要编译多源码构成的一个模块时,可以使用: vers-objs = xx.o xxx.o ....

驱动相关命令:

安装:insmod xxxx.ko

查看模块安装列表:lsmod

卸载:rmmod xxx

信息:modinfo 查看模块相应的信息以及依赖项

modprobe 安装命令,1、解决依赖项 2、可以直接在目录下搜索相关的驱动,不需要带.ko即可

卸载驱动出错:

rmmod fs4412_led
rmmod: can't change directory to '/lib/modules': No such file or directory

解决方案:

mkdir -p  /lib/modules/3.14.0

1,参数传递

加载ko:  insmod hello.ko myname="avd" myvalue=33

用途: wifi驱动,wifiname wifipass
在代码如何处理参数:
module_param(name, type, perm)
参数1:表示参数到名字,比如myname, myvalue
参数2:参数到类型, charp, int,bool...
参数3: /sys/modules/表示文件到权限: 0666

用法:
    module_param(myvalue, int, 0666);
    module_param(myname, charp, S_IRUGO|S_IWUGO|S_IXUGO);

2,符号导出

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



    //不需要模块加载和卸载到入口声明,直接定义好一些封装的函数

    int my_add(int a, int b)
    {
        return a+b;
    }

    EXPORT_SYMBOL(my_add);

    int my_sub(int a, int b)
    {
        return a-b;
    }

    EXPORT_SYMBOL(my_sub);

    MODULE_LICENSE("GPL");

字符设备驱动基础

一、 linux 设备驱动类型主要有以下四种:字符设备,块设备,网络设备,杂项设备

二、设备号:

一个设备有主次设备号;主设备号可以理解成为一类,而次设备可以理解成为具体某个对象

以下面的为例:

7:0 -> ../loop0
7:1 -> ../loop1
7:2 -> ../loop2
7:3 -> ../loop3
7:4 -> ../loop4
7:5 -> ../loop5
7:6 -> ../loop6
7:7 -> ../loop7

7代表主设备号,0~7代表各个设备的次设备号

三、Linux提供的字符设备驱动接口

3.1、申请注册一个设备(char 字符设备):

static inline int register_chrdev(unsigned int major, const char *name,
                                const struct file_operations *fops);

参数1、major 表设备号(32位的值 由主+次设备号构成)高12位主设备号+低20位的次设备号构成 exp: 主设备号:1 次设备号:2 设备号=1<<20|2 =12构成了设备号

#define     MINORBITS   20
#define MINORMASK   ((1U << MINORBITS) - 1)
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
//注:ma表示主设备号,mi表示次设备号

参数2:name 表示设备名字

/dev/下面的设备名字 /dev/xxx

参数3:file_operations

表示文件操作集:比如常见的open,read,write,close,ioctl.....

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*iterate) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

3.2、释放一个设备(char 字符设备):

static inline void unregister_chrdev(unsigned int major, const char *name)

参数1、major 表设备号

参数2:name 表示设备名字

3.3、创建设备节点:

3.3.1、手动创建一个设备节点:mknod

mknod: invalid option -- 'h'
BusyBox v1.22.1 (2014-08-10 15:54:57 CST) multi-call binary.

Usage: mknod [-m MODE] NAME TYPE MAJOR MINOR

Create a special file (block, character, or pipe)

    -m MODE Creation mode (default a=rw)
TYPE:
    b   Block device
    c or u  Character device
    p   Named pipe (MAJOR and MINOR are ignored)

用法: mknod /dev/设备名 类型 主设备号 次设备号

比如: mknod /dev/chr0 c 250 0

注:手动创建--缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失

3.3.2、自动创建(通过udev/mdev机制)

接口一:创建一个类

    struct class *class_create(owner, name)//创建一个类

参数1: THIS_MODULE

参数2: 字符串名字,自定义

返回一个class指针

举例: class_create(THIS_MODULE,"hello_class")

注:调试信息: cat /sys/class/ 可以看到:与class_create中对应的hello_class

bdi/   hello_class/   misc/  rtc/   tty/
block/ i2c-adapter/   mmc_host/  scsi_device/   vc/
dma/   input/ net/   scsi_disk/ vtconsole/
firmware/  mdio_bus/  phy/   scsi_generic/
graphics/  mem/   regulator/ scsi_host/

接口二:销毁一个类

void class_destroy(devcls);

参数1: class结构体,class_create调用之后到返回值

接口三:创建一个设备文件

    struct device *device_create(struct class * class, struct device * 
    parent, dev_t devt, void * drvdata, const char * fmt,...)

参数1: class结构体,class_create调用之后到返回值

参数2:表示父亲,一般直接填NULL

参数3: 设备号类型 dev_t

参数4:私有数据,一般直接填NULL

参数5和6:表示可变参数,字符串,表示设备节点名字:(exp)led2、gpio ---->>/dev/led2 、gpio

举例:

device_create(hello_class, NULL,MKDEV(my_major,0), NULL,"hello_dev");

注:调试信息: ls /dev/hello_dev -l

可以看到:与device_create中对应的主、次设备号,以及设备节点名称

  crw-r--r--1 00 253,   0 Jan  1 00:08 /dev/hello_dev

接口四:销毁一个设备文件

原型: void device_destroy(struct class *class, dev_t devt)

参数1: class结构体,class_create调用之后到返回值

参数2: 设备号类型 dev_t

例子: void device_destroy(devcls, MKDEV(dev_major, 0));

3.4、 应用程序需要传递数据给驱动

int copy_to_user(void __user * to, const void * from, unsigned long n)

将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用

参数1:应用驱动中的一个buffer

参数2:内核空间到一个buffer

参数3:个数

返回值:大于0,表示出错,剩下多少个没有拷贝成功;;等于0,表示正确

int copy_from_user(void * to, const void __user * from, unsigned long n)

将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用

参数1:内核驱动中的一个buffer

参数2:应用空间到一个buffer

参数3:大小

3.5、IOCTL的用法:

驱动层: static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {

    switch(cmd)
    {   
        case cmd_test1:
            printk("cmd is cmd_test1\n");
            printk("cmd test1 's arg:%ld\n",arg);
            break;
        case cmd_test2:
            printk("cmd is cmd_test2\n");
            break;
        default:
            printk("out of cmd!\n");
            return -1;
        break;

    }
    return 0;
}

应用层:

ioctl(fd,cmd,arg);

3.6 printk 等级说明:

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

#define KERN_DEFAULT    KERN_SOH "d"    /* the default kernel loglevel */

3.7 字符设备驱动框架总结:

字符设备驱动的框架:

1、完善最基本的框架: 头文件

入出口声明

MODULE_LICENSE("GPL");

2、在入口函数里面:

2.1 申请一个字符设备:register_chrdev

  2.1.1 补充相应的参数,设备号,名字,文件操作集:file_operations

2.2 申请一个类:class_create

2.3 申请一个设备:device_create

3、在出口函数里面:

3.1 释放设备: device_destroy 3.2 释放类: class_destroy 3.3 释放字符设备: unregister_chrdev

4、文件操作集:file_operations 接口完善

open,write,read,ioctl.......

3.8 出错码提示:

static inline long __must_check IS_ERR(__force const void *ptr)

注:关于指针是否出错?

static inline long __must_check PTR_ERR(__force const void *ptr)

注:将指针出错的具体原因转换成一个出错码

3.9 在驱动程序中向内核申请内存空间:

static __always_inline void *kmalloc(size_t size, gfp_t flags)

flags 取 GFP_KERNEL:

代表向内核申请内存资源,如果出错了,就会一直阻塞(休眠)

4.0 硬件重映射

led 对应的gpiox2_7 con :0x11000c40 led 对应的gpiox2_7 dat :0x11000c44

ioremap();

原型: static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)

参数1:物理地址 参数2:需要映射的大小

iounmap();//把ioremap的操作取消

对于寄存器操作的API:

readl(addr)//从地址addr中读取出其值并返回
writel(addr,value)  //把值 value写入到addr地址中