随笔 - 34  文章 - 0  评论 - 0  阅读 - 3303

IMX6Ull驱动

mount -t nfs -o nolock,vers=3 192.168.1.117:/home/book/nfs_rootfs /mnt

cat /proc/sys/kernel/printk

echo 8 > /proc/sys/kernel/printk

cp led.ko  ~/nfs_rootfs/

arm-buildroot-linux-gnueabihf-gcc app.c -o app -static

vi ~/.bashrc

一、C语言LED驱动实验

1.设置处理器模式

  设置6ULL处于SVC模式下。设置CPSR寄存器的bit4-0,也就是M[4:0]=0x13。读写状态要用到MRS指令和MSR指令。MRS指令将CPSR寄存器数据读出到通用寄存器里面,MSR指令将通用寄存器的值写入到CPSR里面去。

2.设置SP指针

  处理器栈增长方式,对于A7而言是向下增长的。设置SP指向 0x200000+0x80000000=0x80200000。

3.跳转到C语言

  使用b指令,跳转到C语言函数,比如main函数

 

 

 

 

1.怎么进入到make menuconfig图形化界面?

首先进入到内核源码的路径下,然后输入make menuconfig即可打开这个界面。

2.make menuconfig图形化界面的操作

(1)搜索功能

  输入 / ,即可弹出搜索界面,然后输入我们想要搜索的内容即可。

(2)配置驱动的状态。

  a.把驱动编译成模块

  b.把驱动编译到内核里面,用*

  c.不编译

3.和make menuconfig有关的文件

Makefile  里面是编译规则,告诉我们在make 的时候要怎么编译,相当于菜的做饭。

Kconfig   内核配置的选项,相当于吃饭的菜单。

.config     配置完内核以后生成的配置选项,相当于我们点完的菜

4.make menuconfig 会读取哪个目录下的Kconfig文件。

arch/ $ARCH /目录下的Kconfig。

/arch/arm/configs#下面有好多的配置文件。相当于饭店的特色菜。

5.为什么要复制成.config而不复制成其他文件呢?

因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不能改名字。

6.复制的这个默认的配置选项不符合要求咋办?

7.怎么和Makefile文件建立的关系呢?

 

内核中编译驱动

 Kconfig的一个例子

source “drivers/redled/Kconfig”
config LED__4412
tristate “Led Support for GPIO Led”
depends on LEDS_CLASS
help

1.source “drivers/redled/Kconfig”, 他会包含 drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
2.config LED__4412 配置选项的名称,CONFIG_LED_4412
3.tristate 表示的驱动的状态, 三种状态是把驱动编译成模块, 把驱动编译到内核, 不编译。 与之对应的还
bool 分别是编译到内核, 不编译
4 “Led Support for GPIO Led”make menuconfig 显示的名字
5 A depends on B 表示只有在选择 B 的时候才可以选择 A
比如我想直接去掉 LED 相关的驱动, 我们直接改.config 文件可以吗? 可以, 但是不推荐。 如果有依赖的话,
直接修改.config 是不成功的。
6.select 反向依赖, 该选项被选中时, 后面的定义也会被选中。
7.help

 

 我们输入“ vim Kconfig” 命令编辑 KconfigKconfig 写入以下内容:

config HELLO
  tristate "helloworld"
  help
  hello hello

 

 

 config HELLO 里就是Makefile里的名字。

 

 

Linux三大设备驱动

字符设备:IO的传输过程是以字符位单位的,没有缓冲的。比如 I2C,SPI  都是字符设备

块设备:IO的传输过程是以块为单位的。跟存储相关的,都属于块设备,比如 tf 卡

网络设备:与前两个不一样,是以socket套接字来访问的。

1.杂项设备是字符设备的一种,可以自动生成设备节点。

 

 

我们的系统里面有很多杂项设备。我们可以输入 cat /proc/misc 命令来查看。

2.杂项设备除了比字符设备简单,杂项设备的主设备号是相同的,均为10次,次设备号是不同的。主设备号相同就可以节省内核的资源。

3.主设备号和次设备号的概念

设备号包含主设备号和次设备号, 设备号是计算机识别设备的一种方式, 主设备号相同的就被视为同
一类设备, 主设备号在 Linux
系统里面是唯一的, 次设备号不一定唯一。
主设备可以比作电话号码的区号。比如北京区号010。次设备号相当于电话号码。

主设备号可以通过以下命令来查看, 前面的数字就是主设备号, 如下图所示:cat /proc/devices

 

 

4.杂项设备的描述

misc 设备用 miscdevice 结构体表示, miscdevice 结构体的定义在内核源码具体定义在include/linux/miscdevice.h 中, 内容如下:

复制代码
struct miscdevice  {
        int minor;     //次设备号
        const char *name;//设备节点的名字
        const struct file_operations *fops;//文件操作集
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const struct attribute_group **groups;
        const char *nodename;
        umode_t mode;
};
复制代码

file_operations 文件操作集在定include/linux/fs.h 下面

 

 

 里面的一个结构体成员都对应一个调用。

