香橙派(orangePiZero2):交叉编译、内核编译及驱动开发

一、准备工作

1、安装好相关环境(Ubuntu 18.04)

开发板:orangepi-zero2

交叉编译器:aarch64-none-linux-gnu-

2、安装交叉编译工具:

(1)下载并安装交叉编译工具,下载地址如下:Index of /armbian-releases/_toolchain/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

下载好压缩包,将压缩包放到Ubuntu里如下所示:

输入以下命令进行压缩包的解压:

tar -xf gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz

解压完后,会有一个文件夹,输入命令进入文件夹,可看到对应的交叉编译工具:

(2)设置环境变量临时有效

        1)输入以下命令,输出环境变量:

        echo $PATH

        2)pwd,打印当前路径:

        3)设置临时环境变量,用到以下命令:

        export PATH=$PATH:<路径>         

        4)我们再次输出环境变量,看看是否已经添加进来:

        但是,这个方法只适用于当前终端,一旦当前终端关闭或者在其他终端中则无效。它是一个临时的环境变量。

(3)设置永久有效

        修改工作目录的 .bashrc 是一个隐藏文件,用来配置命令终端(这里要注意每个人的工作目录可能不太一样,不要盲目复制命令,有些命令是需要改动的)。

vi /home/wsm/.bashrc

注意:上面路径中的wsm是我自己的工作目录,每个人的不一样,需要按照自己的工作目录修改

在最后一行加上:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/wsm/orangePiZero2/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin

保存并退出,然后输入以下命令,使其生效:

source /home/wsm/.bashrc

(5)测试,验证是否成功:

在虚拟机编写一个hello.c的文件如下:

/*************************************************************************
        > File Name: helloc.c
        > Author:阿哈、小吴
        > Mail: 1971363937@qq.com 
        > Created Time: Sun 05 Nov 2023 06:23:58 AM PST
************************************************************************/

#include <stdio.h>
 
int main(void){
      printf("hello!\n");
      return 0;
}

用交叉编译工具编译为 test可执行文件:

aarch64-none-linux-gnu-gcc hello.c -o test

查看test文件是否是arm架构:

file test

如下:

        证明我们交叉编译工具已经安装好,你也可以将test文件拷贝到板子上运行看是否正常。

3、将Linux SDK 的源码准备好,并放在Ubuntu上,如下所示:

4、先将Linux 内核压缩包解压出来

unzip orangepi-build-main.zip

解压后,会多出解压好的文件夹:

二、编译 Linux 内核

1、先进入我们解压好的源码文件夹,如下所示:

2、运行 buid.sh 脚本,输入如下命令:

sudo ./build.sh

它会自动帮我们下载编译工具和源码。

3、运行上述脚本后,弹出下图,我们选择 Kernel package ,然后按回车:

4、紧接着,需要我们选择开发板的型号,这里我的开发板的型号是 OrangePiZero2,选择按回车即可:

5、接着选择型号分支,我的开发板是linux 4.9的,按回车即可:

(1)current 会去编译 linux 5.13

(2)legacy 会去编译 linux 4.9

6、然后会弹出 make menuconfig 配置内核的界面,此时,可以直接修改内核的配置,这里我没有配置,直接退出,退出后就会开始编译内核源码:

        (1)如果不需要修改内核的配置选项,在一开始运行 build.sh 脚本时,传入 KERNEL_CONFINGRE=no 就可以临时屏蔽弹出内核的配置界面了。

sudo ./build.sh KERNEL_CONFIIGURE=no

        (2)如果你想永久禁用内核配置界面的弹出,也可以设置 orangepi-buid-main/userpatches/config-default.conf 配置文件中的 KERNEL_CONFINGRE=no ,这样就可以永久禁用这个功能了:

        (3)编译内核的时候,如果提示错误信息为 Your display is too small to run MenuConfig! ,这是由于你的 Ubuntu PC 的终端界面太小,导致 MenuConfig 的界面无法显示,这时,只要重新把终端调大,然后重新运行 build.sh 脚本即可。

