1. 程式人生 > >s5p4418 Android 4.4.2 驅動層 HAL層 服務層 應用層 開發流程記錄(一 硬體驅動層)

s5p4418 Android 4.4.2 驅動層 HAL層 服務層 應用層 開發流程記錄(一 硬體驅動層)

歡迎轉載,務必註明出處:http://blog.csdn.net/wang_shuai_ww/article/details/44303069

本文章是記錄Android開發中驅動層、HAL層、應用層之間的關係,以及其開發方法,本文將會以實現LED的控制為例來進行記錄。

一是可以給以後自己做開發做參考,二是希望可以幫助正在學習的朋友參考。

一般的app不需要我們去關注hal和驅動,但在設計一個硬體系統時,原生的Android並未提供合適的服務,所以我們才需要去了解這個流程。由於也是剛入門,很多還不太懂,朋友們有什麼疑問可以留言。

首先需要了解,Android的app想要操作硬體,是什麼樣的一個流程。一般是這樣的,app應用層、服務層、硬體抽象層、底層驅動。

我是從底層到上層來進行學習和測試的。也就是:底層->硬體抽象層->服務層->app。原因是,首先需要確定底層的驅動沒有問題,而且底層驅動可以使用Linux的方法來進行測試,一步一步走到上層應用。

驅動程式碼我就直接貼上來,就不去詳細解釋裡面的含義了,不懂的可以參考羅昇陽的《Android系統原始碼情景分析》的第二章。

程式碼如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h> 
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/gpio.h>

#include <mach/platform.h>
#include <mach/devices.h>
#include <mach/soc.h>
#include <mach/gpio.h>
#include <linux/uaccess.h>
#include <linux/pci.h>

#include <linux/proc_fs.h>


#define DEVICE_NAME "real_led"

#define LED_DEVICE_NODE_NAME	DEVICE_NAME
#define LED_DEVICE_FILE_NAME	DEVICE_NAME
#define LED_DEVICE_PROC_NAME	DEVICE_NAME
#define LED_DEVICE_CLASS_NAME	DEVICE_NAME

static int led_gpios[] = {
	(PAD_GPIO_C + 1),
};

#define LED_NUM		ARRAY_SIZE(led_gpios)

/*主裝置和從裝置號變數*/  
static int led_major = 0;  
static int led_minor = 0;  

static struct class* led_class = NULL; 

/*訪問設定屬性方法*/  
static ssize_t led_val_show(struct device* dev, struct device_attribute* attr,  char* buf);  
static ssize_t led_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);  
  
/*定義裝置屬性*/  
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, led_val_show, led_val_store);  

struct leds_dev
{
	struct cdev dev;
	int led_status;
};

struct leds_dev *led_dev=NULL;

/*開啟裝置方法*/  
static int led_open(struct inode* inode, struct file* filp) {  
    struct leds_dev* dev;          
      
    /*將自定義裝置結構體儲存在檔案指標的私有資料域中,以便訪問裝置時拿來用*/  
    dev = container_of(inode->i_cdev, struct leds_dev, dev);  
    filp->private_data = dev;  
    
    return 0;  
}  
  
/*裝置檔案釋放時呼叫,空實現*/  
static int led_release(struct inode* inode, struct file* filp) {  
    return 0;  
}  
  
/*讀取裝置的暫存器val的值*/  
static ssize_t led_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {  
    ssize_t err = 0;  
    struct leds_dev* dev = filp->private_data;          
  
  
  
    if(count < sizeof(dev->led_status)) {  
        goto out;  
    }          
  
    /*將暫存器val的值拷貝到使用者提供的緩衝區*/  
    if(copy_to_user(buf, &(dev->led_status), sizeof(dev->led_status))) {  
        err = -EFAULT;  
        goto out;  
    }  
  
    err = sizeof(dev->led_status);  
  
out:  
    return err;  
}  
  