extern int misc_register(struct miscdevice *misc);注册杂项设备
extern void misc_deregister(struct miscdevice *misc);注销杂项设备

5 注册杂项设备的流程。

(1)填充 miscdevice 这个结构体

(2)填充 file_operations 这个结构体

(3)注册杂项设备并生生成设备节点

 

 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific  devices */

struct file_operations misc_fops = {  //文件操作集
    .owner = THIS_MODULE

};

struct miscdevice misc_dev = {   //杂项设备结构体
    .minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号 
    .name = "hello_misc",     //杂项设备名字是hello_misc
    .fops = &misc_fops,      //文件操作集

};

static int misc_init(void) //注册杂项设备
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error\n");
        return -1;
    }

    printk("misc registe is succed\n");
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);
    printk("misc bye bye\n");
}

module_init(misc_init);
module_exit(misc_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

 

应用层和内核层数据传输

Linux一切接文件!

文件对应的操作有打开,关闭,读写。

设备节点对应的操作有打开,关闭,读写

 

1.如果我们在应用层使用系统IO对设备节点进行打开,关闭,读写等操作会发生啥???

复制代码
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数。
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    //当我们在应用层 poll/select设备节点的时候,就会触发我们驱动里面poll这个函数
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    //当我们在应用层ioctl设备节点的时候,就会触发发我们驱动里面ioctl这个函数
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 
    //当我们在应用层open设备节点的时候,就会触发我们驱动里面open这个函数
    int (*open) (struct inode *, struct file *);
    //当我们在应用层close设备节点的时候,就会触发我们驱动里面close这个函数
    int (*release) (struct inode *, struct file *);

复制代码

 通过框图我们可以知道:

上层应用        设备节点        底层驱动

设备节点就是连接上层应用和底层驱动的桥梁

2.假如我们的file_operations里面没有read,我们在应用层read设备

3.我们的应用层和内核层是不能直接进行数据传输的。

 copy to 

 

 

 

 

 

 open read write close

 应用层从内核读数据

 

 

 从设备中读取数据, 当用户层调用函数 read 时, 对应的, 内核驱动就会调用这个函数。

 应用层从内核写数据

 

 

 

Linux物理地址到虚拟地址映射

1.操作一个寄存器, 可以定义一个指针来操作寄存器

  unsighted int *p = 0x12345678;
  *p=0x87654321;

  但是在linux上不行,在Linux上,如果想要操作硬件,需要先把物理地址转换成虚拟地址。

   因为Linux使能了MMU,所以我们在Linux上不能直接操作物理地址。

2.使能了MMU的好处???

(1)让虚拟地址成了可能

(2)可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,所以这样就保证了系统安全。

3.MMU非常复杂,如何完成物理地址到虚拟地址的转换呢??

内核提供了函数

ioremap: 把物理地址转换成虚拟地址
iounmap: 释放掉 ioremap 映射的地址

static inline void __iomem *ioremap(phys_addr_t offset, size_t size)

phys_addr_t offset:映射物理地址的起始地址。

size_t size:要映射多大的内存空间

返回值:成功返回虚拟地址的首地址,失败返回NULL

static inline void iounmap(void __iomem *addr)

*addr:要取消映射的虚拟地址的首地址。

注意: 物理地址只能被映射一次, 多次映射会失败。 

4.如何查看哪些物理地址被映射过了呢??

可以用  cat /proc/iomem

 

 驱动模块传参

1.什么是驱动传参

驱动传参就是传递参数给我们的驱动。

ex:

insmod beep.ko  a=1

 

2.驱动传参的作用??

(1)设置驱动的相关参数,比如设置缓冲区的大小

(2)设置安全校验,防止我们写的驱动被人盗用

 

3.怎么给驱动传参数?

(1)传递普通的参数,比如char ,int 类型的

  函数:

  module_param(name, type, perm);

  参数:

    name  要传递进去的参数的名称

    type    类型

    perm   参数读写的权限

 

 

(2)传递数组

module_param_array(name, type, nump, perm);

 参数:

    name  要传递进去的参数的名称

    type    类型

    nump   实际传入进去的参数的个数

    perm   参数读写的权限

复制代码
#include <linux/init.h>
#include <linux/module.h> 

static int a;
static int b[5];
static int count;
//传递普通的参数 名字 类型 参数读写的权限
module_param(a, int, S_IRUSR);
//传递数组            名字 类型  传入进去参数的个数 参数读写的权限
module_param_array(b, int, &count,      S_IRUSR);
static int hello_init(void)
{ 
    int i;
    for(i=0; i<count; i++)
    {
        printk("b[%d] = %d\n", i , b[i]);
    }
    printk("count = %d \n", count);
    printk("a = %d \n", a);
    return 0;
}
static void hello_exit(void)
{   
    // printk("a=%d \n", a); 
    // printk("bye bye! \n");
}

module_init(hello_init); 
module_exit(hello_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

 (3)如果多传递进去参数,会发生什么?

    会报错!!!

 

   

 

 

1.字符设备和杂项设备的区别

1.杂项设备的主设备号是固定的,固定为10, 那么我们要学习的字符类设备号就需要自己或者系统来分配了。

杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。

 2.注册字符类设备号的两个方法。

第一种:静态分配一个设备号,使用的是:

register_chrdev_region(dev_t,  unsigned, const char *);

需要明确知道我们系统里面哪些设备号没有用。

参数:

dev_t:设备号的起始值。类型是 dev_t 类型

unsigned:次设备号的个数。

const char *:设备的名称

返回值:成功返回0,失败返回非0

 

 

 dev_t 类型:

dev_t是用来保存设备号的,是一个32位数。

高12位是用来保存设备号,低12位用来保存次设备的号

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

 

Linux提供了几个宏定义来操作设备号

复制代码
#define MINORBITS    20
次设备号的位数,一共是20位 #define MINORMASK ((1U << MINORBITS) - 1) 次设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
在dev_t 里面获取我们的主设备号 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
在dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
将我们主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号。
复制代码

 

第二种方法:动态分配

alloc_chrdev_region(dev_t * , unsigned, unsigned,  const char *);

参数:

dev_t *:保存生成的设备号

unsigned:我们请求的第一个次设备号,通常是0

unsigned:连续申请的设备号的个数。

const char *:设备名称

返回值:成功返回0,失败返回负数

使用动态分配会优先使用255到234

 

3.注销设备号

unregister_chrdev_region(dev_t, unsigned)

dev_t  分配设备号的起始地址

unsigned  申请的连续设备号的个数

 

 

 

复制代码
#include <linux/init.h>
#include <linux/module.h> 
#include <linux/fs.h>
#include <linux/kdev_t.h>

#define DEVICE_NUMBER  1  //定义次设备号的个数
#define DEVICE_SNAME "schrdev"   //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev"   //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER  0   //定义次设备号的起始地址
static int major_num, minor_num; //定义主设备号和次设备号

//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);

static int hello_init(void)
{ 
    dev_t dev_num;
    int ret;
    /*  如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
    if(major_num)
    {
        /** 静态注册设备号 */
        printk("major_num = %d \n", major_num);
        printk("minor_num = %d \n", minor_num);

        dev_num = MKDEV(major_num, minor_num);  //MKDEV 将主设备号和次设备号合并为一个设备号
        ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
        if(ret < 0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region OK\n");
    }
    else
    {                            //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
        ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
        if(ret < 0)
        {
            printk("alloc_chrdev_region error\n");
        }
        printk("alloc_chrdev_region OK\n");
    
    major_num = MAJOR(dev_num);
    minor_num = MINOR(dev_num);
    printk("major_num = %d \n", major_num);
    printk("minor_num = %d \n", minor_num);

    }
    return 0;
}

static void hello_exit(void)
{   
    unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
    printk("bye bye! \n");
}

module_init(hello_init); 
module_exit(hello_exit);

MODULE_LICENSE("GPL");
复制代码
  •  使用静态分配
  •  使用动态分配,不传递参数

 

 

 

建议使用动态申请。如果驱动很多人用,动态不会重复!!!

 

 

注册杂项设备:

misc_register(&misc_dev);

注销杂项设备:

misc_deregister(&misc_dev);

 

cdev结构体:描述字符设备的一个结构体

struct cdev{

  struct kobject kobj;

  struct module *owner;

  const struct file_operation *ops;

  struct list_head list;

  dev_t dev;

  unsigned int count; 

};

 

步骤一:定义一个cdev结构体

步骤二:使用cdev_init函数初始化cdev结构体成员变量

 void cdev_init(struct cdev *, struct file_operations *); 

参数:

struct cdev *  要初始化的cdev

file_operations *  文件操作集

cdev->ops=fops;  //实际就是把文件操作集写个ops

步骤三:cdev_add函数注册到内核

cdev_add(struct cdev *, dev_t, unsigned);

第一个参数:cdev 的结构体指针

第二个:设备号

第三个:次设备号的数量

 

注销字符设备:

void cdev_del(struct cdev *);

 

字符设备注册完以后自动生成设备节点。

我们需要使用 mknod 命令创建一个设备节点

格式:

mknod 名称 类型 主设备号 次设备号

eg:

  mknod /dev/test  c  245  0

 

 

 

32

高12 主   低20 次设备号

 

手动创建

  可以通过命令mknod创建设备节点。

  mknod命令格式:

  mknod  设备节点名称   设备类型(字符设备用c,块设备用b)   主设备号   次设备号

       举例: mknod /dev/test c236 0

2.在注册设备的时候自动创建。

        可以通过mdev机制实现设备节点的自动创建与删除。

 自动创建设备节点

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEVICE_NUMBER 1                  //定义次设备号的个数
#define DEVICE_SNAME "schrdev"           //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev"           //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0            //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
static int major_num, minor_num;         //定义主设备号和次设备号

struct cdev cdev;
struct class *class;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);

int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open\n");
    return 0;
}

