Android GPIO LED 驅動與HAL分析

轉載:http://blog.csdn.net/fyyy4030/article/details/7207953

前言:

以一個GPIO控制的GPIO LED爲例,描述Android系統中,如何完成一個最簡單的從軟件控制硬件的示例:

l  如何完成一個最簡單的驅動程序控制某個GPIO引腳

l  如何在Android系統中建立這個驅動程序對應的HAL

l  如何使上層應用程序通過HAL來控制驅動程序

 

1           總體結構

modkoid工程提供了一個LedTest示例程序,是臺灣的Jollen用於培訓的。

 

原始工程下載方法:

#svn checkout http://mokoid.googlecode.com/svn/trunk/mokoid-read-only

本文所使用的代碼基於硬件(s5pc100開發板)做了部分修改。

http://download.csdn.net/source/3542132

 

 

2           HAL在Android系統中的位置

 

 

3           驅動程序

本部分共兩個文件。一個是led_drv.ko,這是驅動程序;另一個是:main,這個實際上main.c生成的測試程序。它可以通過ioctl來控制驅動程序,測試驅動程序是否達到目標。

 

3.1          驅動程序初始化和退出

static int simple_major = 250;//默認的設備號碼,如果爲0則嘗試自動分配

……

/*

 * Set up the cdev structure for a device.

 */

static void simple_setup_cdev(struct cdev *dev, int minor,

               struct file_operations *fops)//自編的函數,註冊字符設備

{

      int err, devno = MKDEV(simple_major, minor);//建立設備號

 

      cdev_init(dev, fops);//初始化設備結構體struct cdev *dev

      dev->owner = THIS_MODULE;

      dev->ops = fops;//關聯fops

      err = cdev_add (dev, devno, 1);//註冊一個字符設備

      /* Fail gracefully if need be */

      if (err)//註冊失敗處理

               printk (KERN_NOTICE "Error %d adding simple%d", err, minor);

}

/*

 * Our various sub-devices.

 */

/* Device 0 uses remap_pfn_range */

static struct file_operations simple_remap_ops = { //定義設備的fops

      .owner   = THIS_MODULE,

      .open    = simple_open,

      .release = simple_release,

      .read    = simple_read,

      .write   = simple_write,

      .ioctl   = simple_ioctl,  

};

 

/*

 * We export two simple devices.  There's no need for us to maintain any

 * special housekeeping info, so we just deal with raw cdevs.

 */

static struct cdev SimpleDevs;

 

/*

 * Module housekeeping.

 */

static struct class *my_class;

static int simple_init(void)

{

      int result;

      dev_t dev = MKDEV(simple_major, 0);//將設備號轉化爲dev_t的結構

 

      /* Figure out our device number. */

      if (simple_major)

               result = register_chrdev_region(dev, 1, "simple");//嘗試申請主設備號

      else {

               result = alloc_chrdev_region(&dev, 0, 1, "simple");//請求自動分配主設備號,起始值是0,總共分配1個,設備名simple

               simple_major = MAJOR(dev);//將分配成功的設備號保存在simple_major變量中

      }

      if (result < 0) {//分配主設備號失敗

               printk(KERN_WARNING "simple: unable to get major %d\n", simple_major);

               return result;

      }

      if (simple_major == 0)//將返回值記錄爲主設備號。需要麼?

               simple_major = result;

 

      /* Now set up two cdevs. */

      simple_setup_cdev(&SimpleDevs, 0, &simple_remap_ops);//調用自編的函數註冊字符設備,有Bug沒有返回註冊是否成功。

      printk("simple device installed, with major %d\n", simple_major);//Bug:打印前應該檢查註冊是否成功?

 

      my_class= class_create(THIS_MODULE, "simple");//建立一個叫simple的內核class,目的是下一步創建設備節點文件

      device_create(my_class, NULL, MKDEV(simple_major, 0),

                     NULL, "led");//創建設備節點文件

      return 0;

}

 

 

static void simple_cleanup(void)

