您的位置:首页 > 运维架构 > Linux

在Ubuntu上为Android系统编写Linux内核驱动程序(学习老罗的)

2013-06-29 20:57 976 查看
首先提出2个问题
1. 驱动程序的作用是什么?

答:驱动程序的作用主要是向上层提供访问设备寄存器的一个接口,包括读和写。

2. 访问设备驱动程序的方法?

答:a. 通过proc文件系统来访问;b. 通过传统的设备文件的方法来访问;c.
通过devfs文件系统来访问。

他定义的hello.h在最新goldfish路径应该为:~/Android_4.2.2_SourceCode/kernel/goldfish/drivers/hello中

内容为:结构体hello_android_dev来表示虚拟的硬件设备

#ifndef _HELLO_ANDROID_H_  
#define _HELLO_ANDROID_H_  
  
#include <linux/cdev.h>  
#include <linux/semaphore.h>  

// 一些字符串常量宏
#define HELLO_DEVICE_NODE_NAME  "hello"  
#define HELLO_DEVICE_FILE_NAME  "hello"  
#define HELLO_DEVICE_PROC_NAME  "hello"  
#define HELLO_DEVICE_CLASS_NAME "hello"  

// 字符设备结构体
// 也是我们定义的虚拟的硬件设备
struct hello_android_dev {  
    int val;   // 设备里面的寄存器
    struct semaphore sem;  // sem成员变量是一个信号量,是用同步访问寄存器val的
    struct cdev dev;  // 一个内嵌的字符设备
};  
  
#endif
并且这样的写法是标准的Linux驱动程序自定义字符设备结构体方法。

下面是hello.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 "hello.h"

/*主设备和从设备号变量*/
static int hello_major = 0;
static int hello_minor = 0;

/*设备类别和设备变量*/
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;

/*传统的设备文件操作方法*/
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);

/*设备文件操作方法表。即 -- proc文件系统访问方法 */
 static struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write, 
};

/*访问设置属性方法*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr,  char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// part_函数实现

 /*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp) {
	struct hello_android_dev* dev;        
	
	/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
	dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
	filp->private_data = dev;
	
	return 0;
}

/*设备文件释放时调用,空实现*/
static int hello_release(struct inode* inode, struct file* filp) {
	return 0;
}

/*读取设备的寄存器val的值*/
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
	ssize_t err = 0;
	struct hello_android_dev* dev = filp->private_data;        

	/*同步访问*/
	if(down_interruptible(&(dev->sem))) {
		return -ERESTARTSYS;
	}

	if(count < sizeof(dev->val)) {
		goto out;
	}        

	/*将寄存器val的值拷贝到用户提供的缓冲区*/
	if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	up(&(dev->sem));
	return err;
}

/*写设备的寄存器值val*/
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
	struct hello_android_dev* dev = filp->private_data;
	ssize_t err = 0;        

	/*同步访问*/
	if(down_interruptible(&(dev->sem))) {
		return -ERESTARTSYS;        
	}        

	if(count != sizeof(dev->val)) {
		goto out;        
	}        

	/*将用户提供的缓冲区的值写到设备寄存器去*/
	if(copy_from_user(&(dev->val), buf, count)) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	up(&(dev->sem));
	return err;
}

/*读取寄存器val的值到缓冲区buf中,内部使用*/
static ssize_t __hello_get_val(struct hello_android_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);
}

/*把缓冲区buf的值写到设备寄存器val中去,内部使用*/
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count) {
	int val = 0;        

	/*将字符串转换成数字*/        
	val = simple_strtol(buf, NULL, 10);        

	/*同步访问*/        
	if(down_interruptible(&(dev->sem))) {                
		return -ERESTARTSYS;        
	}        

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

	return count;
}

/*读取设备属性val*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
	struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);        

	return __hello_get_val(hdev, buf);
}

/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) { 
	struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);  
	
	return __hello_set_val(hdev, buf, count);
}

/*读取设备寄存器val的值,保存在page缓冲区中*/
static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
	if(off > 0) {
		*eof = 1;
		return 0;
	}

	return __hello_get_val(hello_dev, page);
}

/*把缓冲区的值buff保存到设备寄存器val中去*/
static ssize_t hello_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 = __hello_set_val(hello_dev, page, len);

out:
	free_page((unsigned long)page);
	return err;
}

/*创建/proc/hello文件*/
static void hello_create_proc(void) {
	struct proc_dir_entry* entry;
	
	entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
	if(entry) {
		entry->owner = THIS_MODULE;
		entry->read_proc = hello_proc_read;
		entry->write_proc = hello_proc_write;
	}
}

/*删除/proc/hello文件*/
static void hello_remove_proc(void) {
	remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}

/*初始化设备*/
static int  __hello_setup_dev(struct hello_android_dev* dev) {
	int err;
	dev_t devno = MKDEV(hello_major, hello_minor);

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

	cdev_init(&(dev->dev), &hello_fops);
	dev->dev.owner = THIS_MODULE;
	dev->dev.ops = &hello_fops;        

	/*注册字符设备*/
	err = cdev_add(&(dev->dev),devno, 1);
	if(err) {
		return err;
	}        

	/*初始化信号量和寄存器val的值*/
	init_MUTEX(&(dev->sem));
	dev->val = 0;

	return 0;
}

/*模块加载方法*/
static int __init hello_init(void){ 
	int err = -1;
	dev_t dev = 0;
	struct device* temp = NULL;

	printk(KERN_ALERT"Initializing hello device.\n");        

	/*动态分配主设备和从设备号*/
	err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
	if(err < 0) {
		printk(KERN_ALERT"Failed to alloc char dev region.\n");
		goto fail;
	}

	hello_major = MAJOR(dev);
	hello_minor = MINOR(dev);        

	/*分配helo设备结构体变量*/
	hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
	if(!hello_dev) {
		err = -ENOMEM;
		printk(KERN_ALERT"Failed to alloc hello_dev.\n");
		goto unregister;
	}        

	/*初始化设备*/
	err = __hello_setup_dev(hello_dev);
	if(err) {
		printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
		goto cleanup;
	}        

	/*在/sys/class/目录下创建设备类别目录hello*/
	hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
	if(IS_ERR(hello_class)) {
		err = PTR_ERR(hello_class);
		printk(KERN_ALERT"Failed to create hello class.\n");
		goto destroy_cdev;
	}        

	/*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/
	temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
	if(IS_ERR(temp)) {
		err = PTR_ERR(temp);
		printk(KERN_ALERT"Failed to create hello device.");
		goto destroy_class;
	}        

	/*在/sys/class/hello/hello目录下创建属性文件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, hello_dev);        

	/*创建/proc/hello文件*/
	hello_create_proc();

	printk(KERN_ALERT"Succedded to initialize hello device.\n");
	return 0;

destroy_device:
	device_destroy(hello_class, dev);

destroy_class:
	class_destroy(hello_class);

destroy_cdev:
	cdev_del(&(hello_dev->dev));

cleanup:
	kfree(hello_dev);

unregister:
	unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);

fail:
	return err;
}