struct file_operations chrdev_ops = {
    .owner = THIS_MODULE,
    .open = chrdev_open};

static int hello_init(void)
{
    dev_t dev_num;
    int ret;
    /*  如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
    if (major_num)
    {
        /** 静态注册设备号 */
        printk("major_num = %d \n", major_num);
        printk("minor_num = %d \n", minor_num);

        dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
        ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
        if (ret < 0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region OK\n");
    }
    else
    { //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
        ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
        if (ret < 0)
        {
            printk("alloc_chrdev_region error\n");
        }
        printk("alloc_chrdev_region OK\n");

        major_num = MAJOR(dev_num);
        minor_num = MINOR(dev_num);
        printk("major_num = %d \n", major_num);
        printk("minor_num = %d \n", minor_num);
    }

    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &chrdev_ops);
   cdev_add(&cdev, dev_num, DEVICE_NUMBER);
   class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
return 0; } static void hello_exit(void) { unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER); cdev_del(&cdev); class_destroy(class); printk("bye bye! \n"); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
复制代码

 


 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEVICE_NUMBER 1                  //定义次设备号的个数
#define DEVICE_SNAME "schrdev"           //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev"           //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0            //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
#define DEVICE_NODE_NAME  "chrdev_test"  //宏定义设备节点的名字