{

      cdev_del(&SimpleDevs);//刪除字符設備

      unregister_chrdev_region(MKDEV(simple_major, 0), 1);//註銷主設備號

      device_destroy(my_class,MKDEV(simple_major,0));//刪除設備節點

      printk("simple device uninstalled\n");

}

 

module_init(simple_init);

module_exit(simple_cleanup);

 

3.2          驅動程序Open and release 函數

 

//寄存器地址,見CPU手冊70頁

#define pGPG3CON 0xE03001C0

#define pGPG3DAT 0xE03001C4

//寄存器操作指針

static void *vGPG3CON , *vGPG3DAT;

#define GPG3CON (*(volatile unsigned int *) vGPG3CON)

#define GPG3DAT (*(volatile unsigned int *) vGPG3DAT)

 

static int simple_major = 250;//默認的主設備號

module_param(simple_major, int, 0);//向內核申明一個參數,可以在insmod的時候傳遞給驅動程序

MODULE_AUTHOR("farsight");

MODULE_LICENSE("Dual BSD/GPL");

 

/*

 * Open the device; in fact, there's nothing to do here.

 */

int simple_open (struct inode *inode, struct file *filp)

{

      vGPG3CON=ioremap(pGPG3CON,0x10);//io remap地址pGPG3CON到變量

      vGPG3DAT=vGPG3CON+0x04;//計算vGPG3DAT寄存器的地址

      GPG3CON=0x1111;//使用宏設定寄存器初始值

      GPG3DAT=0xff; //使用宏設定寄存器初始值

      return 0;

}

 

ssize_t simple_read(struct file *file, char __user *buff, size_t count, loff_t *offp)

{

      return 0;

}

 

ssize_t simple_write(struct file *file, const char __user *buff, size_t count, loff_t *offp)

{

      return 0;

}

 

……

 

static int simple_release(struct inode *node, struct file *file)

{

      return 0;

}

 

3.3          驅動程序ioctl函數

 

//ioctl命令值,LED ON ,LED OFF

#define LED_ON 0x4800

#define LED_OFF 0x4801

 

void led_off( void )

{

      GPG3DAT=GPG3DAT|(1<<2);//通過宏設定寄存器的值,置1

      //printk("stop led\n");

}

void led_on( void )

 

{

      GPG3DAT=GPG3DAT&(~(1<<2)); //通過宏設定寄存器的值,置0

      //printk("start led\n");

}

 

static int simple_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)

{

      switch ( cmd )

      {

               case LED_ON://判斷命令

                         {

                                  led_on();//執行命令

                                  break;

                         }

               case LED_OFF:

                         {

                                  led_off();

                                  break;

                         }

               default:

                         {

                                  break;

                         }

      }

      return 0;

}

 

3.4          驅動測試程序main.c

 

/*

 * main.c : test demo driver

 */

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#define LED_ON 0x4800

#define LED_OFF 0x4801

int main()

{

      int i = 0;

      int dev_fd;

      dev_fd = open("/dev/simple",O_RDWR | O_NONBLOCK);//打開設備文件

      if ( dev_fd == -1 ) {

               printf("Cann't open file /dev/simple\n");

               exit(1);

      }

      while(1)

      {

      ioctl(dev_fd,LED_ON,0);//發送ioctl命令LED_ON

  //     printf("on\n");

      sleep(1);

      ioctl(dev_fd,LED_OFF,0); //發送ioctl命令LED_OFF

//     printf("off\n");

               sleep(1);

}

return 0;

}

 

4           HAL

4.1          Hardware Stub (運行在用戶空間,直接操作設備文件,對上提供操作接口)

本層生成一個so文件,作爲Android HAL層的Stub。按照Android HAL的要求,此文件必須放在固定的目錄下面,並且具有特定的文件名。

規則如下:

# HAL module implemenation, not prelinked and stored in

# hw/<OVERLAY_HARDWARE_MODULE_ID>.<ro.product.board>.so

 

本例中:#define LED_HARDWARE_MODULE_ID "led"

所以生成:system/lib/hw/led.default.so

 