/*模块卸载方法*/
static void __exit hello_exit(void) {
	dev_t devno = MKDEV(hello_major, hello_minor);

	printk(KERN_ALERT"Destroy hello device.\n");        

	/*删除/proc/hello文件*/
	hello_remove_proc();        

	/*销毁设备类别和设备*/
	if(hello_class) {
		device_destroy(hello_class, MKDEV(hello_major, hello_minor));
		class_destroy(hello_class);
	}        

	/*删除字符设备和释放设备内存*/
	if(hello_dev) {
		cdev_del(&(hello_dev->dev));
		kfree(hello_dev);
	}        

	/*释放设备号*/
	unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");

module_init(hello_init);
module_exit(hello_exit);


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

转自:http://blog.csdn.net/luoshengyang/article/details/6568411

在智能手机时代,每个品牌的手机都有自己的个性特点。正是依靠这种与众不同的个性来吸引用户,营造品牌凝聚力和用户忠城度,典型的代表非iphone莫属了。据统计,截止2011年5月,AppStore的应用软件数量达381062个,位居第一,而Android Market的应用软件数量达294738,紧随AppStore后面,并有望在8月份越过AppStore。随着Android系统逐步扩大市场占有率,终端设备的多样性亟需更多的移动开发人员的参与。据业内统计,Android研发人才缺口至少30万。目前,对Android人才需求一类是偏向硬件驱动的Android人才需求,一类是偏向软件应用的Android人才需求。总的来说,对有志于从事Android硬件驱动的开发工程师来说,现在是一个大展拳脚的机会。那么,就让我们一起来看看如何为Android系统编写内核驱动程序吧。

这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为Android系统编写内核驱动程序的过程,我们使用一个虚拟的硬件设备,这个设备只有一个4字节的寄存器,它可读可写。想起我们第一次学习程序语言时,都喜欢用“Hello, World”作为例子,这里,我们就把这个虚拟的设备命名为“hello”,而这个内核驱动程序也命名为hello驱动程序。其实,Android内核驱动程序和一般Linux内核驱动程序的编写方法是一样的,都是以Linux模块的形式实现的,具体可参考前面Android学习启动篇一文中提到的Linux
Device Drivers一书。不过,这里我们还是从Android系统的角度来描述Android内核驱动程序的编写和编译过程。

一. 参照前面两篇文章在Ubuntu上下载、编译和安装Android最新源代码在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)准备好Android内核驱动程序开发环境。

二. 进入到kernel/common/drivers目录,新建hello目录:

USER-NAME@MACHINE-NAME:~/Android$ cd kernel/common/drivers

USER-NAME@MACHINE-NAME:~/Android/kernel/common/drivers$ mkdir hello

三. 在hello目录中增加hello.h文件:

[cpp]
view plaincopy

#ifndef _HELLO_ANDROID_H_
#define _HELLO_ANDROID_H_

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

#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"

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

#endif

这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_android_dev,这个就是我们虚拟的硬件设备了,val成员变量就代表设备里面的寄存器,它的类型为int,sem成员变量是一个信号量,是用同步访问寄存器val的,dev成员变量是一个内嵌的字符设备,这个Linux驱动程序自定义字符设备结构体的标准方法。

四.在hello目录中增加hello.c文件,这是驱动程序的实现部分。驱动程序的功能主要是向上层提供访问设备的寄存器的值,包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc文件系统来访问,二是通过传统的设备文件的方法来访问,三是通过devfs文件系统来访问。下面分段描述该驱动程序的实现。

首先是包含必要的头文件和定义三种访问设备的方法:

[cpp]
view plaincopy

#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 "hello.h"

/*主设备和从设备号变量*/
static int hello_major = 0;
static int hello_minor = 0;

/*设备类别和设备变量*/
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;

/*传统的设备文件操作方法*/
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);

/*设备文件操作方法表*/
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};

/*访问设置属性方法*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:

[cpp]
view plaincopy

/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp) {
struct hello_android_dev* dev;

/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;

return 0;
}

/*设备文件释放时调用,空实现*/
static int hello_release(struct inode* inode, struct file* filp) {
return 0;
}

/*读取设备的寄存器val的值*/
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;

/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}

if(count < sizeof(dev->val)) {
goto out;
}

/*将寄存器val的值拷贝到用户提供的缓冲区*/
if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
err = -EFAULT;
goto out;
}

err = sizeof(dev->val);

out:
up(&(dev->sem));
return err;
}

/*写设备的寄存器值val*/
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;

/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}

if(count != sizeof(dev->val)) {
goto out;
}

/*将用户提供的缓冲区的值写到设备寄存器去*/
if(copy_from_user(&(dev->val), buf, count)) {
err = -EFAULT;
goto out;
}

err = sizeof(dev->val);

out:
up(&(dev->sem));
return err;
}

定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val:

[cpp]
view plaincopy

/*读取寄存器val的值到缓冲区buf中,内部使用*/
static ssize_t __hello_get_val(struct hello_android_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);
}

/*把缓冲区buf的值写到设备寄存器val中去,内部使用*/
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count) {
int val = 0;

/*将字符串转换成数字*/
val = simple_strtol(buf, NULL, 10);

/*同步访问*/
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}

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

return count;
}

/*读取设备属性val*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

return __hello_get_val(hdev, buf);
}

/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);

return __hello_set_val(hdev, buf, count);
}

定义通过proc文件系统访问方法,主要实现了hello_proc_read和hello_proc_write两个方法,同时定义了在proc文件系统创建和删除文件的方法hello_create_proc和hello_remove_proc:

[cpp]
view plaincopy

/*读取设备寄存器val的值,保存在page缓冲区中*/
static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
if(off > 0) {
*eof = 1;
return 0;
}

return __hello_get_val(hello_dev, page);
}

/*把缓冲区的值buff保存到设备寄存器val中去*/
static ssize_t hello_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 = __hello_set_val(hello_dev, page, len);

out:
free_page((unsigned long)page);
return err;
}

/*创建/proc/hello文件*/
static void hello_create_proc(void) {
struct proc_dir_entry* entry;

entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
if(entry) {
entry->owner = THIS_MODULE;
entry->read_proc = hello_proc_read;
entry->write_proc = hello_proc_write;
}
}

/*删除/proc/hello文件*/
static void hello_remove_proc(void) {
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}

最后,定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作:

[cpp]
view plaincopy

/*初始化设备*/
static int __hello_setup_dev(struct hello_android_dev* dev) {
int err;
dev_t devno = MKDEV(hello_major, hello_minor);

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

cdev_init(&(dev->dev), &hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;

/*注册字符设备*/
err = cdev_add(&(dev->dev),devno, 1);
if(err) {
return err;
}

/*初始化信号量和寄存器val的值*/
init_MUTEX(&(dev->sem));
dev->val = 0;

return 0;
}

/*模块加载方法*/
static int __init hello_init(void){
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;

printk(KERN_ALERT"Initializing hello device.\n");

/*动态分配主设备和从设备号*/
err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
if(err < 0) {
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}

hello_major = MAJOR(dev);
hello_minor = MINOR(dev);

/*分配helo设备结构体变量*/
hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
if(!hello_dev) {
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev.\n");
goto unregister;
}

/*初始化设备*/
err = __hello_setup_dev(hello_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}

/*在/sys/class/目录下创建设备类别目录hello*/
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
if(IS_ERR(hello_class)) {
err = PTR_ERR(hello_class);
printk(KERN_ALERT"Failed to create hello class.\n");
goto destroy_cdev;
}

/*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
if(IS_ERR(temp)) {
err = PTR_ERR(temp);
printk(KERN_ALERT"Failed to create hello device.");
goto destroy_class;
}

/*在/sys/class/hello/hello目录下创建属性文件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, hello_dev);

/*创建/proc/hello文件*/
hello_create_proc();

printk(KERN_ALERT"Succedded to initialize hello device.\n");
return 0;

destroy_device:
device_destroy(hello_class, dev);

destroy_class:
class_destroy(hello_class);

destroy_cdev:
cdev_del(&(hello_dev->dev));

cleanup:
kfree(hello_dev);

unregister:
unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);

fail:
return err;
}