static int major_num, minor_num;         //定义主设备号和次设备号
dev_t dev_num;                           //设备号

struct cdev cdev;
struct class *class;
struct device *device;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);

int chrdev_open(struct inode *inode, struct file *file)
{
    printk("chrdev_open\n");
    return 0;
}

struct file_operations chrdev_ops = {
    .owner = THIS_MODULE,
    .open = chrdev_open};

static int hello_init(void)
{
    dev_t dev_num;
    int ret;
    /*  如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
    if (major_num)
    {
        /** 静态注册设备号 */
        printk("major_num = %d \n", major_num);
        printk("minor_num = %d \n", minor_num);

        dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
        ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
        if (ret < 0)
        {
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region OK\n");
    }
    else
    { //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
        ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
        if (ret < 0)
        {
            printk("alloc_chrdev_region error\n");
        }
        printk("alloc_chrdev_region OK\n");

        major_num = MAJOR(dev_num);
        minor_num = MINOR(dev_num);
        printk("major_num = %d \n", major_num);
        printk("minor_num = %d \n", minor_num);
    }

    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &chrdev_ops);
    cdev_add(&cdev, dev_num, DEVICE_NUMBER);
    class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
    //在class类下创建设备                            设备号               设备节点的名字
    device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME);

    return 0;
}