7、编译内核源码时,提示的部分信息说明如下(了解)

        (1)Linux 内核源码的版本信息:

        [ o.k. ]Compiling legacy Kernel [ 4.9.170 ]

        (2)使用的交叉编译工具链的版本:

         [ o.k. ]Compiler version [ aarch64-none-linux-gnu-gcc 9.2.1 ]

        (3)内核默认使用的配置文件以及它存放的路径:

         [ o.k. ]Using kernel config file [ config/kernel/linux-sun50iw9-legacy.config ]

        (4)如果 KERNEL_CONFINGRE=yes ,内核最终使用的配置文件 .config 会复制到 output/config 中,如果没有对内核配置进行修改,最终的配置文件和默认的配置文件是一致的:

        [ o.k. ]Exporting new kernel config [ output/config/linux-sun50iw9-legacy.config ]

        (5)编译生成的内核相关的 deb 包的路径:

        [ o.k. ]Target directory [ output/debs ]

        (6)编译生成的内核镜像 deb 包的包名:

        [ o.k. ]File name [ linux-image-legacy-sun50iw9_2.2.0_arm64.deb ]

        (7)编译使用的时间:

        [ o.k. ]Runtime [ 5 min ]

        (8)最后会显示重复编译上一次选择的内核的编译命令,使用下面的命令无需通过图形界面选择,可以直接开始编译内核源码:  

8、编译好后,生成的目录如下:

以上文件释义如下所示:

a. build.sh: 编译启动脚本
b. external: 包含编译镜像需要用的配置文件、特定功能的脚本以及部分程序的源码,编译镜像过程中缓存的 rootfs 压缩包也存放在 external 中
c. kernel: 存放 linux 内核的源码,里面名为 orange-pi-4.9-sun50iw9 的文件夹存 放 的 就 是 H616 系 列 开 发 板 legacy 分 支 的 内 核 源 码 , 里 面 名 为orange-pi-5.13-sunxi64 的文件夹存放的就是 H616 开发板 current 分支的内核源码(如果只编译了 legacy 分支的 linux 镜像,那么则只能看到 legacy 分支的内核源码;如果只编译了 current 分支的 linux 镜像那么则只能看到current 分支的内核源码),内核源码的文件夹的名字请不要手动修改,如果修改了,编译系统运行时会重新下载内核源码
d. LICENSE: GPL 2 许可证文件
e. README.md: orangepi-build 说明文件
f. output: 存放编译生成的 u-boot、linux 等 deb 包、编译日志以及编译生成的镜像等文件
g. scripts: 编译 linux 镜像的通用脚本
h. toolchains: 存放交叉编译工具链
i. u-boot: 存放 u-boot 的源码,里面名为 v2018.05-sun50iw9 的文件夹存放的就是 H616 系列开发板 legacy 分支的 u-boot 源码,里面名为 v2021.07-sunxi的文件夹存放的就是 H616 开发板 current 分支的 u-boot 源码(如果只编译了current 分支的 linux 镜像,那么则只能看到 current 分支的 u-boot源码),u-boot 源码的文件夹的名字请不要手动修改,如果修改了,编译系统运行时会重新下载 u-boot 源码
j. userpatches: 存放编译脚本需要用到的配置文件

编译好的内核文件就在 kernel 下。

三、内核移植

1、查看我们编译生成的内核相关的 deb 包:

我生成的如下所示: 

2、将编译好的 deb 上传至 开发板的 /root 目录下:

可以通过SSH连接,使用scp命令上传

scp linux-image-legacy-sun50iw9_2.2.2_arm64.deb root@orangepi:/root

3、查看已经安装了的 deb 文件:

dpkg -l | grep linux-image

4、卸载开发板中的 deb 文件:

apt purge -y linux-image-legacy-sun50iw9

5、安装上传的 deb 文件:

dpkg -i linux-image-legacy-sun50iw9_2.2.2_arm64.deb

6、重启开发板

sudo reboot.

7、再次查看安装的 deb 文件:

可见,已经安装好了最新的 deb 文件。

四、驱动编译

1、写一个示例代码(放在orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 下):

新建一个 pin4driver.c 文件:

文件写入代码如下:

#include <linux/fs.h>        //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>    //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

// 将该文件放在 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 下
static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major =231;                     //主设备号
static int minor =0;                       //次设备号
static char *module_name="pin4";   //模块名

//led_read函数
static ssize_t pin4_read(struct file *file1,char __user *buf,size_t count, loff_t *ppos)
{
    printk("pin4_read\n");  //内核的打印函数和printf类似
    return 0;
}

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
    printk("pin4_open\n");  //内核的打印函数和printf类似
    return 0;
}

//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{

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

