上圖爲我板子的Led硬件原理:通過 改變LED_CTL引腳的輸出電平 點亮Led 。低電平時紅燈亮,高電平時藍燈亮。
1、驅動部分爲上層提供 /dev/led 操作節點。
led.c:
/*********************************************************************************
* Copyright: (C) 2019 Arctan<2757904225.com>
* All rights reserved.
*
* Filename: mycdev.c
* Description: This file
*
* Version: 1.0.0(2019年04月10日)
* Author: Arctan
* ChangeLog: 1, Release initial version on "2019年04月10日 10時39分47秒"
*
********************************************************************************/
#include <linux/module.h> /* 模塊所需的大量符號和函數定義 */
#include <linux/init.h> /* 指定初始化和清除函數 */
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* struct fops */
#include <linux/errno.h> /* error codes */
#include <linux/cdev.h> /* cdev_alloc() */
#include <asm/uaccess.h> /*在內核和用戶空間中移動數據的函數copy_to_user和copy_from_user*/
#include <linux/device.h> /*包含了device、class 等結構的定義*/
#include <linux/slab.h> /* kmalloc()*/
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio.h>
#include <asm/ioctl.h> /* 系統空間的_IO 和 ioctl */
#ifndef __KERNEL__
#include <sys/ioctl.h> /* 用戶空間的_IO 和 ioctl */
#endif
#define DRV_AUTHOR "Arctan [email protected]"
#define DEV_NAME "led"
//#define DRV_NAME "mycdev_driver" //ioctl() 函數傳送的變量 cmd 是應用程序用於區別設備驅動程序請求處理內容的值
#define MAGIC_NUM 0x97 //ioctl()的cmd可以通過使用宏_IO()得到
#define LED_IOCTL_RED _IO(MAGIC_NUM, 0x01) //_IO (魔數, 基數);
#define LED_IOCTL_BLUE _IO(MAGIC_NUM, 0x02) //
#define LED_IOCTL_FLASH _IO(MAGIC_NUM, 0x03) //詳細解釋:
//#define GET_NUM _IO(MAGIC_NUM, 0x04) //http://blog.csdn.net/ghostyu/article/details/8085693
#define DEBUG_ON
#ifdef DEBUG_ON
#define Led_Dbg(fmt,arg...) do{\
printk("Led %s:%d "fmt"\n",__FUNCTION__,__LINE__,##arg);\
}while(0)
#else
#define Led_Dbg
#endif
static int Led_Ctrl;
int i_major = 0;
int i_minor = 0;
int private_data = 0; //驅動運行過程中可以用來保存中間數據,本例用來保存讀寫的數據
static struct cdev *LedDev; //字符設備結構體,每個字符設備對應一個結構體
static struct class *LedDev_class; //爲了實現設備節點文件自動掛載,class_create爲該設備創建一個class,再爲每個設備調用
//class_device_create創建對應的設備
static int LedDev_open(struct inode *inode, struct file *file) //用戶空間調用open函數,最終調用的是這裏的mycdev_open
{ //下面帶有read write ioctl字眼的函數同上
file->private_data = &private_data; //每個打開的文件都會對應一個內核分配的file結構體,file結構體中有一成員爲:
Led_Dbg("open \n"); //void *private_data,我們可以利用該成員進行數據的傳遞。
return 0;
}
static ssize_t LedDev_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
char rcvBuf[20];
Led_Dbg("mycdev read");
int ret = copy_from_user(rcvBuf,buf,len);
int cmdValue = rcvBuf[0]&0x0f;
Led_Dbg("--> mycdev read %d\n",cmdValue);
gpio_direction_output(Led_Ctrl,cmdValue);
return sizeof(int);
}
static ssize_t LedDev_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
char rcvBuf[20];
Led_Dbg("mycdev write ");
int ret = copy_from_user(rcvBuf,buf,len);
int cmdValue = rcvBuf[0]&0x0f;
Led_Dbg("cmdV : %d\n",cmdValue);
gpio_direction_output(Led_Ctrl,cmdValue);
return len;
}
static int LedDev_release(struct inode *inode, struct file *file) //用戶空間的close函數調用最終由本函數執行
{
printk("closed \n");
return 0;
}
static long CompatLedDev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int *data = file->private_data; //驅動裏的private_data ==》file->private_data ==》 data
Led_Dbg("Compate ioctl cmd is %d ",cmd);
switch(cmd)
{
case LED_IOCTL_RED:
gpio_direction_output(Led_Ctrl,1); // 紅燈
Led_Dbg("ioctl : LED_RED\n");
break;
case LED_IOCTL_BLUE:
gpio_direction_output(Led_Ctrl,0); // 藍燈
Led_Dbg("ioctl : LED_BLUE\n");
break;
case LED_IOCTL_FLASH:
break;
default:Led_Dbg("unkown commad\n");
}
return 0;
}
static long unLockLedDev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int *data = file->private_data; //驅動裏的private_data ==》file->private_data ==》 data
Led_Dbg("unLock ioctl cmd is %d ",cmd);
switch(cmd)
{
case LED_IOCTL_RED:
gpio_direction_output(Led_Ctrl,1); // 紅燈
Led_Dbg("ioctl : LED_RED\n");
break;
case LED_IOCTL_BLUE:
gpio_direction_output(Led_Ctrl,0); // 藍燈
Led_Dbg("ioctl : LED_BLUE\n");
break;
case LED_IOCTL_FLASH:
break;
default:Led_Dbg("unkown commad\n");
}
return 0;
}
static struct file_operations fops = // file_operation結構體詳解
{ //http://blog.csdn.net/l627859442/article/details/7513817
.owner = THIS_MODULE, //就是這個結構體決定了用戶空間的各種文件操作函數最終調用的實際函數
.write = LedDev_write,
.read = LedDev_read,
.open = LedDev_open,
.release = LedDev_release,
.unlocked_ioctl = unLockLedDev_ioctl,
.compat_ioctl = CompatLedDev_ioctl,
};
static const struct of_device_id of_gpio_match[] = {
{ .compatible = "videostrong,gpio" },
{ /*Sentinel*/ }
};
static int Led_probe(struct platform_device *pdev){
int ret = -1;
enum of_gpio_flags flag;
struct device_node* led_node = pdev->dev.of_node;
Led_Ctrl = of_get_named_gpio_flags(led_node,"POWR_LED",0,&flag);
if(!gpio_is_valid(Led_Ctrl)){
Led_Dbg("The Led_Ctr is not valid %d\n",Led_Ctrl);
}
ret = gpio_request(Led_Ctrl,"Led_Ctrl");
if(ret!=0){
gpio_free(Led_Ctrl);
Led_Dbg("Gpio requese Led_Ctrl fail");
}
return ret;
}
static int Led_remove(struct platform_device *pdev){
return 0;
}
static struct platform_driver Led_driver={
.probe = Led_probe,
.remove = Led_remove,
.driver ={
.name = "led_gpio",
.owner = THIS_MODULE,
.of_match_table = of_gpio_match,
},
};
static int __init Led_cdev_init(void) //整個驅動的執行起點
{
int ret;
int devno;
int result = platform_driver_register(&Led_driver); // 平臺硬件初始化
if(result){
Led_Dbg("platform register fail");
return -1;
}
if( 0 != i_major) //如果已經指定了主設備號
{
devno = MKDEV(i_major, 0); //產生設備號,MKDEV(主設備號,次設備號)。
ret = register_chrdev_region (devno, 1, DEV_NAME); //靜態註冊字符設備號
}
else
{
ret = alloc_chrdev_region(&devno, i_minor, 1, DEV_NAME); //動態註冊字符設備號,保證不和現有設備號衝突
i_major = MAJOR(devno); //由設備號計算出主設備號
}
if( ret < 0 )
{
Led_Dbg("%s driver can't use major %d\n", DEV_NAME, i_major);
return -ENODEV;
}
Led_Dbg("%s driver use major %d\n", DEV_NAME, i_major);
if(NULL == (LedDev = cdev_alloc()) ) //靜態內存定義初始化:
{ //struct cdev my_cdev;
Led_Dbg(KERN_ERR "%s driver can't alloc for the cdev.\n", DEV_NAME); //cdev_init(&my_cdev, &fops);
unregister_chrdev_region(devno, 1); //my_cdev.owner = THIS_MODULE;
return -ENOMEM; //動態內存定義初始化:
} //struct cdev *my_cdev = cdev_alloc();
//my_cdev->ops = &fops;
LedDev->owner = THIS_MODULE; //my_cdev->owner = THIS_MODULE;
LedDev->ops = &fops; //
ret = cdev_add(LedDev, devno, 1); //向內核添加cdev結構體
if (0 != ret)
{
Led_Dbg("%s driver can't reigster cdev: result=%d\n", DEV_NAME, ret);
goto ERROR;
}
LedDev_class = class_create(THIS_MODULE, DEV_NAME);
device_create(LedDev_class, NULL, MKDEV(i_major ,0), NULL, DEV_NAME); //在系統/dev/目錄下動態創建節點
Led_Dbg(KERN_ERR "%s driver[major=%d] installed successfully!\n", DEV_NAME, i_major);
return 0;
ERROR:
Led_Dbg("%s driver installed failure.\n", DEV_NAME);
cdev_del(LedDev); //釋放 cdev佔用的內存
unregister_chrdev_region(devno, 1); //解除註冊
return ret;
}
static void __exit Led_cdev_exit(void) //驅動卸載時執行的函數
{
dev_t devno = MKDEV(i_major, i_minor);
cdev_del(LedDev);
device_destroy(LedDev_class, devno); //刪除設備節點
class_destroy(LedDev_class);
unregister_chrdev_region(devno, 1);
platform_driver_unregister(&Led_driver);
Led_Dbg(KERN_ERR "%s driver removed!\n", DEV_NAME);
return ;
}
module_init(Led_cdev_init); //指定驅動入口函數
module_exit(Led_cdev_exit); //指定驅動卸載函數
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DEV_NAME);
MODULE_LICENSE("GPL");
2、增加HAL層驅動模塊
(1) hal 層的驅動主要有三個重要的結構體
struct hw_module_t; //模塊類型
struct hw_module_methods_t; //模塊方法
struct hw_device_t; //設備類型,向上提供接口
如果我們要寫一個自定義設備的驅動的HAL層時,我們得首先自定義兩個數據結構:
/*定義模塊ID*/
#define XXX_HARDWARE_MODULE_ID "XXX"
/*硬件模塊結構體*/
//見hardware.h中的hw_module_t定義的說明,xxx_module_t的第一個成員必須是hw_module_t類型,其次纔是模塊的一此相關信息,當然也可以不定義,
//這裏就沒有定義模塊相關信息
struct xxx_module_t {
struct hw_module_t common;
};
/*硬件接口結構體*/
//見hardware.h中的hw_device_t的說明,要求自定義xxx_device_t的第一個成員必須是hw_device_t類型,其次纔是其它的一些接口信息.
struct xxx_device_t {
struct hw_device_t common;
//以下成員是HAL對上層提供的接口或一些屬性
int fd;
int (*set_val)(struct xxx_device_t* dev, int val);
int (*get_val)(struct xxx_device_t* dev, int* val);
};
(2)創建led.h文件
#ifndef LED_H
#define LED_H
#include <hardware/hardware.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define LED_HARDWARE_MODULE_ID "led"
struct led_module_t {
struct hw_module_t common;
};
struct led_control_device_t {
struct hw_device_t common;
int fd;
int (*set_blue_on)(struct led_control_device_t *dev, int led);
int (*set_red_on)(struct led_control_device_t *dev, int led);
};
#endif
(3)如下創建led文件夾 如下文件添加 led文件加 添加 led.c 和 Android.mk
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := led.default
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
led.c
/*************************************************************************
> File Name: led.cpp
> Author: Arctan
> Mail: [email protected]
> Created Time: Tue 06 Aug 2019 02:16:44 AM PDT
************************************************************************/
#define LOG_TAG "Led"
#include <hardware/hardware.h>
#include <hardware/led.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#define MAGIC_NUM 0X97
#define LED_IOCTL_RED _IO(MAGIC_NUM,0X01)
#define LED_IOCTL_BLUE _IO(MAGIC_NUM,0X02)
#define LED_IOCTL_FLASH _IO(MAGIC_NUM,0X03)
#define DEVICE_NAME "/dev/led"
int led_device_close(struct hw_device_t* device){
struct led_control_device_t* pdev = (struct led_control_device_t*)device;
if(pdev!=NULL){
close(pdev->fd);
free(pdev);
pdev = NULL;
}
return 0;
}
int led_blue_on(struct led_control_device_t *pled,int led){
if(pled->fd<0){
return -1;
}
ALOGI("Led set blue %d\n",led);
ioctl(pled->fd,LED_IOCTL_BLUE,NULL);
return 0;
}
int led_red_on(struct led_control_device_t *pled,int led){
if((pled==NULL)||(pled->fd<0)){
return -1;
}
ALOGI("Led set red %d\n",led);
ioctl(pled->fd,LED_IOCTL_RED,NULL);
return 0;
}
static int led_device_open(const hw_module_t* module, const char* name,
hw_device_t** device){
struct led_control_device_t *dev;
dev = (struct led_control_device_t *)calloc(1,sizeof(struct led_control_device_t));
if(!dev){
ALOGE("led device open error");
return -ENOMEM;
}
ALOGI("Led enter led open ");
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t*)module;
dev->common.close = led_device_close;
dev->set_blue_on = led_blue_on;
dev->set_red_on = led_red_on;
dev->fd = open(DEVICE_NAME,O_RDWR);
if(dev->fd<0){
ALOGI("Led open /dev/led fail ");
}else{
ALOGI("Led open /dev/led success");
}
*device = &(dev->common);
return 0;
}
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
struct led_module_t HAL_MODULE_INFO_SYM ={
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LED_HARDWARE_MODULE_ID,
.name = "LED FLASH HW HAL",
.author = "Arctan project",
.methods = &led_module_methods,
},
};
至此驅動層和hal層驅動模塊已經完成,編譯代碼燒寫層序看看有沒有生成相應的 *.so 文件和 /dev/led節點。
3.JNI層,編寫JNI方法,在應用程序框架層提供Java接口訪問硬件
jni在註冊方法時用到com/android/server/led/LedService
所以在frameworks/base/services/core/java/com/android/server/目錄下新建led文件夾
在Android系統中,硬件服務一般是運行在一個獨立的進程中爲各種應用程序提供服務。
因此,調用這些硬件服務的應用程序與這些硬件服務之間的通信需要通過代理來進行
創建aidl文件來描述通信接口
frameworks/base/core/java/android/os/ILedService.aidl
package android.os;
/**
* {@hide}
*/
interface ILedService {
boolean setOn();
boolean setOff();
}
frameworks/base/services/core/java/com/android/server/led/LedService.java
/*************************************************************************
> File Name: com_android_server_led_LedService.cpp
> Author: Arctan
> Mail: [email protected]
> Created Time: Tue 06 Aug 2019 07:52:44 PM PDT
************************************************************************/
#include <iostream>
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/led.h>
#include <stdio.h>
namespace android{
struct led_control_device_t* ledDevices = NULL;
static inline int led_control_open(const struct hw_module_t *module, struct led_control_device_t** device){
return module->methods->open(module, LED_HARDWARE_MODULE_ID,
(struct hw_device_t**)device);
}
static jboolean led_blue(JNIEnv *env,jobject clazz,jint led){
ALOGI("jni led blue");
if(ledDevices ==NULL){
ALOGI("led bulue ledDevices == NULL");
return -1;
}else{
return ledDevices->set_blue_on(ledDevices,led); //藍燈
}
}
static jboolean led_red(JNIEnv* env,jobject clazz,jint led){
ALOGI("jni led blue");
if(ledDevices ==NULL){
ALOGI("led bulue ledDevices == NULL");
return -1;
}else{
return ledDevices->set_red_on(ledDevices,led); //藍燈
}
}
static jboolean led_init(JNIEnv* env,jclass clazz){
struct led_module_t* ledModule;
ALOGI("--> enter LedService.cpp init %s",LED_HARDWARE_MODULE_ID);
if((hw_get_module(LED_HARDWARE_MODULE_ID,(const struct hw_module_t**)&ledModule))==0){
ALOGI("get Led MOUDLE SUCCESS");
if(led_control_open(&ledModule->common,&ledDevices)==0){
ALOGI("led_control open success");
return 0;
}else{
return -1;
}
}
ALOGI("led init fail");
return -1;
}
static const JNINativeMethod method_table[]={
{"init_native","()Z",(void*)led_init},
{"led_blue","(I)Z",(void*)led_blue},
{"led_red","(I)Z",(void*)led_red},
};
int register_android_server_LedService(JNIEnv *env){
return jniRegisterNativeMethods(env, "com/android/server/led/LedService", method_table, NELEM(method_table));
}
};
(1)、frameworks/base/services/core/jni/Android.mk
(2)、在 frameworks/base/Android.mk裏增加對aidl的編譯項
4、增加Manger 來管理相應的服務
frameworks/base/core/java/android/os/LedManager.java
//*************************************************************************
// > File Name: LedManager.java
// > Author: Arctan
// > Mail: [email protected]
// > Created Time: Tue 06 Aug 2019 11:16:45 PM PDT
// ************************************************************************/
package android.os;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.util.Log;
import android.os.ILedService;
public class LedManager{
private static final String TAG="LedManager";
private ILedService mILedService;
private Context mContext;
public LedManager(Context context,ILedService service){
mContext = context;
mILedService = service;
}
public boolean LedBlue(){
try{
Log.i(TAG,"enter set on");
return mILedService.setBlue();
}catch(RemoteException e){
Log.e(TAG,"set on fail ");
return false;
}
}
public boolean LedRed(){
try{
Log.i(TAG,"ENTER SET OFF");
return mILedService.setRed();
}catch(RemoteException e){
Log.e(TAG,"set on fail ");
return false;
}
}
}
5.添加註冊服務,addService開機服務就可以啓動
frameworks/base/services/java/com/android/server/SystemServer.java
6.更新API
make update-api -j16
7.修改節點權限
Android在Selinux下要獲取對內核節點訪問的權限,需要修改.te文件
1)爲/dev/led節點定義一個名字led_device
--- a/system/sepolicy/file_contexts
+++ b/system/sepolicy/file_contexts
@@ -8,6 +8,7 @@
/dev/ttymxc[0-9]* u:object_r:tty_device:s0
/dev/ttyUSB[0-9]* u:object_r:tty_device:s0
/dev/mma8x5x u:object_r:sensors_device:s0
+/dev/led u:object_r:led_device:s0
2)將led_device聲明爲dev_type
--- a/system/sepolicy/device.te
+++ b/system/sepolicy/device.te
@@ -1,2 +1,3 @@
type caam_device, dev_type;
type pxp_device, dev_type;
+type led_device,dev_type;
3)修改讀寫權限
--- a/system/sepolicy/system_server.te
+++ b/system/sepolicy/system_server.te
@@ -4,3 +4,4 @@ allow system_server system_data_file:file {relabelto rw_file_perms};
allow system_server system_data_file:dir {relabelto rw_dir_perms};
allow system_server kernel:system { syslog_read };
allow system_server debugfs_tracing:file { write };
+allow system_server led_device:chr_file { open read write ioctl };
4)修改linux下節點自身的權限
--- a/device/rockchip/common/ueventd.rockchip.rc
+++ b/device/rockchip/common/ueventd.rockchip.rc
@@ -1,3 +1,4 @@
+/dev/led 0660 system system
5)增加服務的權限
--- a/system/sepolicy/service_contexts
+++ b/system/sepolicy/service_contexts
@@ -116,6 +116,7 @@ scheduling_policy u:object_r:scheduling_policy_service:s
search u:object_r:search_service:s0
sensorservice u:object_r:sensorservice_service:s0
serial u:object_r:serial_service:s0
+led u:object_r:led_service:s0
--- a/system/sepolicy/service.te
+++ b/system/sepolicy/service.te
@@ -96,6 +96,7 @@ type rttmanager_service, app_api_service, system_server_service, service_manager
type samplingprofiler_service, system_server_service, service_manager_type;
type scheduling_policy_service, system_server_service, service_manager_type;
type search_service, app_api_service, system_server_service, service_manager_type;
+type led_service, app_api_service, system_server_service, service_manager_type;
8.修改 android 自帶app 添加相應代碼
也可以 將out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar裏的
android/os/LedManager.class
導入eclipse 的sdk/platforms/android-xx/android.jar包中
的android/os目錄 自行編寫測試app