static void hello_exit(void)
{
    unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
    cdev_del(&cdev);
    device_destroy(class, dev_num);
    class_destroy(class);
    printk("bye bye! \n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

 平台总线模型介绍

1.什么是平台总线模型?

  平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线,不是真实的导线。

  平台总线模型就是把原来的驱动C文件分成了两个C文件,一个是

2.为什么会有平台总线模型?

(1)可以提高代码的重用性

(2)减少重复性代码。

设备            总线            驱动

device.c                        driver.c        

3.平台总线的优点

4.怎么编写以平台总线模型设计的驱动?

一个是device.c,一个是driver.c,分别注册device.c和driver.c。

平台总线是以名字来匹配的,实际上就是字符串比较。

 

 

注册Platform设备

1.平台总线注册一个device

    device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,中断号,时钟等硬件资源。

 在Linux内核里面,我们是用一个结构体来描述硬件资源的。

复制代码
struct platform_device
{
    const char *name;  //平台总线进行匹配的时候用到的name, /sys/bus......
    int id;   //设备id,一般写-1
    bool id_auto;
    struct device dev;//内嵌的device结构体
u32 num_resources; //资源的个数
struct resource *resource;//device里面的硬件资源 };

struct resource {
    resource_size_t start; //资源的起始
    resource_size_t end;   //资源的结束
    const char *name;      //资源的名字
    unsigned long flags;   //资源的类型
    struct resource *parent, *sibling, *child;
};
 
复制代码

#define IORESOURCE_IO         IO 的内存
#define IORESOURCE_MEM    表述一段物理内存
#define IORESOURCE_IRQ      表示中断

 

 注册platform驱动

 编写driver.c思路

  首先定义一个platform_driver结构体变量,然后去实现结构体中的各个成员变量,那么当我们的driver和device匹配成功的时候,就会执行probe函数,所以匹配成功以后的重点就在于probe函数的编写。

复制代码
struct platform_driver {
  /*当 driver 和 device 匹配成功的时候,就会执行 probe 函数*/
  int (*probe)(struct platform_device *);
  /*当 driver 和 device 任意一个 remove 的时候,就会执行这个函数*/
  int (*remove)(struct platform_device *);
  /*当设备收到 shutdown 命令的时候,就会执行这个函数*/
  void (*shutdown)(struct platform_device *);
  /*当设备收到 suspend 命令的时候,就会执行这个函数*/
  int (*suspend)(struct platform_device *, pm_message_t state);
  /*当设备收到 resume 命令的时候,就会执行这个函数*/
  int (*resume)(struct platform_device *);
  //内置的 device_driver 结构体
  struct device_driver driver;
   //该设备驱动支持的设备的列表, 他是通过这个指针去指向 platform_device_id 类型的数组
  const struct platform_device_id *id_table;
};
复制代码

 

struct device_driver{
    const  char      *name;     //这个是我们匹配时用到的名字
    struct bus_type  *bus;     
    struct module    *owner;
}

 

 

 

 

 改改

 

 

 

id_table的优先级比driver的.name这个高。

driver.c里的.name的名字要和device.c里面的名字一样

 1.driver.c和device.c里面都要定义一个platform_driver和platform_device结构体变量。

2.匹配成功,执行probe函数。。id_table的优先级比driver.name的优先级要高。

 

 

 平台总线probe函数的编写

编写probe函数的思路:

(1)从 device.c 里面获得硬件资源,因为我们的平台总线将驱动拆成了俩部分,第一部分是 device.c,另一部分是 driver.c。那么匹配成功了之后,driver.c 要从 device.c 中获得硬件资源,那么 driver.c 就是在 probe 函数中获得的。

 方法一: 直接获取,不推荐

int beep_probe(struct platform_device *pdev){
    printk("beep_probe\n");
    return 0;
}

方法二:只用函数获得

extern struct resource * platform_get_resource(struct platform_device *, unsigned int ,unsigned int)

(2)获得硬件资源之后,就可以在 probe 函数中注册杂项/字符设备,完善 file_operation 结构体,并生成设备节点。

 注册之前要先登记:

  request()

 

 

ls  /sys/bus/platform/devices/

 

 设备树中添加自定义节点

 

 

 

 

 

make dtbs  

cp arch/arm/boot/dts/100ask_imx6ul l_mini.dtb ~/nfs_rootfs/decp

sudo cp arch/arm/boot/dts/100ask_i mx6ull_mini.dtb   ~/nfs_rootfs/demo_hcl/

 

 

 

 

 

 

 

获得设备树文件节点里面资源的步骤:

步骤一:查找我们要找的节点。
步骤二:获取我们需要的属性值。

 与查找节点有关的 OF 函数有 3 个

  • inline struct device_node *of_find_node_by_path(const char *path)
  • struct device_node *of_get_parent(const struct device_node *node)
  • struct device_node *of_get_next_child(const struct device_node *node struct device_node *prev)

 

 

 获取属性值的 of 函数

  • of_find_property 函数     property *of_find_property(const struct device_node *np,const char *name,int *lenp)

 

 设备树下的platform总线

优先级顺序:

 

 

Pinctrl 子系统和 GPIO 子系统

 

 

 

 匹配成功后,进到了probe函数。查找我们要查找的节点

 

 

 注册杂项设备和字符设备,GPIO用杂项设备完成

 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific  devices */
#include <linux/uaccess.h>
#include <linux/io.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>

int size;
u32 out_values[2];
struct device_node *test_device_node;
struct property *test_node_property;
unsigned int *virt_gpio_dr;
int led_gpio = 0;

int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_release bye bye\n");
    return 0;
}

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    char kbuf[64] = "hahaha";
    if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0) //成功了是0
    {
        printk("copy_to_user error\n");
        return -1;
    }
    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    char kbuf[64] = {0};
    if (copy_from_user(kbuf, ubuf, size) != 0) //成功   了是0
    {
        printk("copy_from_user error\n");
        return -1;
    }
    printk("kbuf is %s\n", kbuf);

    if (kbuf[0] == 1)
        gpio_set_value(led_gpio, 1);
    else if (kbuf[0] == 0)
        gpio_set_value(led_gpio, 0);


    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write

};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc", //
    .fops = &misc_fops};

int led_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("led_probe\n");
    
    /*查找我们要的节点*/
    test_device_node = of_find_node_by_path("/test");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error~ \n");
        return -1;
    }

    led_gpio =  of_get_named_gpio(test_device_node, "led-gpio", 0);
    if(led_gpio <0){
        printk("of_get_named_gpio is error~ \n");
        return -1;
    }
    printk("led_gpio is %d \n", led_gpio);

    ret = gpio_request(led_gpio, "led");
    if(ret <0){
        printk("gpio_request is error~ \n");
        return -1;
    }
    
    gpio_direction_output(led_gpio, 1);

    //杂项设备
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error\n");
        return -1;
    }

    return 0;
}

int led_remove(struct platform_device *pdev)
{
    misc_deregister(&misc_dev);
      gpio_free(led_gpio);
    printk("led_remove  \n");
    return 0;
}

const struct platform_device_id  led_id_table = {
    .name = "led_test"};

const struct of_device_id  of_match_table[]={
    {.compatible = "test1234"},
    {}         
};

struct platform_driver led_device = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "hhh",
        .of_match_table = of_match_table
    },
    // const struct platform_device_id *id_table;
    //如果id_table的优先级比driver的.name这个高,就不能匹配成功
    .id_table = &led_id_table
};