/*模块卸载方法*/
static void __exit hello_exit(void) {
dev_t devno = MKDEV(hello_major, hello_minor);

printk(KERN_ALERT"Destroy hello device.\n");

/*删除/proc/hello文件*/
hello_remove_proc();

/*销毁设备类别和设备*/
if(hello_class) {
device_destroy(hello_class, MKDEV(hello_major, hello_minor));
class_destroy(hello_class);
}

/*删除字符设备和释放设备内存*/
if(hello_dev) {
cdev_del(&(hello_dev->dev));
kfree(hello_dev);
}

/*释放设备号*/
unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");

module_init(hello_init);
module_exit(hello_exit);

五.在hello目录中新增Kconfig和Makefile两个文件,其中Kconfig是在编译前执行配置命令make menuconfig时用到的,而Makefile是执行编译命令make是用到的:

Kconfig文件的内容

config HELLO
tristate "First Android Driver"
default n
help
This is the first android driver.
Makefile文件的内容
obj-$(CONFIG_HELLO) += hello.o
在Kconfig文件中,tristate表示编译选项HELLO支持在编译内核时,hello模块支持以模块、内建和不编译三种编译方法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig命令来配置编译选项,使得hello可以以模块或者内建的方法进行编译。
在Makefile文件中,根据选项HELLO的值,执行不同的编译方法。
六. 修改arch/arm/Kconfig和drivers/kconfig两个文件,在menu "Device Drivers"和endmenu之间添加一行:
source "drivers/hello/Kconfig"
这样,执行make menuconfig时,就可以配置hello模块的编译选项了。.
七. 修改drivers/Makefile文件,添加一行:
obj-$(CONFIG_HELLO) += hello/
八. 配置编译选项:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$ make menuconfig
找到"Device Drivers" => "First Android Drivers"选项,设置为y。
注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig文件中配置了HELLO选项为tristate。要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support选项;在支持动态卸载模块选项,必须要在Enable loadable module support菜单项中,选择Module unloading选项。
九. 编译:
USER-NAME@MACHINE-NAME:~/Android/kernel/common$ make
编译成功后,就可以在hello目录下看到hello.o文件了,这时候编译出来的zImage已经包含了hello驱动。
十. 参照在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,验证hello驱动程序是否已经正常安装:
[b]USER-NAME@MACHINE-NAME:~/Android$ emulator -kernel ./kernel/common/arch/arm/boot/zImage &[/b]
[b]USER-NAME@MACHINE-NAME:~/Android$ adb shell[/b]

进入到dev目录,可以看到hello设备文件:
root@android:/ # cd dev
root@android:/dev # ls

进入到proc目录,可以看到hello文件:
root@android:/ # cd proc
root@android:/proc # ls
访问hello文件的值:
root@android:/proc # cat hello
0

root@android:/proc # echo '5' > hello

root@android:/proc # cat hello

5
进入到sys/class目录,可以看到hello目录:

root@android:/ # cd sys/class
root@android:/sys/class # ls
进入到hello目录,可以看到hello目录:
root@android:/sys/class # cd hello
root@android:/sys/class/hello # ls

进入到下一层hello目录,可以看到val文件:

root@android:/sys/class/hello # cd hello

root@android:/sys/class/hello/hello # ls
访问属性文件val的值:

root@android:/sys/class/hello/hello # cat val

5

root@android:/sys/class/hello/hello # echo '0' > val

root@android:/sys/class/hello/hello # cat val

0
至此,我们的hello内核驱动程序就完成了,并且验证一切正常。这里我们采用的是系统提供的方法和驱动程序进行交互,也就是通过proc文件系统和devfs文件系统的方法,下一篇文章中,我们将通过自己编译的C语言程序来访问/dev/hello文件来和hello驱动程序交互,敬请期待。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

分享到:

上一篇:Android硬件抽象层(HAL)概要介绍和学习计划

下一篇:在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序

顶67
踩0

查看评论

60楼 顾小林
2013-05-15 13:17发表
[回复]

老罗:

您好,关于在hello_open()这个函数里面我有点疑惑

我想知道 当调用这个函数传进来的这个 inode* inode

inode->i_cdev 这个怎么理解呢

我通过一些资料了解到 这个container_of 大概是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址 是否正确呢?

如果上面的那个解释正确 那那个inode->i_cdev理解为哪个成员地址呢?

菜鸟一枚,请帮忙指点一下,谢谢!

Re: 罗升阳
2013-05-15 23:18发表
[回复]

回复shen332401890:hello_android_dev结构体的成员变量dev

Re: 顾小林
2013-05-16 09:49发表
[回复]

回复Luoshengyang:谢谢老罗!

59楼 ajige
2013-05-09 10:49发表
[回复]

博主,/sys/class/hello/hello

为什么这个文件 我用 echo cat 去测试 结果是正确的,

但是用C语言的read和write去测试的时候,结果却很怪,

这两者有区别吗?

Re: 罗升阳
2013-05-09 11:35发表
[回复]

回复a_jige:因为/sys/class/hello/hello接受的是字符串,不是二进制数据,cat和echo读取和输入的都是字符串,read和wrtie读取和输入的都是二进制数据。

58楼 u010493203
2013-05-03 15:12发表
[回复]

老罗,我一个有一个问题想问,就是HAL、Overlay、Opengl、Omx(Openmax)之间的联系是什么。个人感觉,overlay是Hal的一种,opengl和omx(openmax)是两个独立的体系,在显示图像的时候Opengl应该是调用openmax的,openmax则和HAL没有关系。不知道是不是这样,望解答,不胜感激~

Re: 罗升阳
2013-05-03 18:30发表
[回复]

回复u010493203:你问到我的痛处了,我也正想搞清楚它们的关系,过阵子就去研究。

Re: u010493203
2013-05-03 23:11发表
[回复]

回复Luoshengyang:恩, 我也在研究,等有了结果在和你讨论一下吧

57楼 hgd_dingjun
2013-04-08 16:46发表
[回复]



[cpp]
view plaincopy

#define DBGLCDS_IOCTL_RESET _IO(DBGLCDS_IOC_MAGIC, 0)
#define DBGLCDS_IOCTL_INIT _IO(DBGLCDS_IOC_MAGIC, 1)
#define DBGLCDS_IOCTL_SLEEP _IO(DBGLCDS_IOC_MAGIC, 2)
#define DBGLCDS_IOCTL_WAKEUP _IO(DBGLCDS_IOC_MAGIC, 3)
#define DBGLCDS_IOCTL_OPENBL _IO(DBGLCDS_IOC_MAGIC, 4)
#define DBGLCDS_IOCTL_CLOSEBL _IO(DBGLCDS_IOC_MAGIC, 5)

56楼 hgd_dingjun
2013-04-08 16:44发表
[回复]



[cpp]
view plaincopy

switch( id )
{
......
case 3:
{
struct dbglcds_setting_table param =
{
0,
0,
{0,},
};
ret = ioctl(fd, DBGLCDS_IOCTL_OPENBL, ¶m);
if(ret)
{
TEST_LOG("dbglcds fail SLEEP!!\n");
return TEST_FAIL;
}
TEST_LOG("dbglcds SLEEP successfully!!\n");
return TEST_SUCCESS;
}
break;
......
}

Re: hgd_dingjun
2013-04-08 16:47发表
[回复]

回复hgd_dingjun:这个是应用程序的代码片段

55楼 hgd_dingjun
2013-04-08 16:41发表
[回复]

评论区怎么贴源代码,好像代码超出字数限制?!

54楼 hgd_dingjun
2013-04-08 16:40发表
[回复]



[cpp]
view plaincopy

static int dbglcds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct dbglcds_android_dev* dev;
dev = container_of(inode->i_cdev, struct dbglcds_android_dev, c_dev);
switch(cmd)
{
case DBGLCDS_IOCTL_RESET:
{
}
break;
case DBGLCDS_IOCTL_INIT:
{
lcm_init();
}
break;
case DBGLCDS_IOCTL_SLEEP:
{
lcm_suspend();
}
break;
case DBGLCDS_IOCTL_WAKEUP:
{
lcm_resume();
}
break;
case DBGLCDS_IOCTL_OPENBL:
{
lcm_suspend();
}
break;
case DBGLCDS_IOCTL_CLOSEBL:
{
lcm_suspend();
}
break;
}
return ret;
}