/*寫裝置的暫存器值val*/  
static ssize_t led_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {  
    struct leds_dev* dev = filp->private_data;  
    ssize_t err = 0;          
  	int i;
    
  
    if(count != sizeof(dev->led_status)) {  
        goto out;          
    }          
  
    /*將使用者提供的緩衝區的值寫到裝置暫存器去*/  
    if(copy_from_user(&(dev->led_status), buf, count)) {  
        err = -EFAULT;  
        goto out;  
    }  
  
	for(i=0;i<LED_NUM;i++){
		if((0x01&(dev->led_status>>i))==1)
			nxp_soc_gpio_set_out_value(led_gpios[i], 0);
		else
			nxp_soc_gpio_set_out_value(led_gpios[i], 1);

	}

    err = sizeof(dev->led_status);  
  
out:   
    return err;  
} 

static int led_ioctl(struct file *file, unsigned int cmd, unsigned long num) 
{
	//由於開發板只有一個LED,所以這裡做個判斷,傳入的num不等於0,返回錯誤  
	if(num != 0)  
	{  
		printk("RealARM S5P4418 board only have one led lights.Please check app input parameters.\n");
		return -EINVAL;  
	}
	nxp_soc_gpio_set_out_value(led_gpios[num], cmd);
}
/**************************************************************************************/

/*裝置檔案操作方法表*/  
static struct file_operations led_fops = {  
    .owner = THIS_MODULE,  
    .open = led_open,  
    .release = led_release,  
    .read = led_read,  
    .write = led_write, 
    .unlocked_ioctl= led_ioctl,  
};  

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

static ssize_t __led_get_val(struct leds_dev* dev, char* buf) {  
    int val = 0;          
  
       
  
    val = dev->led_status;                  
  
    return snprintf(buf, PAGE_SIZE, "%d\n", val);  
}  
  
/*把緩衝區buf的值寫到裝置暫存器val中去,內部使用*/  
static ssize_t __led_set_val(struct leds_dev* dev, const char* buf, size_t count) {  
    int val = 0;          
    int i;
   
    /*將字串轉換成數字*/          
    val = simple_strtol(buf, NULL, 10); 
             	
    for(i=0;i<LED_NUM;i++){
    	nxp_soc_gpio_set_out_value(led_gpios[i], val);
    }  
  
    dev->led_status = val;          
  
    return count;  
}  
  
/*讀取裝置屬性val*/  
static ssize_t led_val_show(struct device* dev, struct device_attribute* attr, char* buf) {  
    struct leds_dev* hdev = (struct leds_dev*)dev_get_drvdata(dev);          
  
    return __led_get_val(hdev, buf);  
}  
  
/*寫裝置屬性val*/  
static ssize_t led_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) {   
    struct leds_dev* hdev = (struct leds_dev*)dev_get_drvdata(dev);    
      
    return __led_set_val(hdev, buf, count);  
}  



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

/*讀取裝置暫存器val的值,儲存在page緩衝區中*/  
static ssize_t led_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {  
    if(off > 0) {  
        *eof = 1;  
        return 0;  
    }  
  
    return __led_get_val(led_dev, page);  
}  
  
/*把緩衝區的值buff儲存到裝置暫存器val中去*/  
static ssize_t led_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {  
    int err = 0;  
    char* page = NULL;  
  
    if(len > PAGE_SIZE) {  
        printk(KERN_ALERT"The buff is too large: %lu./n", len);  
        return -EFAULT;  
    }  
  
    page = (char*)__get_free_page(GFP_KERNEL);  
    if(!page) {                  
        printk(KERN_ALERT"Failed to alloc page./n");  
        return -ENOMEM;  
    }          
  
    /*先把使用者提供的緩衝區值拷貝到核心緩衝區中去*/  
    if(copy_from_user(page, buff, len)) {  
        printk(KERN_ALERT"Failed to copy buff from user./n");                  
        err = -EFAULT;  
        goto out;  
    }  
  
    err = __led_set_val(led_dev, page, len);  
  
out:  
    free_page((unsigned long)page);  
    return err;  
}  
  
/*建立/proc/led檔案*/  
static void led_create_proc(void) {  
    struct proc_dir_entry* entry;  
      
    entry = create_proc_entry(DEVICE_NAME, 0, NULL);  
    if(entry) {  
    //    entry->owner = THIS_MODULE;  
        entry->read_proc = led_proc_read;  
        entry->write_proc = led_proc_write;  
    }  
}  
  