static int led_driver_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&led_device);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
        return ret;
    }
    printk("platform_driver_register OK~ \n");
    return 0;
}

static void led_driver_exit(void)
{
    printk("bye bye\n");
    platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

IOCTL接口

1.什么是unlocked_ioctl接口?

 unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。

2.unlocked_ioctl 和 read/write 函数有什么异同呢?

   相同点:都可以往内核写数据。

   不同点:read 函数只能完成读的功能,write 只能完成写的功能。读取大数据的时候效率高。ioctl 既可以读也可以写,读取大数据的时候效率不高。

3.unlocked_ioctl 接口命令规则

第一个分区:0-7,  命令的编号,范围是 0-255.
第二个分区:8-15,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。
第三个分区:16-29  表示传递的数据大小。

第四个分区:30-31 代表读写的方向。
00:表示用户程序和驱动程序没有数据传递
10:表示用户程序从驱动里面读数据
01:表示用户程序向驱动里面写数据
11:先写数据到驱动里面然后在从驱动里面把数据读出来。

 

 

 

 

 

中断基础概念

在设备树里面配置中断的时候只需要两个步骤即可:

1.把管脚设置为gpio功能。

2.使用 interrupt-parent 和 interrupts 属性来描述中断。

 

 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>

struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu;//GPIO编号
int irq; //中断号


irqreturn_t test_key(int irq, void *args)
{
    printk("test_key hhh\n");
    return IRQ_HANDLED;
}

int led_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("led_probe\n");

    //printk("node name is %s\n", pdev->dev.of_node->name);
    
    /*查找我们要的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    // if (test_device_node == NULL)
    // {
    //     printk("of_find_node_by_path is error~ \n");
    //     return -1;
    // }
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
    if(gpio_nu < 0){
        printk("of_get_named_gpio is error~ \n");
        return -1;
    }

    gpio_direction_input(gpio_nu);
    irq = gpio_to_irq(gpio_nu);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
    if(ret < 0){
        printk("request_irq is error~ \n");
        return -1;
    }

    printk("irq is %d \n", irq);

    return 0;
}

int led_remove(struct platform_device *pdev)
{
    printk("led_remove  \n");
    return 0;
}

const struct platform_device_id  led_id_table = {
    .name = "led_test"};

const struct of_device_id  of_match_table[]={
    {.compatible = "keys"},
    {}         
};

struct platform_driver led_device = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "hhh",
        .of_match_table = of_match_table
    },
    // const struct platform_device_id *id_table;
    //如果id_table的优先级比driver的.name这个高,就不能匹配成功
    .id_table = &led_id_table

};

static int led_driver_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&led_device);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
        return ret;
    }
    printk("platform_driver_register OK~ \n");
    return 0;
}

static void led_driver_exit(void)
{
    printk("bye bye\n");
    free_irq(irq, NULL);
    platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
按键中断
复制代码

 

 

中断下文之tasklet

步骤一:定义一个 tasklet 结构体
步骤二:动态初始化 tasklet
步骤三:编写 tasklet 绑定的函数
步骤四:在中断上文调用 tasklet
步骤五:卸载模块的时候删除 tasklet

 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>

struct device_node *test_device_node;
struct property *test_node_property;
struct tasklet_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号

void test(unsigned long data)
{
    int i = data;
    printk("i is %d\n", i);
    while(i--)
        printk("test_key is %d\n", i);
}

irqreturn_t test_key(int irq, void *args)
{
    printk("start~\n");
    tasklet_schedule(&key_test);
    printk("end~\n");
    return IRQ_HANDLED;
}

int led_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("led_probe\n");

    //printk("node name is %s\n", pdev->dev.of_node->name);
    
    /*查找我们要的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error~ \n");
        return -1;
    }

    /** 获得GPIO的编号*/
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
    if(gpio_nu < 0){
        printk("of_get_named_gpio is error~ \n");
        return -1;
    }

    /*设置GPIO的方向*/
    gpio_direction_input(gpio_nu);

    /*获得中断号*/
    irq = gpio_to_irq(gpio_nu);  
    //irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d \n", irq);

    /*申请中断*/
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
    if(ret < 0){
        printk("request_irq is error~ \n");
        return -1;
    }

    tasklet_init(&key_test, test, 100);

    return 0;
}

int led_remove(struct platform_device *pdev)
{
    printk("led_remove  \n");
    return 0;
}

const struct platform_device_id  led_id_table = {
    .name = "led_test"};

const struct of_device_id  of_match_table[]={
    {.compatible = "keys"},
    {}         
};

struct platform_driver led_device = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "hhh",
        .of_match_table = of_match_table
    },
    // const struct platform_device_id *id_table;
    //如果id_table的优先级比driver的.name这个高,就不能匹配成功
    .id_table = &led_id_table
};

static int led_driver_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&led_device);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
        return ret;
    }
    printk("platform_driver_register OK~ \n");
    return 0;
}