注:hardware\libled目錄下的文件沒用。只用hardware\modules目錄下的文件用用。

 

 

4.1.1     頭文件

 

#include <hardware/hardware.h>

 

#include <fcntl.h>

#include <errno.h>

 

#include <cutils/log.h>

#include <cutils/atomic.h>

 

/********************************************************/

 

struct led_module_t {//定義了一個繼承自hw_module_t的結構,記錄本stub的基本信息和入口

   struct hw_module_t common;

};

 

struct led_control_device_t {//定義一個繼承自hw_device_t的結構記錄本stub操作設備時需要包括的接口

   struct hw_device_t common;

 

   /* attributes */

   int fd;//文件句柄

 

//下面是操作接口

   /* supporting control APIs go here */

   int (*set_on)(struct led_control_device_t *dev, int32_t led);

   int (*set_off)(struct led_control_device_t *dev, int32_t led);

};

 

/**********************************************/

 

struct led_control_context_t {//定義一個繼承自device結構的上下文結構

      struct led_control_device_t device;

};

 

#define LED_HARDWARE_MODULE_ID "led"//定義HAL 的模塊ID

 

4.1.2     C文件

4.1.2.1    入口定義

 

//定一個hw_module_methods_t結構體,關聯入口函數

static struct hw_module_methods_t led_module_methods = {

    open: led_device_open

};

 

//定義Stub入口

//注意必須使用:

//1。hw_module_t繼承類

//2。必須使用HAL_MODULE_INFO_SYM這個名字

const struct led_module_t HAL_MODULE_INFO_SYM = {

    common: {

        tag: HARDWARE_MODULE_TAG,

        version_major: 1,

        version_minor: 0,

        id: LED_HARDWARE_MODULE_ID,//模塊ID,上層的Service通過這個ID應用當前Stub

        name: "Sample LED Stub",

        author: "The Mokoid Open Source Project",

        methods: &led_module_methods,//入口函數管理結構體

    }

    /* supporting APIs go here */

};

 

4.1.2.2    Open & Close函數定義

 

static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device)

{

   struct led_control_device_t *dev;

 

   //建立hw_device_t 繼承類的dev變量,並初始化

 dev = (struct led_control_device_t *)malloc(sizeof(*dev));

   memset(dev, 0, sizeof(*dev));

 

   dev->common.tag =  HARDWARE_DEVICE_TAG;

   dev->common.version = 0;

   dev->common.module = module;

   dev->common.close = led_device_close;//關聯關閉接口

 

   dev->set_on = led_on;//關聯操作接口

   dev->set_off = led_off; //關聯操作接口

 

   *device = &dev->common;//common是hw_device_t結構體

   if((fd=open("/dev/led",O_RDWR))==-1)//將fd初始化爲設備文件

   {

            LOGE("LED open error");

   }

   else//這裏沒有失敗後的處理,只是記錄Log,這是Bug

            LOGI("open ok");

 

success:

   return 0;

}

 

int led_device_close(struct hw_device_t* device)

{

   struct led_control_device_t* ctx = (struct led_control_device_t*)device;//強制轉化,得到設備指針

   if (ctx) {

            free(ctx);//釋放

   }

   close(fd);

   return 0;

}

 

4.1.2.3    操作接口定義

 

#define GPG3DAT2_ON 0x4800

#define GPG3DAT2_OFF 0x4801

int led_on(struct led_control_device_t *dev, int32_t led)

{

      //led參數可以用來控制打開哪個LED,但是沒用到

      LOGI("LED Stub: set %d on.", led);

      ioctl(fd,GPG3DAT2_ON,NULL);//向設備驅動發送請求

      return 0;

}

 

int led_off(struct led_control_device_t *dev, int32_t led)

{

      //led參數可以用來控制打開哪個LED,但是沒用到

      LOGI("LED Stub: set %d off.", led);

ioctl(fd,GPG3DAT2_OFF,NULL); //向設備驅動發送請求

      return 0;

}

 

4.2          Framework

本層共包括兩個java包,一個是com.mokoid.server.LedService,另一個是mokiod.hardware.LedManager。這兩個包編譯在同一個jar文件中:mokoid.jar