Re: hgd_dingjun
2013-04-08 16:47发表
[回复]

回复hgd_dingjun:这个是驱动文件的代码片段

53楼 hgd_dingjun
2013-04-08 16:34发表
[回复]

罗老师,您好!

我参照您的Android系统源代码情景分析一书写了一个dbglcdsdrv.c驱动文件和dbglcds_test.c的应用测试文件,功能就是应用程序通过ioctl直接写lcd寄存器以控制lcd进入睡眠和退出睡眠的两项简单功能,但目前遇到一旦测试进入睡眠的功能时,屏就变白,这可以认为控制到了吗?但接着就出现重启,重启信息(Unable to handle kernel NULL pointer dereference at virtual address 00000000 ):

[ 225.912845] DBGLCDSDRV dbglcds_ioctl

[ 225.913280] *******************dbglcds lcm_suspend *******************

[ 225.914113] [DSI] [DISP] - kernel - DSI_set_cmdq_V2. DSI_CMDQ+0000 : 0x00281500

[ 225.915063] [DSI] [DISP] - kernel - DSI_set_cmdq_V2. DSI_CMDQ+0000 : 0x00101500

[ 225.915978] Unable to handle kernel NULL pointer dereference at virtual address 00000000

[ 225.916993] pgd = ced48000

[ 225.917316] [00000000] *pgd=0974a031, *pte=00000000, *ppte=00000000

[ 225.918189] Internal error: Oops: 80000007 [#1] PREEMPT

[ 225.938389]

[ 225.938392] SP: 0xc83b3e20:

......

......

......

罗老师是否能够帮忙分析下?稍后我贴出源代码

Re: 罗升阳
2013-04-08 17:55发表
[回复]

回复hgd_dingjun:lcm_suspend有个空指针,应该是没初始化好。

Re: hgd_dingjun
2013-04-08 19:23发表
[回复]

回复Luoshengyang:罗老师,您好!

表面上我未看出哪里存在空指针,由于字数的限制,未能贴出完整代码,只能说到哪贴到哪,请见谅!如果您有高见,请不吝赐教,谢谢!

Re: 罗升阳
2013-04-09 09:39发表
[回复]

回复hgd_dingjun:你要跟进去看看,还有你是不是应该先lcm_init一下再lcm_suspend啊。

Re: hgd_dingjun
2013-04-08 19:19发表
[回复]



[cpp]
view plaincopy

/*dbglcds_setting_table结构体定义如下:*/
static struct dbglcds_setting_table {
unsigned reg;
unsigned char cnt;
unsigned char dat_list[64];
};
struct dbglcds_android_dev {
struct dbglcds_setting_table val;
struct semaphore sem;
struct cdev c_dev;
};

Re: hgd_dingjun
2013-04-08 19:12发表
[回复]



[cpp]
view plaincopy

/*罗老师,您好!*/
/*lcm_suspend实现如下:*/
static void push_table(struct dbglcds_setting_table *table, unsigned int count, unsigned char force_update)
{unsigned int i;
for(i = 0; i < count; i++) {
unsigned cmd;
cmd = table[i].reg;
switch (cmd) {
case REGFLAG_DELAY :
MDELAY(table[i].cnt);
break;
case REGFLAG_END_OF_TABLE :
break;
default:
DSI_set_cmdq_V2(cmd, table[i].cnt, table[i].dat_list, force_update);
}
}
}
static struct dbglcds_setting_table lcm_deep_sleep_mode_in_setting[] = {
// Display off sequence
{0x28, 1, {0x00}},
// Sleep Mode On
{0x10, 1, {0x00}},
{REGFLAG_DELAY, 10, {}},//{REGFLAG_DELAY, 150, {}},
{REGFLAG_END_OF_TABLE, 0x00, {}}
};
static void lcm_suspend(void)
{
printk("*******************dbglcds lcm_suspend ******************* \n");
push_table(lcm_deep_sleep_mode_in_setting, sizeof(lcm_deep_sleep_mode_in_setting) / sizeof(struct dbglcds_setting_table), 1);
}

52楼 hgd_dingjun
2013-03-27 16:12发表
[回复]

罗老师,您好!

我现在需求是写一个应用,该应用需要将lcd的初始化代码放到T卡上,通过上层java应用读取该文件的参数,然后又通过java应用直接写回屏的寄存器中,重新初始化lcd,看了您的例子是通过读取或者写入一个参数,那么,我现在是需要写入很多的参数,例如屏的init代码如下:

//part I reset pin

SET_RESET_PIN(1);

SET_RESET_PIN(0);

MDELAY(1);

SET_RESET_PIN(1);

MDELAY(120);

//part II set register code

{

。。。。。。

{0x00,1,{0x00}},

{0x00,1,{0xc7}},//Address shift

{0xCf,1,{0x00}},

{0xCE,14,{0x30,0x02,0x03,0x5e,0x00,0x00,0x00,0x30,0x03,0x03,0x5f,0x00,0x00,0x00}},

{0x00,1,{0xc9}},//Address shift

{0xCf,1,{0x00}},

。。。。。。

{REGFLAG_END_OF_TABLE, 0x00, {}}

请问这种情况用什么方法实现比较好?!期待您的回复,谢谢!

Re: 罗升阳
2013-03-27 16:26发表
[回复]

回复hgd_dingjun:你可以在你的驱动里面实现一个ioctl接口,然后定义一个结构体来接收数据,这个结构体随便你定义,要接收多少个参数都可以。

Re: hgd_dingjun
2013-03-27 17:28发表
[回复]

回复Luoshengyang:罗老师,您好!

由于我刚刚接触android驱动开发,所以很多地方还不太明白,您说在驱动里面实现一个ioctl接口,我是否可以将这个接口直接写在现有的lcd驱动里,还是需要另外建立一个新文件来写?还有关于ioctl接口实现的细节是否有可参考的实例?谢谢!

51楼 livagain1988
2013-03-26 15:37发表
[回复]

您好,我copy您的代码直接编译成功,有hello.o,但是最后在虚拟机上面没有看到hello设备,能给些建议吗?不知到哪里除了问题

50楼 nexttake
2013-03-18 11:04发表
[回复]

老罗你好:hello.o 与在unbuntu 平台编译出了的hello.ko的关系是什么呢?

在android srouce ,如果不想全编整个kernel而单独编译某一个驱动如何编译呢?

Re: 罗升阳
2013-03-18 11:06发表
[回复]

回复nexttake:建议你看一下《Linux Device Drivers》这本书,你的问题在这本书里面都有讲的。

Re: nexttake
2013-03-18 14:20发表
[回复]

我编译出了hello.o

adb shell 进入android

insmod hello.o

insmod: init_module 'hello.o' failed (Exec format error)

可是我的代码的内核版本 和 android系统是一致的

求指教!

Re: all8023
2013-03-29 08:52发表
[回复]

回复nexttake:insmod 命令加载的应该是.ko文件吧,

49楼 xuexingyang
2013-03-04 15:06发表
[回复]

建议老罗把每篇文章的后面“下一篇”链接修改成正确的文章,不要都是指到第一篇,方便大家阅读,谢谢。

Re: 罗升阳
2013-03-04 15:21发表
[回复]

回复xuexingyang:这个是CSDN的BUG。

48楼 hao1056531028
2013-02-25 20:49发表
[回复]

我在android中照着你这个写一个字符设备驱动测试程序时,我已经注册读和写函数在struct file_operations结构中,但是在我open这个字符设备完之后,我调用read和write函数,只有read函数能够调用,我在注册到struct file_operations中的写函数最前面打印log发现:调用write函数并没有调用这个注册写函数,请问这是什么情况?现在只有读函数可以使用

Re: 罗升阳
2013-02-25 22:20发表
[回复]

回复hao1056531028:先看看调write得到的返回值是多少。

47楼 ch_jason
2013-02-25 17:45发表
[回复]

修改的应该是drivers/Kconfig,drivers/Makefile,arch/arm/Kconfig,才能编译出hello.o

46楼 daizhx
2013-02-04 13:55发表
[回复]

LZ v5写的太好了

45楼 zero_create
2012-12-26 19:08发表
[回复]

谢谢!

根据这个实践下来,很有收获

44楼 水弟不水
2012-12-19 20:21发表
[回复]

想问下,您是用什么软件编写源代码的啊,刚到ubuntu下都不会

Re: 天才2012
2012-12-22 11:16发表
[回复]

回复a6624624:恩,csdn有这样给VIm配置为超级IDE的博客

Re: 罗升阳
2012-12-19 22:37发表
[回复]

回复a6624624:VIM,有各种插件,你可以把它配置成一个强大的IDE。

43楼 insoonior
2012-11-14 23:25发表
[回复]

罗老师,我看一下device_create的定义:struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata,const char *fmt, ...)

temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);