static void led_driver_exit(void)
{
    printk("bye bye\n");
    free_irq(irq, NULL);
    tasklet_kill(&key_test);
    platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

 

等待队列

阻塞:  操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

非阻塞:  操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

 

irqreturn_t test_key(int irq, void *args)
{
    value = !value;
    wq_flags = 1;
    wake_up(&key_wq);
    return IRQ_RETVAL(IRQ_HANDLED);
}

唤醒的时候不会立马解除阻塞,先去判断wq_flags是否达成,如果还是0,不会解除,如果标志位为1 解除。

 

 

工作队列

工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的 tasklet 机制有什么不同呢?tasklet 也是实现中断下文的机制。他们俩个最主要的区别是 tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。

Linux 系统在启动期间会创建内核线程,该线程创建以后就处于 sleep 状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h>

struct device_node *test_device_node;
struct property *test_node_property;
//struct tasklet_struct key_test;
struct work_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号

void test(struct work_struct *data)
{
    int i = 100;
    printk("i is %d\n", i);
    while(i--)
        printk("test_key is %d\n", i);
}

irqreturn_t test_key(int irq, void *args)
{
    printk("start~\n");
    //tasklet_schedule(&key_test);
    //调度
    schedule_work(&key_test);
    printk("end~\n");
    return IRQ_HANDLED;
}

int led_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("led_probe\n");

    //printk("node name is %s\n", pdev->dev.of_node->name);
    
    /*查找我们要的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error~ \n");
        return -1;
    }

    /** 获得GPIO的编号*/
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
    if(gpio_nu < 0){
        printk("of_get_named_gpio is error~ \n");
        return -1;
    }

    /*设置GPIO的方向*/
    gpio_direction_input(gpio_nu);

    /*获得中断号*/
    //irq = gpio_to_irq(gpio_nu);  
    irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d \n", irq);

    /*申请中断*/
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
    if(ret < 0){
        printk("request_irq is error~ \n");
        return -1;
    }

    //tasklet_init(&key_test, test, 100);
    INIT_WORK(&key_test, test);

    return 0;
}

int led_remove(struct platform_device *pdev)
{
    printk("led_remove  \n");
    return 0;
}

const struct platform_device_id  led_id_table = {
    .name = "led_test"};

const struct of_device_id  of_match_table[]={
    {.compatible = "keys"},
    {}         
};

struct platform_driver led_device = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "hhh",
        .of_match_table = of_match_table
    },
    // const struct platform_device_id *id_table;
    //如果id_table的优先级比driver的.name这个高,就不能匹配成功
    .id_table = &led_id_table
};

static int led_driver_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&led_device);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
        return ret;
    }
    printk("platform_driver_register OK~ \n");
    return 0;
}

static void led_driver_exit(void)
{
    printk("bye bye\n");
    free_irq(irq, NULL);
    //tasklet_kill(&key_test);
    platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

内核定时器

expires=jiffies+ 1*HZ

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0);
/*现在是10:00,定时了一分钟,到了10:01进入了
超时处理函数,超时处理函数里面又使用了mod_timer()
把它设置了10:02,10:02进来设置成10:03...
*/
static void timer_function(unsigned long data) { printk("This is time_function!\n"); mod_timer(&test_timer, jiffies+ 3*HZ); } static int hello_init(void) { printk("hello world!\n"); test_timer.expires=jiffies+ 3*HZ; add_timer(&test_timer);//启动定时器 return 0; } static void hello_exit(void) { printk("bye bye\n"); del_timer(&test_timer); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");
复制代码

 

 

按键消抖实验

按键按下,第一次进入中断,会定时20ms,20ms超时后,就会在超时处理函数判断当前电容是否低电平,如果是低电平,就执行想要执行的

 

复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>

static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0);

struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu; // GPIO编号
int irq;     //中断号

static void timer_function(unsigned long data)
{
    printk("This is tim e_function!\n");
    //mod_timer(&test_timer, jiffies + 1 * HZ); //周期性定时
    
}

irqreturn_t test_key(int irq, void *args)
{
    printk("test_key hhh\n");
    //20ms
    test_timer.expires = jiffies + msecs_to_jiffies(20); //定义时间点
    add_timer(&test_timer);//添加到内核里面
    return IRQ_HANDLED;
}

int led_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("led_probe\n");

    // printk("node name is %s\n", pdev->dev.of_node->name);

    /*查找我们要的节点*/
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error~ \n");
        return -1;
    }

    /** 获得GPIO的编号*/
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error~ \n");
        return -1;
    }

    /*设置GPIO的方向*/
    gpio_direction_input(gpio_nu);

    /*获得中断号*/
    // irq = gpio_to_irq(gpio_nu);
    irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d \n", irq);

    /*申请中断*/
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
    if (ret < 0)
    {
        printk("request_irq is error~ \n");
        return -1;
    }

    // printk("irq is %d \n", irq);

    return 0;
}

int led_remove(struct platform_device *pdev)
{

    printk("led_remove  \n");
    return 0;
}

const struct platform_device_id led_id_table = {
    .name = "led_test"};