另外還有一個/system/lib/libmokoid_runtime.so文件供JNI接口使用。詳見下面jni一節。

這樣上層的應用程序就可以通過調用LedServer或者LedManager來實現調用HAL的Stub了。

 

注意:這個jar文件需要通過xml描述文件註冊到Android系統中。否則上層的應用程序會找不到需要的服務。

即:拷貝frameworks\base\service\com.mokoid.server.xml到目標系統的system/etc/permissions/目錄下

 

4.2.1     Make file  (frameworks\base\Android.mk)

 

……

 

LOCAL_SRC_FILES := \

            $(call all-subdir-java-files) #編譯子目錄下的所有Java文件

 

LOCAL_MODULE_TAGS := eng

 

LOCAL_MODULE := mokoid  #編譯結果爲mokoid.jar

 

# AIDL

LOCAL_SRC_FILES += \

      core/java/mokoid/hardware/ILedService.aidl #接口定義文件

 

……

 

4.2.2     Service

本層負責通過JNI接口將C層的接口映射到Java層。其中C部分的內容以libmokoid_runtime.so文件存在,java部分以com.mokoid.server.LedServer的形式存在(編譯在mokiod.jar文件中,見本章開始處Makefile的說明)

 

4.2.2.1    jni (com_mokoid_server_LedService.cpp)

4.2.2.1.1   JNI_OnLoad 入口函數

在Android中,JNI部分採用JNI_OnLoad作爲入口的方式實現,即:所有的C實現的so文件以JNI_OnLoad爲入口。

 

/*

 *

 * This is called by the VM when the shared library is first loaded.

 */

jint JNI_OnLoad(JavaVM* vm, void* reserved) {

                  JNIEnv* env = NULL;

                  jint result = -1;

                  LOGI("JNI_OnLoad LED");

         if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {//獲取當前的VM的環境,保存在env變量中,稍候通過這個變量

                           LOGE("ERROR: GetEnv failed\n");

                           goto fail;

                  }

             assert(env != NULL);

                  if (registerMethods(env) != 0) {//自己寫的函數,向當前JAVA環境中註冊接口

                           LOGE("ERROR: PlatformLibrary native registration failed\n");

                           goto fail;

                  }

                  /* success -- return valid version number */  

                  result = JNI_VERSION_1_4;

fail:

                  return result;

}

 

4.2.2.1.2   registerMethods 註冊函數

 

static int registerMethods(JNIEnv* env) {

            static const char* const kClassName =

                                        "com/mokoid/server/LedService";

            jclass clazz;

       /* look up the class */

            clazz = env->FindClass(kClassName);//查找被註冊的類

            if (clazz == NULL) {

                                                           LOGE("Can't find class %s\n", kClassName);

                                                           return -1;

            }

 

                     /* register all the methods */

            if (env->RegisterNatives(clazz, gMethods,

                                                           sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)//向類中註冊本SO中Native的接口,接口定義在gMethods數組中

            {

                     LOGE("Failed registering methods for %s\n", kClassName);

                     return -1;

            }

   /* fill out the rest of the ID cache */

            return 0;

}

 

gMethods的定義如下:

/*

 * Array of methods.

 *

 * Each entry has three fields: the name of the method, the method

 * signature, and a pointer to the native implementation.

 */

static const JNINativeMethod gMethods[] = {

    { "_init",            "()Z",        (void *)mokoid_init },

    { "_set_on",        "(I)Z", (void *)mokoid_setOn },

    { "_set_off",       "(I)Z", (void *)mokoid_setOff },

};

 

/* 

*JNINativeMethod是jni層註冊的方法,Framework層可以使用這些方法 

*_init 、_set_on、_set_off是在Framework中調用的方法名稱,函數的類型及返回值如下: 

*()Z   無參數    返回值爲bool型 

* (I)Z   整型參數  返回值爲bool型 

*/ 

 

4.2.2.1.3   mokoid_init 初始化函數

在本層的java文件frameworks\base\service\java\com\mokoid\server\ LedService.java中,構造函數public LedService()會通過_init接口調用到本函數static jboolean mokoid_init(JNIEnv *env, jclass clazz),來完成本模塊的初始化。

 

struct led_control_device_t *sLedDevice = NULL;

……

static jboolean mokoid_init(JNIEnv *env, jclass clazz)

{

    led_module_t* module;

     LOGI("jni init-----------------------.");

    if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {//調用Android HAL標準函數hw_get_module,通過LED_HARDWARE_MODULE_ID獲取LED Stub的句柄,句柄保存在module變量中

        LOGI("LedService JNI: LED Stub found.");

        if (led_control_open(&module->common, &sLedDevice) == 0) {//通過自定義函數調用stub中的open接口,並將stub中的devices handle保存到變量sLedDevice中。

           LOGI("LedService JNI: Got Stub operations.");

            return 0;

        }

    }

 

    LOGE("LedService JNI: Get Stub operations failed.");

    return -1;

}

 

//自定義函數如下

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);//通過標準的Open接口,調用stub中的open函數

}

