【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 APP (Linux 驱动开发)

【安卓开发系列 -- 系统开发】安卓开发流程梳理 -- 从底层到 APP (Linux 驱动开发)

【0】开发环境简介

1. 主机,Win10;虚拟机,Ubuntu 16.04
2. 开发板,AIO-3399C 六核 AI 开发板
3. android 源码,android 7.1

【1】Linux 驱动开发

Linux 驱动开发 Makefile 文件

ifneq ($(KERNELRELEASE),)
 
obj-m += freg.o

else
	# 指定内核代码树
	KERNELDIR := /home/shallysun/proj/firefly-rk3399-Industry/kernel
	# 获取代码所在的当前目录
	PWD := $(shell pwd)
	# 指定交叉编译链
	ARCH = arm64
	CROSS_COMPILE=/home/shallysun/proj/firefly-rk3399-Industry/prebuilts/gcc/linux-x86/aarch64/
aarch64-linux-android-4.9/bin/aarch64-linux-android-
	
.PHONY: modules clean

modules:
	# 编译内核驱动模块
	$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	# 清除中间文件
	@rm -rf *.o *.order *.symvers *.mod.* .*.o.cmd .*.mod.o.cmd .*.ko.cmd .tmp_versions *.ko
	
endif

Linux 驱动开发 驱动示例源码 (Android 系统源代码情景分析(第三版) 第二章 源码)

驱动示例代码 H 头文件

#ifndef _FAKE_REG_H_
#define _FAKE_REG_H_

#include <linux/cdev.h>
#include <linux/semaphore.h>

#define FREG_DEVICE_NODE_NAME  "freg"
#define FREG_DEVICE_FILE_NAME  "freg"
#define FREG_DEVICE_CLASS_NAME "freg"

struct fake_reg_dev {
	int val;
	struct semaphore sem;
	struct cdev dev;
};

#endif

驱动示例代码 C 文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

#include "freg.h"

// 指定主设备号与从设备号
static int freg_major = 0;
static int freg_minor = 0;

// 设备类别和设备变量
static struct class* freg_class = NULL;
static struct fake_reg_dev* freg_dev = NULL;

/******************************************************************************************************
 * 设备文件的操作方法
 ******************************************************************************************************/
// 打开设备文件函数
static int freg_open(struct inode* inode, struct file* filp);
// 释放设备文件函数
static int freg_release(struct inode* inode, struct file* filp);
// 读设备文件函数
static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
// 写设备文件函数
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
// 传统设备文件操作方法表,定义了一个 file_operations 结构体变量,并制定了其中的打开、释放、读、写操作函数
static struct file_operations freg_fops = {
        .owner = THIS_MODULE,
        .open = freg_open,
        .release = freg_release,
        .read = freg_read,
        .write = freg_write,
};

/******************************************************************************************************
 * devfs 文件系统的设备属性操作方法
 ******************************************************************************************************/
// 显示寄存器的值
static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr,  char* buf);
// 写入寄存器
static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, 
size_t count);
// devfs 文件系统的设备属性
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, freg_val_show, freg_val_store);

/******************************************************************************************************
 * 设备文件的操作方法
 ******************************************************************************************************/
static int freg_open(struct inode* inode, struct file* filp) {
	struct fake_reg_dev* dev;
	/**
	 * container_of(ptr, type, member) 函数的实现包括两部分 :
	 * 1.  判断 ptr 与 member 是否为同意类型
     * 2.  计算 size 大小,结构体的起始地址 = (type *)((char *)ptr - size)   (注:强转为该结构体指针)
	 * 
	 * 作用 : 通过一个结构变量中一个成员的地址找到这个结构体变量的首地址
	 */
	dev = container_of(inode->i_cdev, struct fake_reg_dev, dev);
	filp->private_data = dev;

	return 0;
}

static int freg_release(struct inode* inode, struct file* filp) {
	return 0;
}

static ssize_t freg_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
	ssize_t err = 0;
	struct fake_reg_dev* dev = filp->private_data;

	/**
	 * int down_interruptible(struct semaphore *sem)
	 * 这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠;
	 * 但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步;
	 */
	if(down_interruptible(&(dev->sem))) {	
		return -ERESTARTSYS;
	}

	if(count < sizeof(dev->val)) {
		goto out;
	}
	/**
	 * 原型 	: unsigned long copy_to_user(void *to, const void *from, unsigned long n)
	 * to		: 目标地址(用户空间)
	 * from		: 源地址(内核空间)
	 * n		: 将要拷贝数据的字节数
	 * 返回		: 成功返回0,失败返回没有拷贝成功的数据字节数
	 */
	if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	// 唤醒进程
	up(&(dev->sem));
	return err;
}

static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
	struct fake_reg_dev* dev = filp->private_data;
	ssize_t err = 0;

	if(down_interruptible(&(dev->sem))) {
			return -ERESTARTSYS;
	}

	if(count != sizeof(dev->val)) {
			goto out;
	}
	/**
	 * 原型 	: unsigned long copy_from_user(void *to, const void *from, unsigned long n);
	 * to 		: 目标地址(内核空间)
	 * from 	: 源地址(用户空间)
	 * n 		: 将要拷贝数据的字节数
	 * 返回 	:成功返回0,失败返回没有拷贝成功的数据字节数
	 */
	if(copy_from_user(&(dev->val), buf, count)) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	// 唤醒进程
	up(&(dev->sem));
	return err;
}

/******************************************************************************************************
 * devfs 文件系统的设备属性操作方法
 ******************************************************************************************************/
static ssize_t __freg_get_val(struct fake_reg_dev* dev, char* buf) {
	int val = 0;

	if(down_interruptible(&(dev->sem))) {
			return -ERESTARTSYS;
	}

	val = dev->val;
	up(&(dev->sem));

	return snprintf(buf, PAGE_SIZE, "%d\n", val);
}

static ssize_t __freg_set_val(struct fake_reg_dev* dev, const char* buf, size_t count) {
	int val = 0;
	// 将一个字符串转换成 unsigend long long 型数据
	val = simple_strtol(buf, NULL, 10);

	if(down_interruptible(&(dev->sem))) {
			return -ERESTARTSYS;
	}

	dev->val = val;
	up(&(dev->sem));

	return count;
}

static ssize_t freg_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
	struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev);
	
        return __freg_get_val(hdev, buf);
}

static ssize_t freg_val_store(struct device* dev, struct device_attribute* attr, const char* buf, 
size_t count) {
	 struct fake_reg_dev* hdev = (struct fake_reg_dev*)dev_get_drvdata(dev);

        return __freg_set_val(hdev, buf, count);
}

// 初始化设备
static int  __freg_setup_dev(struct fake_reg_dev* dev) {
	int err;
	/**
	 * MKDEV,将主设备号与次设备号转换为 dev_t 类型的数据
	 */
	dev_t devno = MKDEV(freg_major, freg_minor);

	memset(dev, 0, sizeof(struct fake_reg_dev));

	// 静态的方式初始化字符设备
	cdev_init(&(dev->dev), &freg_fops);
	dev->dev.owner = THIS_MODULE;
	dev->dev.ops = &freg_fops;
	// 调用 cdev_add() 函数将初始化之后的 cdev 添加到系统中去;
	err = cdev_add(&(dev->dev),devno, 1);
	if(err) {
		return err;
	}	
	// 初始化信号量
	sema_init(&(dev->sem), 1);
	dev->val = 0;

	return 0;
}

// 驱动初始化函数
static int __init freg_init(void) { 
	int err = -1;
	dev_t dev = 0;
	struct device* temp = NULL;

	printk(KERN_ALERT"Initializing freg device.\n");
	// alloc_chrdev_region 是让内核分配一个尚未使用的主设备号
	err = alloc_chrdev_region(&dev, 0, 1, FREG_DEVICE_NODE_NAME);
	if(err < 0) {
		printk(KERN_ALERT"Failed to alloc char dev region.\n");
		goto fail;
	}
	// 获取主设备号与从设备号
	freg_major = MAJOR(dev);
	freg_minor = MINOR(dev);
	/**
	 * 原型 : void *kmalloc(size_t size, int flags);
	 * size 要分配内存的大小,以字节为单位.
	 * flags 要分配内存的类型
	 * #define GFP_KERNEL(__GFP_WAIT | __GFP_IO | __GFP_FS)
	 * __GFP_WAIT 	: 缺内存页的时候可以睡眠;
 	 * __GFP_IO 	: 允许启动磁盘IO;
	 * __GFP_FS 	: 允许启动文件系统IO;
	 */
	freg_dev = kmalloc(sizeof(struct fake_reg_dev), GFP_KERNEL);
	if(!freg_dev) {
		err = -ENOMEM;
		printk(KERN_ALERT"Failed to alloc freg device.\n");
		goto unregister;
	}
	// 初始化设备
	err = __freg_setup_dev(freg_dev);
	if(err) {
		printk(KERN_ALERT"Failed to setup freg device: %d.\n", err);
		goto cleanup;
	}
	// 在 /sys/class/ 目录下创建设备类别目录
	freg_class = class_create(THIS_MODULE, FREG_DEVICE_CLASS_NAME);
	if(IS_ERR(freg_class)) {
		err = PTR_ERR(freg_class);
		printk(KERN_ALERT"Failed to create freg device class.\n");
		goto destroy_cdev;
	}
	// 在 /dev/ 目录和 /sys/class/freg 目录下分别创建设备文件 freg
	temp = device_create(freg_class, NULL, dev, "%s", FREG_DEVICE_FILE_NAME);
	if(IS_ERR(temp)) {
		err = PTR_ERR(temp);
		printk(KERN_ALERT"Failed to create freg device.\n");
		goto destroy_class;
	}

	/**
	 * dev_attr_val 结合宏 DEVICE_ATTR 理解
	 */
	err = device_create_file(temp, &dev_attr_val);
	if(err < 0) {
		printk(KERN_ALERT"Failed to create attribute val of freg device.\n");
        goto destroy_device;
	}

	dev_set_drvdata(temp, freg_dev);

	printk(KERN_ALERT"Succedded to initialize freg device.\n");

	return 0;

// 错误处理
destroy_device:
	device_destroy(freg_class, dev);
destroy_class:
	class_destroy(freg_class);
destroy_cdev:
	cdev_del(&(freg_dev->dev));	
cleanup:
	kfree(freg_dev);
unregister:
	unregister_chrdev_region(MKDEV(freg_major, freg_minor), 1);	
fail:
	return err;
}

// 驱动退出函数
static void __exit freg_exit(void) {
	dev_t devno = MKDEV(freg_major, freg_minor);

	printk(KERN_ALERT"Destroy freg device.\n");
	// 销毁设备类别和设备
	if(freg_class) {
		device_destroy(freg_class, MKDEV(freg_major, freg_minor));
		class_destroy(freg_class);
	}
	// 删除字符设备和释放设备内存
	if(freg_dev) {
		cdev_del(&(freg_dev->dev));
		kfree(freg_dev);
	}
	// 释放设备号资源
	unregister_chrdev_region(devno, 1);
}
// 驱动模块信息
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fake Register Driver");
// 注册驱动的初始化与卸载函数
module_init(freg_init);
module_exit(freg_exit);

安卓安装 Linux 驱动模块

adb push <linux 内核驱动模块> /data (安卓根文件系统中的路径)

insmod <linux 内核驱动模块名称>.ko      安装驱动模块
rmmod <linux 内核驱动模块名称>          卸载驱动模块
lsmod                                查看安装的模块
dmesg                                查看模块装、卸日志信息

测试 Linux 驱动模块

方法一,测试 /sys/class

方法二,编写 C 代码测试 /dev/freg

测试代码示例

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

#define FREG_DEVICE_NAME "/dev/freg"

int main(int argc, char** argv)
{
	int fd = -1;
	int val = 0;

	fd = open(FREG_DEVICE_NAME, O_RDWR);
	if(fd == -1)
	{
		printf("Failed to open device %s.\n", FREG_DEVICE_NAME);
		return -1;
	}
	
	printf("Read original value:\n");
	read(fd, &val, sizeof(val));
	printf("%d.\n\n", val);

	val = 5;
	printf("Write value %d to %s.\n\n", val, FREG_DEVICE_NAME);
        write(fd, &val, sizeof(val));

	
	printf("Read the value again:\n");
        read(fd, &val, sizeof(val));
        printf("%d.\n\n", val);

	close(fd);

	return 0;
}
LOCAL_PATH := $(call my-dir)
# 每个 Android.mk 文件必须以定义 LOCAL_PATH 为开始,它用于在开发 tree 中查找源文件;
# 宏 my-dir 则由 Build System 提供,返回包含 Android.mk 的目录路径;
include $(CLEAR_VARS)
# CLEAR_VARS 变量由 Build System 提供,并指向一个指定的 GNU Makefile;
# 由它负责清理很多 LOCAL_xxx,但不清理LOCAL_PATH
LOCAL_MODULE_TAGS := optional
# user 		: 指该模块只在user版本下才编译 
# eng 		: 指该模块只在eng版本下才编译 
# tests 	: 指该模块只在tests版本下才编译
# optional 	: 指该模块在所有版本下都编译
LOCAL_MODULE := freg
# LOCAL_MODULE 模块必须定义,以表示 Android.mk 中的每一个模块,名字必须唯一且不包含空格;
# Build System 会自动添加适当的前缀和后缀;
LOCAL_SRC_FILES := $(call all-subdir-c-files)
# LOCAL_SRC_FILES 变量必须包含将要打包如模块的 C/C++ 源码
# 不必列出头文件,build System 会自动帮我们找出依赖文件
include $(BUILD_EXECUTABLE)
# BUILD_STATIC_LIBRARY 		: 编译为静态库
# BUILD_SHARED_LIBRARY 		: 编译为动态库
# BUILD_EXECUTABLE 		: 编译为 Native C 可执行程序

基于 AIO-3399C 六核 AI 开发板编译驱动测试文件

source build/envsetup.sh
标准版:lunch rk3399_firefly_aioc-userdebug
AI  版:lunch rk3399_firefly_aioc_ai-userdebug
mmm ./external/freg/
编译成功后,结果将保存在 ./out/target/product/rk3399_firefly_aioc_ai/system/bin 之中
将编译好的可执行文件上传到 android 系统,
chmod 777 freg,改变文件性质
./freg 执行文件进行测试

注意 : 将驱动测试 C 源文件放置在 external 目录下执行编译

测试结果

常见问题总结

问题 1. implicit declaration of function ‘kmalloc’

解决方案 : 
添加 #include <linux/slab.h>


问题 2. init_MUTEX 的调用编译出错

解决方案 :
init_MUTEX 已经被禁用,改为 sema_init(&(dev->sem), 1);

问题 3. Android adb shell data 目录,Permission denied

解决方案 :
安卓系统的控制台中 su root 获取 root 权限
修改 /data 目录权限,chmod 777 /data
Windows 控制台中,adb push <Linux 驱动模块> /data;上传 Linux 驱动模块到安卓系统中;

问题 4. No command 'mmm' found

解决方案 :
使用m、mm、mmm命令之前要在android源码目录下执行命令
. build/envsetup.sh

 

参考致谢

本博客为博主学习笔记,同时参考了网上众博主的博文以及相关专业书籍,在此表示感谢,本文若存在不足之处,请批评指正。

【1】Android 系统源代码情景分析(第三版) [ M ] 罗升阳

【2】实现内核驱动程序模块

【3】驱动模块编译错误提示--implicit declaration of function ‘kmalloc’

【4】内核模块 (三) 安卓环境下编译ko文件

【5】在Ubuntu上为Android系统编写Linux内核驱动程序 + 编写加载动态模块ko

【6】No command 'mmm' found

【7】Android.mk 文件语法详解

【8】LOCAL_MODULE_TAGS 选项说明

【9】container of()函数简介

【10】深入浅出down_interruptible

【11】linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问

【12】信号量机制中的down和up函数

【13】device_create_file的使用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值