/*刪除/proc/led檔案*/  
static void led_remove_proc(void) {  
    remove_proc_entry(DEVICE_NAME, NULL);  
}  

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




static int  __led_setup_dev(struct leds_dev* dev) {  
    int err;  
    dev_t devno = MKDEV(led_major, led_minor);  
  
    memset(dev, 0, sizeof(struct leds_dev));  
  
    cdev_init(&(led_dev->dev), &led_fops);  
    dev->dev.owner = THIS_MODULE;  
    dev->dev.ops = &led_fops;          
  
    /*註冊字元裝置*/  
    err = cdev_add(&(dev->dev),devno, 1);  
    if(err) {  
        return err;  
    }          
  
    return 0;  
}  





static int __init real4418_led_dev_init(void) {
	int ret;
	int i;
	unsigned int val;
	int err = -1;  
    dev_t dev = 0;  
	struct device* temp = NULL; 
    


	for (i = 0; i < LED_NUM; i++) {
		ret = gpio_request(led_gpios[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
					led_gpios[i], ret);
			goto fail;
		}

		nxp_soc_gpio_set_io_func(led_gpios[i], 1); 
    	nxp_soc_gpio_set_io_dir(led_gpios[i], 1);
		nxp_soc_gpio_set_out_value(led_gpios[i], 0);
	}

  
    /*動態分配主裝置和從裝置號*/  
    err = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);  
    if(err < 0) {  
        printk(KERN_ALERT"Failed to alloc char dev region./n");  
        goto fail;  
    }  
  
    led_major = MAJOR(dev);  
    led_minor = MINOR(dev);          
  
    /*分配led裝置結構體變數*/  
    led_dev = kmalloc(sizeof(struct leds_dev), GFP_KERNEL);  
    if(!led_dev) {  
        err = -ENOMEM;  
        printk(KERN_ALERT"Failed to alloc led_dev./n");  
        goto unregister;  
    }          
  
    /*初始化裝置*/  
    err = __led_setup_dev(led_dev);  
    if(err) {  
        printk(KERN_ALERT"Failed to setup dev: %d./n", err);  
        goto cleanup;  
    }          





    /*在/sys/class/目錄下建立裝置類別目錄led*/  
    led_class = class_create(THIS_MODULE, LED_DEVICE_CLASS_NAME);  
    if(IS_ERR(led_class)) {  
        err = PTR_ERR(led_class);  
        printk(KERN_ALERT"Failed to create led class./n");  
        goto destroy_cdev;  
    }          
  
    /*在/dev/目錄和/sys/class/led目錄下分別建立裝置檔案led*/  
    temp = device_create(led_class, NULL, dev, "%s", LED_DEVICE_FILE_NAME);  
    if(IS_ERR(temp)) {  
        err = PTR_ERR(temp);  
        printk(KERN_ALERT"Failed to create led device.");  
        goto destroy_class;  
    }          
  
    /*在/sys/class/led/led目錄下建立屬性檔案val*/  
    err = device_create_file(temp, &dev_attr_val);  
    if(err < 0) {  
        printk(KERN_ALERT"Failed to create attribute val.");                  
        goto destroy_device;  
    }  
  
    dev_set_drvdata(temp, led_dev);  
	
	led_create_proc();  

    printk(DEVICE_NAME"\tinitialized\n");

    return ret;


	
	destroy_device:  
	    device_destroy(led_class, dev);  
	  
	destroy_class:  
	    class_destroy(led_class);  
	  
	destroy_cdev:  
	    cdev_del(&(led_dev->dev));  
	  
	cleanup:  
	    kfree(led_dev);  
	  
	unregister:  
	    unregister_chrdev_region(MKDEV(led_major, led_minor), 1);  
	  
	fail:  
	for (; i >=0; i--)
		gpio_free(led_gpios[i]);

	return err; 



}

static void __exit real4418_led_dev_exit(void) {
	
	int i;
	dev_t devno = MKDEV(led_major, led_minor);  

	for (i = 0; i < LED_NUM; i++) {
		gpio_free(led_gpios[i]);
	}


  
            
  
    /*刪除/proc/led檔案*/  
   led_remove_proc();          
  
    /*銷燬裝置類別和裝置*/  
    if(led_class) {  
        device_destroy(led_class, MKDEV(led_major, led_minor));  
        class_destroy(led_class);  
    }          
  
    /*刪除字元裝置和釋放裝置記憶體*/  
    if(led_dev) {  
        cdev_del(&(led_dev->dev));  
        kfree(led_dev);  
    }          
  
    /*釋放裝置號*/  
    unregister_chrdev_region(devno, 1);  

	printk(KERN_ALERT"Destroy led device./n");  
}

module_init(real4418_led_dev_init);
module_exit(real4418_led_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("sean <
[email protected]
>");


我的檔名是real4418_leds.c,把該檔案放到核心目錄/drivers/leds下,並修改當前目錄下的kconfig和Makefile檔案,分別如下:

kconfig新增配置選項:

config LEDS_REALARM
	tristate "LED Support for RealARM S5P4418"
	help
	  This option enables support for on-chip LED drivers found on RealARM
	  S5P4418.

Makefile新增下面的程式碼:
obj-$(CONFIG_LEDS_REALARM)	+= real4418_leds.o 
使用make ARCH=arm xconfig命令進行核心配置。如下圖所示:


編譯核心,下載到開發板啟動。

下面是測試上面程式碼的方法:

需要測試三個部分,傳統裝置檔案介面系統、devfs檔案系統介面、proc檔案系統介面,這三種介面均可實現對LED的硬體操作,Android部分的hal層使用的是read和write方式。

傳統的介面系統需要寫Linux應用程式來進行測試,我這裡就不再詳細寫了,可以參考《Android系統原始碼情景分析》裡面的寫法,就是使用open、read、write、unlocked_ioctl這些函式。下面是使用ioctl來進行測試的,read、write按照read、write的用法就行了。

/* 
 ============================================== 
 Name           : led_test.c 
 Author         : sean 
 Date           : 16/3/2015 
 Description    : s5p4418 led driver test 
 ============================================== 
 */  
   
#include <stdio.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include<sys/ioctl.h>  
   
int main(int argc, char**argv)  
{  
    int turn, index, fd;  
   
    if(strcmp(argv[1], "on") == 0)  
    {  
        turn = 1;  
    }  
    else if(strcmp(argv[1], "off") ==0)  
    {  
        turn = 0;  
    }  
    else  
    {  
        printf("Usage: led_test on|off1|2|3|4\n");  
        exit(1);  
    }  
   
    //開啟LED裝置  
    fd = open("/dev/real_led", 0);  
   
    if(fd < 0)  
    {  
        printf("Open Led DeviceFaild!\n");  
        exit(1);  
    }  
   	printf("Open Led Device success!\n");
    //IO控制  
    ioctl(fd, turn, 1);  
   
    //關閉LED裝置  
    close(fd);  
   
    return 0;  
}  

devfs檔案介面系統的測試:

啟動板子,進入到控制檯,進入到/sys/class/real_led/real_led/目錄,如果可以進入,那麼說明我們led的devfs檔案系統註冊成功了,反之。

使用下面所示的方法進行測試:

[email protected]:/sys/class/real_led/real_led # cat val                            
0
[email protected]:/sys/class/real_led/real_led # echo '1' > val                     
[email protected]:/sys/class/real_led/real_led # cat val                            
1
[email protected]:/sys/class/real_led/real_led # echo '0' > val                     
[email protected]:/sys/class/real_led/real_led # cat val                            
0
[email protected]:/sys/class/real_led/real_led # 
當向val寫入1時,觀察LED是否被點亮了,反之。我這裡測試是沒有問題的。

proc檔案系統的測試:

進入到/proc目錄,首先使用ls real_led檢查是否有real_led這個檔案,然後使用echo '1' > real_led和echo '0' > real_led來測試led。

如下:

[email protected]:/proc # ls real_led                                               
real_led
[email protected]:/proc # echo '1' > real_led                                       
[email protected]:/proc # echo '0' > real_led                                       
[email protected]:/proc #
我這裡測試也是沒有問題。


這三種方式測試都沒問題了,那麼驅動程式是沒有什麼問題了,下一部分是硬體抽象層的說明。