4.2.2.1.4   mokoid_setOn / Off 操作接口函數

 

static jboolean mokoid_setOn(JNIEnv* env, jobject thiz, jint led)

{

    LOGI("LedService JNI: mokoid_setOn() is invoked.");

 

    if (sLedDevice == NULL) {

        LOGI("LedService JNI: sLedDevice was not fetched correctly.");

        return -1;

    } else {

        return sLedDevice->set_on(sLedDevice, led);//通過set_on函數指針,調用stub中的led_on函數

    }

}

 

static jboolean mokoid_setOff(JNIEnv* env, jobject thiz, jint led)

{

    LOGI("LedService JNI: mokoid_setOff() is invoked.");

 

 

    if (sLedDevice == NULL) {

        LOGI("LedService JNI: sLedDevice was not fetched correctly.");

        return -1;

    } else {

        return sLedDevice->set_off(sLedDevice, led); //通過set_off函數指針,調用stub中的led_off函數

    }

}

 

4.2.2.2    java (向下調用runtime so來調用Stub,向上暴露Java接口)

 

public final class LedService extends ILedService.Stub {

 

    static {

        System.load("/system/lib/libmokoid_runtime.so");//加載runtime so文件

    }

 

    public LedService() {

        Log.i("LedService", "Go to get LED Stub...");

   _init();//構造函數,通過_init指針調用JNI的CPP文件中的mokoid_init函數

    }

 

    /*

     * Mokoid LED native methods.

     */

    public boolean setOn(int led) {

        Log.i("MokoidPlatform", "LED On");

   return _set_on(led);//調用JNI層的mokiod_setOn

    }

 

    public boolean setOff(int led) {

        Log.i("MokoidPlatform", "LED Off");

   return _set_off(led);//調用JNI層的mokiod_setOff

    }

 

    //申明native層的接口

   private static native boolean _init();

    private static native boolean _set_on(int led);

    private static native boolean _set_off(int led);

}

 

4.2.3     Core (LedManager)

本層是對上一節中的LedService另一種形式的封裝。

我沒有開發過Android上的Java應用程序,不知道爲什麼要這樣做。也許是因爲權限原因?非Root權限不能直接訪問Service?

 

此層分爲兩部分:

ILedService.aidl 接口定義

LedManager.java 實現LedManager

 

4.2.3.1    ILedService.aidl

 

package mokoid.hardware;

 

//定義ILedService接口

interface ILedService

{

    boolean setOn(int led);

    boolean setOff(int led);

}

 

4.2.3.2    LedManager

 

/**

 * Class that lets you access the Mokoid LedService.

 */

public class LedManager

{

    private static final String TAG = "LedManager";

    private ILedService mLedService;//申明ILedService接口的變量

 

    public LedManager() {

        mLedService = ILedService.Stub.asInterface(

                             ServiceManager.getService("led")); //從ServiceManage中獲取一個LedService的實例

 

                if (mLedService != null) { //獲取失敗

            Log.i(TAG, "The LedManager object is ready.");

                }

    }

 

    public boolean LedOn(int n) {

        boolean result = false;

 

        try {

            result = mLedService.setOn(n); //調用Service中的setOn

        } catch (RemoteException e) {

            Log.e(TAG, "RemoteException in LedManager.LedOn:", e);

        }

        return result;

    }

 

    public boolean LedOff(int n) {

        boolean result = false;

 

        try {

            result = mLedService.setOff(n); //調用Service中的setOff

        } catch (RemoteException e) {

            Log.e(TAG, "RemoteException in LedManager.LedOff:", e);

        }

        return result;

    }

}

 

5           應用程序

本理中共有兩個應用程序示例。一個是LedClient,直接調用LedService來實現控制硬件;另一個是LedTest,通過 LedManager來訪問LedService實現控制硬件。

 

由於我沒有開發過Android Java應用程序,所以不太清楚兩種方式的異同。

 

5.1          LedClient

package com.mokoid.LedClient;

import com.mokoid.server.LedService;

 

import android.app.Activity;

import android.os.Bundle;

import android.widget.TextView;

 

public class LedClient extends Activity {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

 

        // Call an API on the library.

                LedService ls = new LedService();//建立LedService

                ls.setOn(1);//操作硬件

                ls.setOff(2); //操作硬件

       

        TextView tv = new TextView(this);

        tv.setText("LED 1 is on. LED 2 is off.");//顯示文字

        setContentView(tv);

    }

}

 

5.2          LedTest

5.2.1     Xml

<activity android:name=".LedTest">

            <intent-filter>

                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>

            </intent-filter>

        </activity>

        <service android:name=".LedSystemServer"

                  android:process=".LedSystemServer" >

            <intent-filter>

                <action android:name="com.mokoid.systemserver"/>

                <category android:name="android.intent.category.DEFAULT"/>

            </intent-filter>

        </service>

從XML中可以看到申明瞭一個應用程序和一個名爲com.mokoid.systemserver的server。

 

5.2.2     LedSystemServer

 

public class LedSystemServer extends Service {

 

    @Override

    public IBinder onBind(Intent intent) {

        return null;

    }

 

    public void onStart(Intent intent, int startId) {

        Log.i("LedSystemServer", "Start LedService...");

 

      /* Please also see SystemServer.java for your interests. */

      LedService ls = new LedService();//建立LedService

 

        try {

            ServiceManager.addService("led", ls);//向ServiceManager增加一個Service

        } catch (RuntimeException e) {

            Log.e("LedSystemServer", "Start LedService failed.");

        }

    }

}

 

5.2.3     LedTest

 

public class LedTest extends Activity implements View.OnClickListener {

    private LedManager mLedManager = null;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

 

        // Start LedService in a seperated process.

        startService(new Intent("com.mokoid.systemserver"));//啓動了自己寫的com.mokiod.systemserver

 

        Button btn = new Button(this);//建立一個按鈕

        btn.setText("Click to turn LED 1 On");

      btn.setOnClickListener(this);

 

        setContentView(btn);

    }

 

    public void onClick(View v) {

 

        // Get LedManager.

        if (mLedManager == null) {

          Log.i("LedTest", "Creat a new LedManager object.");

          mLedManager = new LedManager();//建立LedManager,此時LedManager的構造函數會從ServiceManager獲取LedService的一個實例

        }   

 

        if (mLedManager != null) {

          Log.i("LedTest", "Got LedManager object.");

      }

 

        /** Call methods in LedService via proxy object

         * which is provided by LedManager.

         */

        mLedManager.LedOn(1);//通過mLedManager的接口調用LedService

 

        TextView tv = new TextView(this);

        tv.setText("LED 1 is On.");

        setContentView(tv);

    }

}

 

6           備註

6.1          insmod的時候如何傳遞參數

例如:insmod hello.ko count=0 string="hello"

注:在程序中通過如下宏來申明一個接受參數的變量

module_param(simple_major, int, 0);//向內核申明一個參數,可以在insmod的時候傳遞給驅動程序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章