const struct of_device_id of_match_table[] = {
    {.compatible = "keys"},
    {}};

struct platform_driver led_device = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "hhh",
        .of_match_table = of_match_table},
    // const struct platform_device_id *id_table;
    //如果id_table的优先级比driver的.name这个高,就不能匹配成功
    .id_table = &led_id_table};

static int led_driver_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&led_device);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
        return ret;
    }
    printk("platform_driver_register OK~ \n");
    return 0;
}

static void led_driver_exit(void)
{
    printk("bye bye\n");
    free_irq(irq, NULL);
    platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");
复制代码

 

 

 

 

输入子系统

 

 

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    int fd;
    struct input_event test_event;

    fd = open("/dev/input/event1", O_RDWR);
    
    if (fd < 0)
    {
        perror("open error");
        return fd;
    }
    
    while(1)
    {
        read(fd, &test_event, sizeof(test_event));
        if(test_event.type == EV_KEY){
            printf("type is %#x\n", test_event.type);
            printf("value is %#x\n", test_event.value);
        }
    }
    return 0;
}
复制代码

 

(二)

 

 

 

 

应用层实现I2C通信

 

 

IO 模型

A钓鱼不干其他的事情,等着

 

 B钓鱼看书,画画...

 

 C拿了几个鱼竿钓鱼

 

 D在鱼竿上系了一个铃铛

 

 

 A请助手B钓鱼

 

 

U-Boot

复制代码
    //uboot 版本号和编译时间,可以看出,
    //当前的 uboot 版本号是 2016.03,编译时间是 2020 年 8 月 7 日凌晨 20 点 47 分
    U-Boot 2016.03-gd3f0479 (Aug 07 2020 - 20:47:37 +0800)
    
    //可以看出当前使用的 CPU 是飞思卡尔的 I.MX6ULL(I.MX 以
    前属于飞思卡尔,然而飞思卡尔被 NXP 收购了),频率为 792MHz,但是此时运行在 396MHz。
    这颗芯片是工业级的,结温为-40°C~105°C。
    CPU: Freescale i.MX6ULL rev1.1 792 MHz (running at 396 MHz)
    CPU: Industrial temperature grade (-40C to 105C) at 51C
    
    //当前的复位原因是 POR。I.MX6ULL 芯片上有个 POR_B 引脚,将这
    个引脚拉低即可复位 I.MX6ULL。
    Reset cause: POR
    Board: I.MX6U ALPHA|MINI   //板子名字
    I2C: ready  //提示 I2C 准备就绪
    
    //提示当前板子的 DRAM(内存)为 512MB,如果是 NAND 版本的话内存为256MB。
    DRAM: 512 MiB
    
    //提示当前有两个 MMC/SD 卡控制器:FSL_SDHC(0)和 FSL_SDHC(1)。I.MX6ULL
    支持两个 MMC/SD,正点原子的 I.MX6ULL EMMC 核心板上 FSL_SDHC(0)接的 SD(TF)卡,
    FSL_SDHC(1)接的 EMMC。
    MMC: FSL_SDHC: 0, FSL_SDHC: 1
    
    Display: ATK-LCD-7-1024x600 (1024x600)// LCD 型号
    Video: 1024x600x24
    In: serial //标准输入、标准输出和标准错误所使用的终端
    Out: serial
    Err: serial
    switch to partitions #0, OK
    mmc1(part 0) is current device
    Net: FEC1   //网口信息
    Error: FEC1 address not set.
    
    Normal Boot
    Hit any key to stop autoboot: 0
    =>
复制代码

 

U-Boot启动流程

  • 汇编阶段

1.初始化异常向量表

 2.切换到SVC模式

在 reset 函 数 中 跳 转 到 save_boot_params , save_boot_params 定 义 在 100 行 , 功能是跳转到save_boot_params_ret,

save_boot_params_ret 主要功能是保证 CPU 在 SVC 模式,并禁止 FIQ 和 IRQ 中断。
往下是向量表重定位和 SPL 相关的设置,然后依次跳转到 cpu_init_cp15,cpu_init_crit,_main。

 3.关闭MMU,关闭cache,禁止看门狗

定义了 cpu_init_cp15 函数,此函数主要功能是关闭 cache,MMU,TLB 等,这些都是虚拟内存转化相关功能,uboot 阶段使用的是物理内存。

关闭虚拟内存映射后跳转到 cpu_init_crit 函数,

 cpu_init_crit 跳转到 lowlevel_init,查找函数”lowlevel_init”

 lowlevel_init 定义在 arch/arm/cpu/armv7/lowlevel_init.S 文件,

 

 4.清除bss段,,跳转到 board_init_f,

 然后跳转到 board_init_r

  •  C语言阶段

1.初始gd结构体

gd 结构体用于存储全局信息,如 CPU 主频、环境变量地址、RAM 地址等。

2. 初始化硬件,然后进行自启动倒计时,计时期间没有按键按下会根据
bootcmd 环境变量启动内核,有按键按下会进入命令行。

 

posted on   ccxwyyjy  阅读(227)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示