应该是这样的吧:

temp = device_create(hello_class, NULL, dev, NULL, "%s", HELLO_DEVICE_FILE_NAME);

另外请问一下老师:

cat /dev/hello 时出现了无限调用驱动的hello_read,这是为什么呢?打印出来发现传入hello_read的count值为4096

echo 1 > /dev/hello 时出现11次调用hello_write,传入hello_write的count值为2。

Re: 罗升阳
2012-11-15 02:47发表
[回复]

回复insoonior:是的,你说的没错,这里写错了,不过之所以能正常工作,是因为函数device_create有一个可变参数列表,下面这种写法:

temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);

参数drvdata等于“%s”,fmt等于HELLO_DEVICE_FILE_NAME,即“hello”,由于里面没有%s或者%d之类的格式化符号,因此,就能正常工作,不过是歪打正着了。多谢你的指出,新出的书《Android系统源代码情景分析》也有同样的错误,已经列在勘误表中:http://blog.csdn.net/luoshengyang/article/details/8116866。

设备文件/dev/hello不能用cat或者echo命令来读写,读写它只能用API函数read或者write来进行,具体可以参考:/article/1363573.html。

42楼 dfshk
2012-09-20 09:46发表
[回复]

老师,请问一下开发android底层或是内核程序的时候,一定需要下载android源码吗?可以用其他工具或是其他开发平台进行开发编译吗?(现在想开发android的一些底层的应用程序,尽量支持大部分现有的android手机,也就是一些底层的app,提供给手机下载、安装和使用)

41楼 herodie
2012-09-05 19:49发表
[回复]

呵呵~~今天把楼主的code复制到自己的kernel里试了一把,稍微改了点就很顺利地编译并运行了。仔细看了下insmod和rmmod时的打印和调用顺序。太谢谢楼主了~~~还是直接实践下印象最深。

40楼 tomew
2012-08-22 10:39发表
[回复]

请问err = device_create_file(temp, &dev_attr_val);这句里面的dev_attr_val在哪里定义的

Re: 罗升阳
2012-08-22 11:04发表
[回复]

回复tomew:static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

Re: tomew
2012-08-22 14:31发表
[回复]

回复Luoshengyang:因为底层的函数不熟悉,能不能展开一下啊,包括include的一些文件在哪

另外我看了你的硬件抽象层一章,麻烦问一下

硬件抽象层编写规范 模块文件的命令规范 结构体定义规范

在哪里

Re: 罗升阳
2012-08-22 15:01发表
[回复]

回复tomew:这样讲就太细了,这个需要自己先了解一下Linux驱动的知识的,建议看看《Linux Device Drivers》这本书。

HAL规范定义在hardware/libhardware/include/hardware/hardware.h

文件中

39楼 qq258711519
2012-07-26 17:53发表
[回复]

有种豁然开朗的感觉!真心谢谢老罗老师!!!

38楼 doom66151
2012-07-26 14:50发表
[回复]

写的好,谢谢分享。其实使用ldd里面例子就挺好的。

信号量的初始化应该是sema_init (&(dev->sem), 1) ;不过这里用mutex会更简单吧

Re: 罗升阳
2012-07-26 19:37发表
[回复]

回复doom66151:init_MUTEX的实现就是sema_init(sem, 1),不过新的内核把init_MUTEX移除了。LDD很经典,文章的例子就是参考了LDD来写的。

37楼 sunjianli2007
2012-07-26 10:43发表
[回复]

你好,我已经可以再自己编译的内核模拟器上加载简单的内核模块了,但是,在将.ko模块加载到默认模拟器时就报错了insmod: init_module 'hello.ko' failed (Function not implemented),有人说模拟器默认的内核未开启模块加载功能,要进行设置后在重新编译内核,我想知道除了这种方法还有其他方法么?还有在android手机上也是和模拟器一样默认不允许加载内核模块么?

Re: 罗升阳
2012-07-26 19:33发表
[回复]

回复sunjianli2007:默认应该都是禁止加载模块的,估计是因为安全的原因,厂商本来就是不希望你去改它的系统的。内核都禁止你加载了,想不出还有什么办法。。。

Re: sunjianli2007
2012-08-01 20:09发表
[回复]

回复Luoshengyang:既然android手机的系统默认是不支持LKM的,那么开发的驱动程序该怎么应用呢?

Re: 罗升阳
2012-08-01 20:23发表
[回复]

回复sunjianli2007:只能自己拿来玩了,或者去做系统开发。

36楼 dongshengzou95
2012-07-20 10:33发表
[回复]

请问楼主,我编译正常完成,但是命令行运行模拟器时,不能弹出qemu界面,不知为何?命令是./out/host/linux-x86/bin/emulator -system ./out/target/product/generic/system.img -data ./out/target/product/generic/userdata.img -ramdisk ./out/target/product/generic/ramdisk.img -kernel
./kernel/arch/arm/boot/uImage -skin /jingda/temp/TI_Android_GingerBread_2_3_Sources/sdk/emulator/skins/WVGA800 @android_1

Re: 罗升阳
2012-07-20 11:21发表
[回复]

回复dongshengzou95:先试一下用默认的镜像文件启动模拟器,有没有问题。如果没有的话,再一步一步排除。。。看看是哪个文件有问题。

35楼 baoyu512
2012-05-14 17:31发表
[回复]

为什么我执行了emulator命令后,模拟器是出来了,但是命令行去卡在那儿了,输入adb shell命令没反应?? 楼主帮忙啊