static struct file_operations pin4_fops = {

    .owner = THIS_MODULE,
    .open  = pin4_open,
    .write = pin4_write,
    .read  = pin4_read,
};

int __init pin4_drv_init(void)  //真实入口 
{
    int ret;
    devno = MKDEV(major,minor);  //创建设备号
    ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中

    pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在Dev下自动生成设备
    pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件

    return 0;
}
void __exit pin4_drv_exit(void)
{

    device_destroy(pin4_class,devno);
    class_destroy(pin4_class);
    unregister_chrdev(major, module_name);  //卸载驱动

}
module_init(pin4_drv_init);  //入口 内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);  //出口,卸载模块函数
MODULE_LICENSE("GPL v2");

2、怎么样才能编译到这个驱动代码呢?需要我们去修改 Makefile,如下所示:

注意:我们修改的是 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 这个路径下的 Makefile 文件!!!

我们需要编译成模块的方式,obj-m 就是模块的编译方式,我们需要在Makefile中添加:

obj-m += pin4driver.o

如下所示:

3、然后我们需要回到内核的源码目录下,进行模块的编译,即 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9 这个路径下:

(1)方法一:输入以下命令进行编译,这条命令是将所有内容都编译了,需要蛮久的:

make ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- -j4

编译好后,可以看到编译生成的 .ko 文件所在的路径:

(2)方法二:输入以下命令,只是编译模块,较快:

make modules ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu-

编译如下所示:

4、将编译好的 pin4driver.ko 模块,拷贝到我们的开发板(可以用scp指令拷贝,也可用U盘拷贝)

这里讲一下用U盘拷贝的方法:

(1)首先先将 pin4driver.ko 模块 拷贝到 共享文件夹中:

cp pin4driver.ko /mnt/hgfs/share/

(2)查看共享文件夹中的文件:

ls /mnt/hgfs/share/

(3)从 windows 上的共享文件夹 将 pin4driver.ko 模块 拷贝到U盘上

(4)将U盘插到板子上, 用 dmesg 命令查看识别到的是哪个设备:

可以看到内核识别到的设备是 sda1

(5)输入指令挂载U盘,挂载到 /mnt 目录下:

sudo mount /dev/sda1 /mnt/

挂载好后,cd 进入 /mnt 目录,再 ls,即可看到U盘下的文件:

(6)用cp命令将 pin4driver.ko 模块 拷贝出来即可

(7)拷贝完后,需要取消U盘的挂载:

(8)拔出U盘即可

5、在开发板中加载驱动模块:

(1)输入以下命令即可:

sudo insmod pin4driver.ko

(2)我们看一下是否生成了pin4这个设备驱动:

模块名、主设备号与次设备号都与我们的驱动代码对上了:

6、接下来就是测试

我们需要写一个应用程序来去调用我们的驱动程序:

写一个test.c程序如下(这是用户空间的代码):

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
int main()
{
        int fd;
        fd = open("/dev/pin4",O_RDWR);
        if(fd<0){
                printf("open failed!\n");
                perror("res");
        }else{
                printf("open success!\n");
        }
        fd = write(fd,'1',1);
}

编译运行:

为什么无法打开呢?这是因为,我们加载的驱动的权限问题,我们需要给它可读可写的权限:

修改权限,再次执行即可:

7、查看驱动层的输出语句:

输入dmesg查看:

这样子,就完成了简单的字符设备驱动开发。

五、总结

        (1)通过insmod将模块,加载到内核中去,它会去 module_init 函数 加载驱动模块,再会去调用我们写的 pin4_drv_init 驱动入口函数,而这个函数又会去通过 register_chardev 函数去注册驱动,把这个驱动(整个file_operations结构体变量)加入到内核链表中。

        (2)当驱动装载成功后,会在 /dev/ 文件下生成 一个驱动(如pin4),但是要注意,我们需要给这个驱动一个所有用户均可读可写的权限。

        (3)(用户空间)应用程序中的 open 函数会去通过 系统调用sys_call(软中断,中断号是0x80) “陷入” 到内核空间(sys_call会调用sys_open),然后根据文件名找到相关的设备号,根据设备号从驱动链表里面找出驱动,如果找到了驱动就返回一个 fd 文件句柄。 

        (4)因为它会去调用驱动里面的 open 函数,于是,我们就会在内核里看到打印了相关的信息。

  • 20
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值