本人才疏浅学,写一篇文档总结自己在msm8916平台上移植自己编写的简单的字符设备驱动开发的整个流程。这个小项目的主要功能是开发一个简单的APP,APP通过JNI去调用位于kernel的字符设备驱动。
APP的设计,开发平台Android Studio
主要的文件是下面的三个文件:
MainActivity.java文件的内容如下:
package com.example.administrator.myled; import android.nfc.Tag;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast; import com.zbahuang.led.lowlevel.LedNative; public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "zbzhuang";
Button btn_led_on;
Button btn_led_off;
LedNative myled; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); initUI(); myled = new LedNative();
myled.openDev();
Log.d(TAG,"app:open Dev");
} private void initUI() {
btn_led_on = (Button) findViewById(R.id.btn_led_on);
btn_led_on.setOnClickListener(this); btn_led_off = (Button) findViewById(R.id.btn_led_off);
btn_led_off.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_led_on:
Toast.makeText(MainActivity.this,"拉灯-->",Toast.LENGTH_SHORT).show();
Log.d(TAG,"app:LED on");
myled.devOn();
break;
case R.id.btn_led_off:
Toast.makeText(MainActivity.this,"灭灯-->",Toast.LENGTH_SHORT).show();
Log.d(TAG,"app:LED off");
myled.devOff();
break;
default:
break;
} } @Override
protected void onDestroy() {
super.onDestroy();
myled.closeDev();
Log.d(TAG,"app:close Dev");
}
}
LedNative.java文件的内容如下:
在这个文件中所声明的方法是在jni中实现的啊。使用了特殊的关键字表示该方法是在JNI当中实现的啊。
package com.zbahuang.led.lowlevel; /**
* Created by Administrator on 2017/3/29 0029.
*/ public class LedNative { static {
System.loadLibrary("led_jni");
} public native int openDev();
public native int devOn();
public native int devOff();
public native int closeDev();
}
package com.zbahuang.led.lowlevel; /**
* Created by Administrator on 2017/3/29 0029.
*/ public class LedNative { static {
System.loadLibrary("led_jni");
} public native int openDev();
public native int devOn();
public native int devOff();
public native int closeDev();
}
activity_main.xml文件的内容如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.administrator.myled.MainActivity"> <RelativeLayout
android:layout_width="394dp"
android:layout_height="520dp"
tools:layout_editor_absoluteX="-5dp"
tools:layout_editor_absoluteY="-10dp"> <Button
android:id="@+id/btn_led_on"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="56dp"
android:layout_marginTop="109dp"
android:text="拉灯" /> <Button
android:id="@+id/btn_led_off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/btn_led_on"
android:layout_alignBottom="@+id/btn_led_on"
android:layout_marginStart="81dp"
android:layout_toEndOf="@+id/btn_led_on"
android:text="灭灯" />
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
JNI的文件:
led_jni.cpp
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> #define LOG_TAG "zbzhuang"
#include <utils/Log.h> #include "jni.h" static jint fd; static jint open_led(JNIEnv *env,jobject thiz)
{
ALOGD("JNI:-----------%s--------------",__FUNCTION__); fd = open("/dev/led1",O_RDWR);
if(fd < ){
ALOGD("JNI:open error:%s\n",strerror(errno));
return -;
} return ;
} static jint led_on(JNIEnv *env,jobject thiz)
{
ALOGD("JNI:-----------%s--------------",__FUNCTION__);
jint ret ;
jint on = ; ret = write(fd,&on,);
if(ret < ){
ALOGD("JNI:write off error:%s\n",strerror(errno));
return -;
} return ;
} static jint led_off(JNIEnv *env,jobject thiz)
{
ALOGD("JNI:-----------%s--------------",__FUNCTION__);
jint ret;
jint off = ; ret = write(fd,&off,);
if(ret < ){
ALOGD("JNI:write off error:%s\n",strerror(errno));
return -;
} return ;
} static jint close_led(JNIEnv *env,jobject thiz)
{
ALOGD("JNI:-----------%s--------------",__FUNCTION__);
close(fd); return ;
} const JNINativeMethod led_jni_methods[] = {
{"openDev","()I",(void *)open_led},
{"devOn","()I",(void *)led_on},
{"devOff","()I",(void *)led_off},
{"closeDev","()I",(void *)close_led}, }; jint JNI_OnLoad(JavaVM * vm,void * reserved)
{
JNIEnv *env = NULL;
jint ret ; ALOGD("%s[%s:%d]JNI:--------------^_&--------------------\n",__func__,__FILE__,__LINE__);
ret = vm->GetEnv((void * *)&env,JNI_VERSION_1_4);
if(ret != JNI_OK){
ALOGE("JNI:vm->GetEnv error");
return -;
} jclass clz = env->FindClass("com/zbahuang/led/lowlevel/LedNative"); if(clz == NULL){
ALOGE("%s[%s:%d]JNI:env->FindClass error",__func__,__FILE__,__LINE__);
return -;
} ret = env->RegisterNatives(clz,
led_jni_methods,
sizeof(led_jni_methods)/sizeof(led_jni_methods[])); if(ret < ){
ALOGE("%s[%s:%d]JNI:env->RegisterNatives error\n",__func__,__FILE__,__LINE__);
return -;
} return JNI_VERSION_1_4; }
Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_MODULE:= libled_jni LOCAL_SRC_FILES:= \
led_jni.cpp LOCAL_SHARED_LIBRARIES := \
libutils liblog LOCAL_C_INCLUDES += \
$(JNI_H_INCLUDE) include $(BUILD_SHARED_LIBRARY)
执行: mmm mytest/led_jni/ 之后会生成动态库放在 out/target/product/msm8916_64/obj/lib/libled_jni.so
将这个库推送到平板电脑就可以通过这个库去调用驱动。
内核的驱动文件:kernel/drivers/input/misc/led.c
/*1. 头文件*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/slab.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<asm/io.h> static unsigned int led_major=;
volatile unsigned long *gpc0con = NULL;
volatile unsigned long *gpc0dat = NULL; struct led_device{
struct class *led_class ; //表示一类设备, 存储某些信息
struct device *led_device ; //表示一个设备
struct cdev *led_cdev;
unsigned int val; }; struct led_device *s5pv_led_dev; /*可用于查询LED的状态*/
static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *opps)
{
int ret; ret = copy_to_user(buf, &s5pv_led_dev->val, count);
if(ret>)
{
printk(KERN_ERR "zbzhuang### copy to user failed!\n");
return ret;
} printk(KERN_INFO "zbzhuang### val=%d\n", s5pv_led_dev->val); return ret?:count;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *opps)
{
int ret; /*拷贝成功,返回0; 拷贝失败, 返回没有被拷贝的字节数*/
ret = copy_from_user(&s5pv_led_dev->val, buf, count);
if(ret>)
{
printk(KERN_ERR "zbzhuang### copy from user failed!\n");
return ret;
} if(s5pv_led_dev->val)
{
/*点亮LED*/
//*gpc0dat |= ((0x1<<3)|(0x1<<4));
printk(KERN_ERR "zbzhuang### led on\n");
}
else
{
/*熄灭LED*/
// *gpc0dat &= ~((0x1<<3)|(0x1<<4));
printk(KERN_ERR "zbzhuang### led off\n");
} return ret?:count;
} static int led_open(struct inode *inode, struct file *file)
{
#if 0
/*1. 将物理地址映射为虚拟地址*/
gpc0con = ioremap(0xE0200060, );
gpc0dat = gpc0con +; /*2. 初始化GPC0_3,4引脚功能为输出*/
*gpc0con &= ~((0xf<<)|(0xf<<));
*gpc0con |= ((0x1<<)|(0x1<<));
#endif
printk(KERN_ERR "zbzhuang### -----------%s-------------\n",__FUNCTION__); return ;
} static int led_close(struct inode *inode, struct file *file)
{
#if 0 *gpc0con &= ~((0xf<<)|(0xf<<));
iounmap(gpc0con);
#endif
printk(KERN_ERR "zbzhuang### ------------%s-----------------\n",__FUNCTION__); return ; } /*硬件操作方法*/
struct file_operations led_fops={
.owner = THIS_MODULE, //当前模块所用
.open = led_open,
.write = led_write,
.read = led_read,
.release = led_close, }; static void setup_led_cdev(void)
{
/*1. 为cdev结构体分配空间*/
s5pv_led_dev->led_cdev = cdev_alloc(); /*2. 初始化cdev结构体*/
cdev_init(s5pv_led_dev->led_cdev, &led_fops); /*3. 注册cdev,加载到内核哈希表中*/
cdev_add(s5pv_led_dev->led_cdev, MKDEV(led_major, ), ); } /*2. 实现模块加载函数*/
static int __init led_init(void)
{
dev_t devno;
int ret;
/*1. 新的申请主设备号的方法*/
if(led_major)
{
/*静态申请*/
devno = MKDEV(led_major, );
register_chrdev_region(devno, , "led");
}
else
{
/*动态申请*/
alloc_chrdev_region(&devno, , , "led");
led_major = MAJOR(devno);
} /*2. 为本地结构体分配空间*/ /*
** param1: 大小
** param2: 标号: GFP_KERNEL--->表示如果分配不成功,则休眠
*/
s5pv_led_dev = kmalloc(sizeof(struct led_device), GFP_KERNEL);
if (!s5pv_led_dev)
{
printk(KERN_ERR "zbzhuang NO memory for malloc!\n");
ret = -ENOMEM;
goto out_err_1;
} /*3. 构建struct cdev结构体*/
setup_led_cdev(); /*4. 创建设备文件*/
/*
** param1: struct class
** param2: 父类, 一般为NULL
** param3: dev_t ---> 表示一个设备号, 是一个无符号32位整形
** 其中高12位为主设备号, 低20为次设备号
** 如何操作设备号: MKDEV(major, minor)根据主设备号和次设备号组成一个设备号
*/ s5pv_led_dev->led_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(s5pv_led_dev->led_class)) {
printk(KERN_ERR "zbzhuang class_create() failed for led_class\n");
ret = -EINVAL;
goto out_err_2;
} s5pv_led_dev->led_device = device_create(s5pv_led_dev->led_class, NULL, MKDEV(led_major, ), NULL, "led1"); // 创建设备文件/dev/led1
if (IS_ERR(s5pv_led_dev->led_device)) {
printk(KERN_ERR "zbzhuang device_create failed for led_device\n");
ret = -ENODEV;
goto out_err_3;
} return ; out_err_3:
class_destroy(s5pv_led_dev->led_class); out_err_2:
cdev_del(s5pv_led_dev->led_cdev);
kfree(s5pv_led_dev); out_err_1:
unregister_chrdev_region(MKDEV(led_major, ), );
return ret; } /*3. 实现模块卸载函数*/
static void __exit led_exit(void)
{
unregister_chrdev_region(MKDEV(led_major, ), );
device_destroy(s5pv_led_dev->led_class, MKDEV(led_major, ));
class_destroy(s5pv_led_dev->led_class);
cdev_del(s5pv_led_dev->led_cdev);
kfree(s5pv_led_dev);
} /*4. 模块许可声明*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
修改kconfig和makefile,在内核当中添加:
makefile
kconfig:
修改这个文件将驱动编译进内核:kernel/arch/arm64/configs/msm_defconfig 与msm-perf_deconfig
之后使用adb工具将重新编译的boot.imge镜像重新烧录就可以了啊。
看看log的输出验证一下结果:
android studio查看log的结果:
adb查看log的结果:
从app到jni到kernel,整个调用的过程成功。
如果出现打开设备失败的话在system/core/rootdir/uevent.rc中添加设备节点的权限777即可。