34楼 Rickbeyond
2012-05-14 12:34发表
[回复]

麻烦楼主大人帮忙看看:

drivers/built-in.o:(.data+0x20f5c): undefined reference to `hello_write'

drivers/built-in.o:(.data+0x20fc4): undefined reference to `hello_val_store'

make: *** [.tmp_vmlinux1] 错误 1

这错误怎么跟built-in扯上了,我是新手,不知道如何修改

Re: Rickbeyond
2012-05-14 13:51发表
[回复]

回复Rickbeyond:抱歉,找到原因了,代码拷贝少了这两部分...

Re: ging450
2013-02-19 18:56发表
[回复]

回复Rickbeyond:什么原因?我也出现这错误

33楼 memorysense
2012-04-25 14:19发表
[回复]

编译内核时出现:

drivers/char/goldfish_tty.c: In function 'goldfish_tty_probe':

drivers/char/goldfish_tty.c:233: error: 'IO_START' undeclared (first use in this function)

drivers/char/goldfish_tty.c:233: error: (Each undeclared identifier is reported only once

drivers/char/goldfish_tty.c:233: error: for each function it appears in.)

make[2]: *** [drivers/char/goldfish_tty.o] 错误 1

make[1]: *** [drivers/char] 错误 2

make: *** [drivers] 错误 2

make: *** 正在等待未完成的任务....

32楼 new_abc
2012-04-16 16:01发表
[回复]

这个hellolworld的含金量高,要顶的!

31楼 z1w2p32012
2012-04-07 16:40发表
[回复]

楼主辛苦了,写的非常棒。但是我遇到一个问题,能帮你忙解答一下吗?问题是:我下到真机了面 dev 、proc、class 下面的文件都能看到,就是echo 和 cat 命令都不能正常执行。执行情况如下:

# cd /sys/class/hello

cd /sys/class/hello

# cd hello

cd hello

# cat val

cat val

然后就停在那了,没有返回,我也不能重新敲命令。我也没看到任何log,有没有遇到同样问题的,帮忙解答一下。

十分谢谢!

Re: z1w2p32012
2012-04-14 18:21发表
[回复]

前一段时间忙别的了,这个问题我后来调试了一下找到原因了,所以写出来,原因就是:init_MUTEX这个函数在我的平台没有,我用的是sema_init(&dev->sem,0);初始值不对,要改成sema_init(&dev->sem,1);就OK了,好像初始为0,在down_interruptible(&(dev->sem))) ;时睡眠在里面了,具体原因不是很理解,如果有懂得帮忙解析一下。谢谢!

Re: c_j_zhou
2012-09-11 10:54发表
[回复]

回复z1w2p32012:多谢,支持楼主~多谢这位大哥的解决与分享~

Re: Rickbeyond
2012-05-17 16:38发表
[回复]

回复z1w2p32012:我也碰到了这个问题,init_MUTEX编译报错,改成sema_init(&dev->sem,0)可以通过编译,但在adb shell中cat hello就卡死。我连的是真机,不是在模拟器环境下。找不到解决方法。

30楼 yefei089
2012-03-23 11:35发表
[回复]

楼主你好!我接触android有差不多一个月了,看了你的文章受益菲浅。

我在android2.2下按照你的步骤,make menuconfig出来了那个hello驱动的选项,但是选‘y’时,然后make,并没有编译hello模块。hello.o文件没有生成,这是什么原因呢?请教下!

29楼 thrill007
2012-03-09 10:07发表
[回复]

罗老师,

我的hello驱动写进去和读出来不一样,所以我着手开始调试hello driver,但是发现我添加的printk(KERN_ALERT。。。)语句无输出。

在hello_init()里面添加的printk都可以输出(随着kernel启动输出到串口终端,dmesg也可以看到), 但是在运行hello后,__hello_set_val()和__hello_get_val()里添加的printk都没有输出,为何?

28楼 jqyp324
2012-02-25 13:21发表
[回复]

楼主,我是编译的goldfish内核,也是4.0.3最新的代码,所以和你上面些的有些不一样,但是这些都没有关系,我照着你的博客顺序做到了这篇,在编译的时候出现了In file included from fs/coda/psdev.c:44:

include/linux/coda.h:223: error: expected specifier-qualifier-list before 'u_quad_t'

make[2]: *** [fs/coda/psdev.o] 错误 1

make[1]: *** [fs/coda] 错误 2

make: *** [fs] 错误 2

这样的错误,希望楼主能帮助解决

Re: chenlong12580
2012-02-28 16:58发表
[回复]

回复jqyp324:可见看看http://blog.csdn.net/livingpark/article/details/5929071

27楼 ludeng8
2011-12-19 12:16发表
[回复]

楼主有出书么,必火

Re: 罗升阳
2011-12-19 21:26发表
[回复]

回复ludeng8:非常感谢你的建议~

26楼 cyq1028
2011-11-16 15:00发表
[回复]

写得很认真,真的用心了,佩服!

25楼 Righthek
2011-11-11 09:08发表
[回复]

佩服,罗博主写得很好呀!谢谢分享!针对有的博友说在结构体proc_dir_entry里没有owner成员,在linux2.6.29及之前的内核版本都有owner成员。在linux2.6.30以上的内核里,就没有了ower成员,而多了一个struct list_head pde_openers; /* who did ->open, but not ->release */ 。 博主,这个问题能否解决呢?

Re: 罗升阳
2011-11-11 22:38发表
[回复]

回复Righthek:这里大概解释了一下移除的原因:https://lkml.org/lkml/2009/1/26/342

Re: Righthek
2011-11-14 19:17发表
[回复]

回复Luoshengyang:谢谢博主为我们解答

Re: 罗升阳
2011-11-11 22:37发表
[回复]

回复Righthek:刚才查了一下,从2.6.30开始,的确是把proc_dir_entry结构体的owner域移除了,这样应该不用管它了吧。我手头没有2.6.30的代码,最好是在代码里面搜一下用到了proc_dir_entry结构体的地方,看看它们是怎么处理的。

24楼 flxue
2011-11-07 23:57发表
[回复]

LZ的Hello world很棒!從後的博文看到前,再從前面往後看,每篇都花了不少心思,很厲害!實在佩服!

23楼 floweriswho
2011-11-02 23:38发表
[回复]

罗老师 我卡住了

在编译内核时提示 错误 。。。以前是好使的。。。

帮我看看 。。。

li@li-HP-ProBook-4421s:/usr/android_src/kernel/common$ make

make: arm-eabi-gcc:命令未找到

CHK include/linux/version.h

make[1]: “include/asm-arm/mach-types.h”是最新的。

CHK include/linux/utsrelease.h

SYMLINK include/asm -> include/asm-arm

CC kernel/bounds.s

/bin/sh: arm-eabi-gcc: not found

make[1]: *** [kernel/bounds.s] 错误 127

make: *** [prepare0] 错误 2

li@li-HP-ProBook-4421s:/usr/android_src/kernel/common$

Re: 罗升阳
2011-11-03 21:26发表
[回复]

回复floweriswho:交叉编译工具没设置好~~回到前面一篇文章《在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)》去看看

Re: floweriswho
2011-11-03 23:19发表
[回复]

回复Luoshengyang:哦!原来是 环境变量没配。。。

:-)

希望早日看到 java实现部分

C语言 不懂阿。。。

22楼 chenlingwx
2011-11-02 17:22发表
[回复]

罗老师您好:

因为这个“hello”是你虚拟的设备,所以想问下,如果有个真实的设备,怎么写核心层的驱动呢?

我这里的情况是这样的:

我有一台三星P7510的pad,在pad上面插上U盘能识别也能复制U盘里的东西,对应该U盘的是/dev/sg0设备文件(因为不插U盘没有sg0,插了U盘只增加1个sg0,所以我想就是这个对应了)。

好下面头疼的问题来了,我把U盘拔掉,换我自己的一个USB接口的小型设备(不是U盘)插上,pad能向这个小型设备供电(因为小型设备亮灯了),但是在/dev目录下没有看见增加任何设备文件与这个小型设备对应,这样我就无法操作这个小型设备了。

所以想请您指导下,在这个时候如何能让/dev目录下增加那个对应的设备文件呢?求罗老师指导下思路,或者罗老师说说您自己的看法也可以。

如有指导,万分感谢!

Re: chenlingwx
2011-11-02 17:24发表
[回复]

回复chenlingwx:总之,就是想通过/dev下的设备文件去操作自己的这个小型USB设备,但现在貌似找不到对应的设备文件。

Re: 罗升阳
2011-11-02 22:00发表
[回复]

回复chenlingwx:估计是现有的驱动识别不了你的设备,因此,你在/dev目录下看不到相应的设备文件,也操作不了它,这样的话,得自己写一个驱动程序了,或者去跟厂家要驱动

21楼 jxgtalent
2011-10-26 15:42发表
[回复]

我反复看了你这篇文章很多次,也看了我自己代码中的修改。没发现有错误的地方,但是加上这个模块后编译总是报告此错误

20楼 jxgtalent
2011-10-26 15:41发表
[回复]

报告以下错误,求教了

root@flyingfisher-virtual-machine:/Work/ABC/prettyboy/linux_kernel/kernel.git# make

HOSTLD scripts/kconfig/conf

scripts/kconfig/conf -s arch/arm/Kconfig

drivers/video/Kconfig:1897:warning: multi-line strings not supported

CHK include/linux/version.h

SYMLINK include/asm-arm/arch -> include/asm-arm/arch-goldfish

make[1]: `include/asm-arm/mach-types.h' is up to date.

CHK include/linux/utsrelease.h

CALL scripts/checksyscalls.sh

<stdin>:1097:2: warning: #warning syscall fadvise64 not implemented

~~~~~中间略去~~~~~~~~~

CHK include/linux/compile.h

LD drivers/built-in.o

arm-eabi-ld: error: cannot open drivers/hello/built-in.o: No such file or directory

make[1]: *** [drivers/built-in.o] Error 1

make: *** [drivers] Error 2

Re: 罗升阳
2011-10-26 21:26发表
[回复]

回复jxgtalent:看来像是你有编译脚本写得有问题

Re: jxgtalent
2011-10-26 21:48发表
[回复]

已经OK了,我在window用计事本写的脚本。后来看别处的脚本,发现后面的换行符。所以拷贝了个修改了下编译就OK了

19楼 stephen318tao
2011-10-25 14:32发表
[回复]

楼主

我在arch/arm/Kconfig中找不到menu “Device Drivers”

然后make menuconfig就报出

make: *** No rule to make target `menuconfig'. Stop.

Re: 罗升阳
2011-10-25 21:38发表
[回复]

回复stephen318tao:你看看这篇文章《在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)》,make menuconfig之前要配置一下

Re: caoyiustc
2011-11-16 14:05发表
[回复]

回复Luoshengyang:楼主麻烦了,我也没有生成这个,make menuconfig这些我也都弄了,肯定什么地方做的有问题,具体怎么配置楼主能详细讲讲吗?麻烦了麻烦了,我被卡在这里走投无路了。

Re: 罗升阳
2011-11-16 23:34发表
[回复]

回复caoyiustc:这篇文章《在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)》已经讲得比较详细了~操作的过程中还是要仔细一点的

Re: songjam
2012-08-14 18:00发表
[回复]

2.3.7及4.0上测试,好像不用修改arch/arm/Kconfig也可以的回复Luoshengyang:

Re: jgm928
2013-06-18 15:45发表
[回复]

回复songjam:是不是在文件末尾有类似行

source "drivers/Kconfig",所以不用配置了

18楼 feimor
2011-10-20 15:18发表
[回复]

楼主,有一个问题请教:

可不可以跳过kernel设备驱动层,直接用hal层控制底层模块?

Re: 罗升阳
2011-10-20 21:27发表
[回复]

回复feimor:你的意思是跳过内核直接操作硬件?不可以,HAL层是位于用户空间的,不能直接访问硬件。

17楼 ThinkZhang
2011-10-14 11:00发表
[回复]

编译可以,hello.o也生成了,可就是在dev下找不到hello,这是什么原因。。。

Re: ThinkZhang
2011-10-14 16:23发表
[回复]

回复s762888517:可以了。版本错了

Re: caoyiustc
2011-11-15 15:08发表
[回复]

回复s762888517:您好,能解释一下吗,我是小白,编译之后没有出现/dev/hello。我是在真机上运行的。

Re: 罗升阳
2011-11-15 22:44发表
[回复]

回复caoyiustc:有点看不明白你的问题,你是用带有这个驱动模块的内核来启动你的机器的么?

Re: caoyiustc
2011-11-16 13:00发表
[回复]

回复Luoshengyang:是的,在drivers目录下添加了hello文件夹之后编译,用编译后生成的zImage去刷手机内核,之后用adb shell之后看不到您说的这个目录。

Re: 罗升阳
2011-11-16 23:31发表
[回复]

回复caoyiustc:那要debug一下了,cat一下/proc/kmsg,看看日志里面有没有什么提示

16楼 wantianpei
2011-09-28 16:05发表
[回复]

楼主:

"六. 修改arch/arm/Kconfig和drivers/kconfig两个文件"

修改drivers/kconfig 这个文件可以理解, 可是为什么要修改arch/arm/Kconfig这个文件呢? 不理解.

而且我发现并不是所有的驱动都会在arch/arm/Kconfig里添加一份拷贝的. 所以不是很明白.请多多指教!

Re: 罗升阳
2011-09-28 23:06发表
[回复]

回复wantianpei:实际上执行make menuconfig命令时,首先是会去读取arch/$(ARCH)/Kconfig,$(ARCH)就是你要在哪个体系架构上编译这个内核。

一般各体系架构下的Kconfig文件会通过命令soruce "drivers/Kconfig"把drivers目录下的Kconfig目录包含进来,例如x86体系架构,但是arm体系架构比较特殊,它没有把drivers/Kconfig文件包含进去,因此,它把drivers/Kconfig文件的内容都拷贝进去。

事实上,对于arm体系架构,这里我们不在drivers/Kconfig文件加上我们自己的Kconfig也可以,但是为了兼容其它体系架构,就需要在drivers/Kconfig文件也加上自己的Kconfig了。

15楼 kevinacc
2011-09-20 14:59发表
[回复]

$ echo '5' > hello

cannot create hello: permission denied

出现了权限的问题。请问博主有没遇到这样的问题。

Re: kevinacc
2011-09-20 15:01发表
[回复]

回复kevinacc:修改了权限就可以了,请问有可能是哪些原因?

Re: Anotherzhou
2011-10-11 19:14发表
[回复]

回复kevinacc:直接su一下就可以了,是你在登录的时候登录方式没有选择adb登录吧。

Re: 罗升阳
2011-09-20 15:08发表
[回复]

回复kevinacc:参与一下《在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序》这篇文章

14楼 wantianpei
2011-09-07 00:39发表
[回复]

楼主得把这几行代码顺序改成这样:

/*访问设置属性方法*/

static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);

static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/

static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);

////////反正我按照你上面那样编译是会报错的,根据提示改成这样就OK通过了。

Re: 罗升阳
2011-09-07 00:51发表
[回复]

回复wantianpei:这里拷代码的时候弄反了,呵呵,马上改过来,多谢指正~

Re: SzZhangfq
2011-10-11 10:20发表
[回复]

回复Luoshengyang:自己调整一下就好。

13楼 太阳山
2011-08-26 21:51发表
[回复]

牛,看不懂

12楼 shcalm
2011-08-26 10:23发表
[回复]

您好,问您个问题,我编译内核的时候,把根目录makefile里面把arch修改为arm以后,修改drivers下面的kconfig,然后make menuconfig,修改的没有起作用,如果我把根目录下面的makefile恢复原状,再make menuconfig,drivers下面kconfig的修改就显示出来了,您知道这是什么原因造成的吗?多谢了

Re: 罗升阳
2011-08-26 21:39发表
[回复]

回复shcalm:看不出来这两者有什么联系~

一个是编译时候的选项,一个编译前的配置选项

11楼 mjk19871208ioy
2011-08-21 16:37发表
[回复]

楼主:编译上面写的Hello源码时会出现以下的错误,求解释啊!!/kernel/drivers/hello/hello.c: In function 'hello_create_proc':

/kernel/drivers/hello/hello.c:205: error: 'struct proc_dir_entry' has no member named 'owner'

即9楼的问题啊!

Re: 罗升阳
2011-08-21 16:51发表
[回复]

回复mjk19871208ioy:1. struct proc_dir_entry定义在<linux/proc_fs.h>文件里面,你去看看它的定义有没有owner成员变量

2. 看看你的hello.c文件有没有#inculde <linux/proc_fs.h>

3. 再不行就检查一下代码哪些不小心写错了

Re: guidian103
2012-02-16 14:29发表
[回复]

检查过来,android4.0的结构体没有那个 成员变量,不知道可以用什么替代掉,或者干脆不要。

10楼 bychen_19851208
2011-08-02 15:16发表
[回复]

LZ,android2.3源码,根目录没有kernel目录啊

Re: 罗升阳
2011-08-02 15:23发表
[回复]

回复bychen_19851208:------------------------------------------------------------------------------

一. 参照前面两篇文章在Ubuntu上下载、编译和安装Android最新源代码和在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)准备好Android内核驱动程序开发环境。

------------------------------------------------------------------------------

9楼 navycsu
2011-07-28 22:38发表
[回复]

楼主,有个问题,内核make的时候提示:hello_val_show, hello_val_store

declare static,but never defined!

把定义该变量的语句放前面后不报错了

之后又说proc_dir_entry没有owner这个变量,然后把

entry->owner = THIS_MODULE;注释掉后,编译通过。不知道会有什么影响...

Re: 罗升阳
2011-07-28 23:27发表
[回复]

回复navycsu:前面的hello_val_show, hello_val_store只是声明,你看一下你后面这两个函数定义的时候的原型和声明的时候是不是一样的。

struct proc_dir_entry是一个结构体,定义在<linux/proc_fs.h>文件里面,怎么会没有owner这个成员变量呢,你去看一下。

Re: liyy0908
2011-10-28 10:46发表
[回复]

回复Luoshengyang:真的没有呢!

8楼 hui05504
2011-07-28 20:30发表
[回复]

回复hui05504:你的模拟器内核是不是把这篇文章所说的驱动程序编译进去了?如果界面是一个黑窗口的话?估计是驱动程序哪个地方有问题了。你可以试一下把驱动程序编译成可加载模块,等模拟器跑起来之后,再通过adb push命令把主机上的驱动模块上传到模拟器中,然后再用insmod命令来加载,这样就可以一步步调试,找出问题。

---谢谢,驱动我编译进去了,没有出现黑窗口,emulator好像就是起不来,奇怪,我用的是英码开发板的代码,都编译通过并且可以在板子上跑了,但是最近想试试模拟器,来调下HAL:)

7楼 hui05504
2011-07-28 15:51发表
[回复]

楼主,有个问题,就是我运行了emulator后,运行adb shell命令提示device off,运行了好几次都是这个错误,看了任务管理器,发现emulator启动了,但是sleep状态,可以指导下么

Re: 罗升阳
2011-07-28 16:06发表
[回复]

回复hui05504:你的模拟器内核是不是把这篇文章所说的驱动程序编译进去了?如果界面是一个黑窗口的话?估计是驱动程序哪个地方有问题了。你可以试一下把驱动程序编译成可加载模块,等模拟器跑起来之后,再通过adb push命令把主机上的驱动模块上传到模拟器中,然后再用insmod命令来加载,这样就可以一步步调试,找出问题。

6楼 asdadasdasd111
2011-07-28 11:35发表
[回复]

对于毫无C++经验的人来说,楼主能否给点建议,不胜感激

Re: 罗升阳
2011-07-28 12:12发表
[回复]

回复asdadasdasd111:那就没办法了,只能学一下了,推荐Thinking in C++这本书。作为一个程序员,各种开发语言还是都要了解一下的,当然不需要每一种语言都很熟悉。个人经验,只要熟悉了C和C++,其它语言学起来都比较容易上手,因为它们都是相通的。

5楼 大只辉
2011-07-21 09:48发表
[回复]

继续写,支持!

4楼 vlone836
2011-07-16 14:46发表
[回复]

还有 怎么单独 编译适合于Android内核下的驱动模块,我按照传统的Linux驱动模块编译,有很多错误提示。高手 请指教

Re: 罗升阳
2011-07-16 16:04发表
[回复]

回复vlone836:编译Android内核的驱动模块的方法和一般Linux内核的驱动模块编译的方法是一样的,建议你网上查找一下相关的资料,或者参考一下Linux Device Drivers一书中第2章中的Compiling and Loading一小节。

3楼 vlone836
2011-07-16 14:45发表
[回复]

再加入hello驱动重新编译内核时,出现以下错误drivers/hello/hello.c : file not recognized: File format not recognized

是什么原因造成的?

Re: 罗升阳
2011-07-16 15:12发表
[回复]

回复vlone836:不好意思,我这里有一处笔误,Makefile内容应该是:

obj-$(CONFIG_HELLO) += hello.o

不是hello.c

Re: guidian103
2012-02-16 13:40发表
[回复]

回复Luoshengyang:

drivers/hello/hello.c: In function 'hello_create_proc':

drivers/hello/hello.c:204: error: 'struct proc_dir_entry' has no member named 'owner'

drivers/hello/hello.c: In function '__hello_setup_dev':

drivers/hello/hello.c:233: error: implicit declaration of function 'init_MUTEX'

是不是那个头文件没有添加?

Re: blackzw
2012-09-20 11:21发表
[回复]

回复guidian103:找到问题了,我的include/linux中的proc_fs.h头文件不是原版的,被硬件供应商修改过,proc_dir_entry结构体被修改了。

Re: 才疏学浅的貓
2013-03-06 10:31发表
[回复]

回复blackzw:遇到同样的问题,请教一下怎么解决啊?

Re: blackzw
2012-09-20 10:53发表
[回复]

回复guidian103:同问,遇到相同问题了

Re: vlone836
2011-07-17 11:01发表
[回复]

回复Luoshengyang:谢谢,你的教程写得非常好。 感激不尽。

2楼 ppcst
2011-07-11 10:24发表
[回复]

楼主写的文章非常不错~谢了

1楼 zhuanghuahui
2011-07-09 09:22发表
[回复]

非常好的android字符驱动入门
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: