Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android下led控制(下)--Linux驅動部分

Android下led控制(下)--Linux驅動部分

編輯:關於Android編程

前面寫了兩個博文,一個是Android下,一個是Linux下led控制,但是Linux下那個寫的有很多漏洞和不清楚的地方。這裡寫一篇作為補充,也是我在學習中理解的深入。當然這個可能也會有很多漏洞,如果我有更深入的了解,繼續進行補充。我的開發板是全志科技的CQA83T,成都啟劃公司出的擴展板。

先貼出來驅動源程序的代碼,此代碼的位置在lichee\linux-3.4\drivers\char\led.c:

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LED_IOCTL_SET_ON		1
#define LED_IOCTL_SET_OFF			0
static script_item_u			led_val[5];
static script_item_value_type_e		led_type;
static struct semaphore lock;

//led_open
static int led_open(struct inode *inode, struct file *file)
{
	if (!down_trylock(&lock))
		return 0;
	else
		return -EBUSY;
}

//led_close
static int  led_close(struct inode *inode, struct file *file)
{
	up(&lock);
	return 0;
}

//led_ioctl
static long  led_ioctl(struct file *filep, unsigned int cmd,
		unsigned long arg)
{
	unsigned int n;
	n = (unsigned int)arg;
	switch (cmd) {
		case LED_IOCTL_SET_ON:
			if (n < 1)
				return -EINVAL;
			if(led_val[n-1].gpio.gpio != -1) {
				__gpio_set_value(led_val[n-1].gpio.gpio, 1);
				printk("led%d on !\n", n);
			}
			break;

		case LED_IOCTL_SET_OFF:
		default:
			if (n < 1)
				return -EINVAL;
			if(led_val[n-1].gpio.gpio != -1) {
				__gpio_set_value(led_val[n-1].gpio.gpio, 0);
				printk("led%d off !\n", n);
			}
			break;
	}

	return 0;
}

//led_gpio
static int __devinit led_gpio(void)
{
	int i = 0;
	char gpio_num[10];

	for(i =1 ; i < 6; i++) 
	{
		sprintf(gpio_num, "led_gpio%d", i);

		led_type= script_get_item("led_para", gpio_num, &led_val[i-1]);
		if(SCIRPT_ITEM_VALUE_TYPE_PIO != led_type) {
			printk("led_gpio type fail !");
//			gpio_free(led_val[i-1].gpio.gpio);
			led_val[i-1].gpio.gpio	= -1;
			continue;
		}
	
		if(0 != gpio_request(led_val[i-1].gpio.gpio, NULL)) {
			printk("led_gpio gpio_request fail !");		
			led_val[i-1].gpio.gpio = -1;
			continue;
		}

		if (0 != gpio_direction_output(led_val[i-1].gpio.gpio, 0)) {
			printk("led_gpio gpio_direction_output fail !");
//			gpio_free(led_val[i-1].gpio.gpio);
			led_val[i-1].gpio.gpio = -1;
			continue;
		}	
	}

	return 0;
}

//file_operations
static struct file_operations leds_ops = {
	.owner			= THIS_MODULE,
	.open			= led_open,
	.release		= led_close, 
	.unlocked_ioctl		= led_ioctl,
};

//miscdevice
static struct miscdevice leds_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "led",
	.fops = &leds_ops,
};

//led_remove
static int __devexit led_remove(struct platform_device *pdev)
{
	return 0;
}

//led_probe
static int __devinit led_probe(struct platform_device *pdev)
{
	int led_used;
	script_item_u	val;
	script_item_value_type_e  type;
	
	int err;
	
    printk("led_para!\n");
	type = script_get_item("led_para", "led_used", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
		printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);
		return -1;
	}
	led_used = val.val;
	printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);

	if(!led_used) {
		printk("%s led_used is not used in config,  led_used=%d\n", __FUNCTION__,led_used);
		return -1;
	}

	err = led_gpio();
	if (err)
		return -1;

	sema_init(&lock, 1);
	err = misc_register(&leds_dev);
	printk("======= cqa83 led initialized ================\n");
	
	return err;
}

//platform_device
struct platform_device led_device = {
	.name		= "led",	
};

//platform_driver
static struct platform_driver led_driver = {
	.probe		= led_probe,
	.remove		= __devexit_p(led_remove),
	.driver		= {
		.name	= "led",
		.owner	= THIS_MODULE,
	},
};

//led_init
static int __init led_init(void)
{	
	
     if (platform_device_register(&led_device)) {
        printk("%s: register gpio device failed\n", __func__);
    }
    if (platform_driver_register(&led_driver)) {
        printk("%s: register gpio driver failed\n", __func__);
    }

	return 0;
}

//led_exit
static void __exit led_exit(void)
{
	platform_driver_unregister(&led_driver);	
}


module_init(led_init);
module_exit(led_exit);

MODULE_DESCRIPTION("Led Driver");
MODULE_LICENSE("GPL v2");

首先,在這裡我們應該獲取這樣幾個信息,

 

1、這是一個Linux驅動程序,一個字符驅動,一個雜項字符驅動。從err = misc_register(&leds_dev);可以知道是雜項字符驅動。

2、這裡使用到了Linux的GPIO驅動模型。

3、這個驅動是基於platform機制的。

 

第一,我們先說一說platform機制。

platform機制是Linux2.6引入的一套新的驅動管理和注冊機制,Linux大部分設備驅動中都能使用這套機制。platform是一種虛擬總線,主要用來管理CPU的片上資源具有很好的移植性。platform機制本身的使用並不復雜,由platform_device(總是設備)和platform_driver(總線驅動)兩部分組成,設備用platform_device表示,驅動用platform_driver注冊。系統首先會初始化platform總線,當platform設備想要掛載到總線上時,定義platform_device和platform_driver,然後使用函數platform_device_register注冊platform_device,再使用platform_driver_register函數注冊platform_driver驅動,這裡要記住,platform_device_register它一定要在platform_driver_register之前。也就是一定要先注冊設備再注冊驅動,因為在驅動注冊是,要先查找與之對應的設備,如果能夠找到並匹配成功才能注冊驅動。具體細節下面會分析。

下面,我們來說一下platform總線,platform總線相關的代碼都在內核linux-3.4\drivers\base\platform.c裡面。既然platform總線是在內核啟動時初始化,那麼先列出初始化函數的調用過程,asmlinkagevoid __init start_kernel(void)[linux-3.4\init\main.c] -->static noinline void __init_refok rest_init(void)[linux-3.4\init\main.c] -->static int __init kernel_init(void * unused)[linux-3.4\init\main.c] -->static void __init do_basic_setup(void) [linux-3.4\init\main.c]-->void __init driver_init(void) [linux-3.4\drivers\base\init.c]-->int __init platform_bus_init(void) [linux-3.4\drivers\base\platform.c] ,中括號裡是文件位置,函數platform_bus_init就是platform的總線初始化函數。

來看platform總線初始化函數platform_bus_init,位於linux-3.4\drivers\base\platform.c中:

 

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}
按圖索骥,繼續深入platform總線初始化函數來看early_platform_cleanup函數,這個函數從名字上就可以看出是一個清理函數。我們來看一下這個函數的源碼,位於linux-3.4\drivers\base\platform.c中:

 

 

/**
 * early_platform_cleanup - clean up early platform code
 */
void __init early_platform_cleanup(void)
{
	struct platform_device *pd, *pd2;

	/* clean up the devres list used to chain devices */
	list_for_each_entry_safe(pd, pd2, &early_platform_device_list,
				 dev.devres_head) {
		list_del(&pd->dev.devres_head);
		memset(&pd->dev.devres_head, 0, sizeof(pd->dev.devres_head));
	}
}
從注釋可以看出,這個函數是清除早期的platform設備鏈表,list_for_each_entry_safe的作用是遍歷先前的platform設備鏈表early_platform_device_list
,並清零每一個鏈表節點。

 

下面繼續platform總線初始化函數中的device_register(&platform_bus)的函數,該函數是將platform總線作為設備進行注冊。我們先看參數plat_bus,位於linux-3.4\drivers\base\platform.c中:

 

struct device platform_bus = {
	.init_name	= "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);
參數platform_bus是一個device類型的結構體,下面EXPORT_SYMBOL_GPL是宏,這個宏說明其參數所指向的函數只給有GPL認證的模塊使用。下面來看一下device結構體,位於linux-3.4\include\linux\device.h中:

 

 

struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
};

這個是基本的設備結構體,用於描述設備相關信息設備之間的層次關系,以及設備與總線驅動的關系。其實簡單說在Linux內核裡用這個結構體來表示一個設備,並用於設備在內核中的注冊。網上有較多關於這個結構體的解析這裡就不多說了。
接下來看設備注冊函數device_register,位於linux-3.4\drivers\base\core.c中:

 

 

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
這個函數作用是向系統注冊一個設備,它首先使用函數device_initialize對設備進行初始化,然後使用device_add添加設備。下面分別來看一下這倆函數,但這裡不做解釋,這倆函數都是位於linux-3.4\drivers\base\core.c中:
void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset;
	kobject_init(&dev->kobj, &device_ktype);
	INIT_LIST_HEAD(&dev->dma_pools);
	mutex_init(&dev->mutex);
	lockdep_set_novalidate_class(&dev->mutex);
	spin_lock_init(&dev->devres_lock);
	INIT_LIST_HEAD(&dev->devres_head);
	device_pm_init(dev);
	set_dev_node(dev, -1);
}

int device_add(struct device *dev)
{
	struct device *parent = NULL;
	struct kobject *kobj;
	struct class_interface *class_intf;
	int error = -EINVAL;

	dev = get_device(dev);
	if (!dev)
		goto done;

	if (!dev->p) {
		error = device_private_init(dev);
		if (error)
			goto done;
	}

	/*
	 * for statically allocated devices, which should all be converted
	 * some day, we need to initialize the name. We prevent reading back
	 * the name, and force the use of dev_name()
	 */
	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);
		dev->init_name = NULL;
	}

	/* subsystems can specify simple device enumeration */
	if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
		dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

	if (!dev_name(dev)) {
		error = -EINVAL;
		goto name_error;
	}

	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

	parent = get_device(dev->parent);
	kobj = get_device_parent(dev, parent);
	if (kobj)
		dev->kobj.parent = kobj;

	/* use parent numa_node */
	if (parent)
		set_dev_node(dev, dev_to_node(parent));

	/* first, register with generic layer. */
	/* we require the name to be set before, and pass NULL */
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error)
		goto Error;

	/* notify platform of device entry */
	if (platform_notify)
		platform_notify(dev);

	error = device_create_file(dev, &uevent_attr);
	if (error)
		goto attrError;

	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &devt_attr);
		if (error)
			goto ueventattrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto devtattrError;

		devtmpfs_create_node(dev);
	}

	error = device_add_class_symlinks(dev);
	if (error)
		goto SymlinkError;
	error = device_add_attrs(dev);
	if (error)
		goto AttrsError;
	error = bus_add_device(dev);
	if (error)
		goto BusError;
	error = dpm_sysfs_add(dev);
	if (error)
		goto DPMError;
	device_pm_add(dev);

	/* Notify clients of device addition.  This call must come
	 * after dpm_sysfs_add() and before kobject_uevent().
	 */
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_ADD_DEVICE, dev);

	kobject_uevent(&dev->kobj, KOBJ_ADD);
	bus_probe_device(dev);
	if (parent)
		klist_add_tail(&dev->p->knode_parent,
			       &parent->p->klist_children);

	if (dev->class) {
		mutex_lock(&dev->class->p->mutex);
		/* tie the class to the device */
		klist_add_tail(&dev->knode_class,
			       &dev->class->p->klist_devices);

		/* notify any interfaces that the device is here */
		list_for_each_entry(class_intf,
				    &dev->class->p->interfaces, node)
			if (class_intf->add_dev)
				class_intf->add_dev(dev, class_intf);
		mutex_unlock(&dev->class->p->mutex);
	}
done:
	put_device(dev);
	return error;
 DPMError:
	bus_remove_device(dev);
 BusError:
	device_remove_attrs(dev);
 AttrsError:
	device_remove_class_symlinks(dev);
 SymlinkError:
	if (MAJOR(dev->devt))
		devtmpfs_delete_node(dev);
	if (MAJOR(dev->devt))
		device_remove_sys_dev_entry(dev);
 devtattrError:
	if (MAJOR(dev->devt))
		device_remove_file(dev, &devt_attr);
 ueventattrError:
	device_remove_file(dev, &uevent_attr);
 attrError:
	kobject_uevent(&dev->kobj, KOBJ_REMOVE);
	kobject_del(&dev->kobj);
 Error:
	cleanup_device_parent(dev);
	if (parent)
		put_device(parent);
name_error:
	kfree(dev->p);
	dev->p = NULL;
	goto done;
}

下面我們繼續回到platform總線初始化函數platform_bus_init中,來看總線注冊函數bus_register(&platform_bus_type),先看一下參數platfor_bus_type,位於linux-3.4\drivers\base\platform.c中:

 

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_attrs	= platform_dev_attrs,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

從上面可以看出這是一個bus_type類型的結構體,bus_type類型結構體的定義位於linux-3.4\include\linux\device.h中:

 

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct bus_attribute	*bus_attrs;
	struct device_attribute	*dev_attrs;
	struct driver_attribute	*drv_attrs;

	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	struct iommu_ops *iommu_ops;

	struct subsys_private *p;
};
這是一個設備總線類型結構體,成員變量指出了總線名稱,子設備前綴名(像"foo%u", dev->id),被用作父設備的默認設備,總線屬性,設備屬性,驅動屬性以及一些回調函數。

下面來看一下總線注冊函數bus_register,位於linux-3.4\include\linux\device.h中,這是一個宏定義:

 

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define bus_register(subsys)			\
({						\
	static struct lock_class_key __key;	\
	__bus_register(subsys, &__key);	\
})

繼續看函數_bus_register(subsys, &__key)函數,位於linux-3.4\drivers\base\bus.c中:

 

 

int __bus_register(struct bus_type *bus, struct lock_class_key *key)
{
	int retval;
	struct subsys_private *priv;

	priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->bus = bus;
	bus->p = priv;

	BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

	retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
	if (retval)
		goto out;

	priv->subsys.kobj.kset = bus_kset;
	priv->subsys.kobj.ktype = &bus_ktype;
	priv->drivers_autoprobe = 1;

	retval = kset_register(&priv->subsys);
	if (retval)
		goto out;

	retval = bus_create_file(bus, &bus_attr_uevent);
	if (retval)
		goto bus_uevent_fail;

	priv->devices_kset = kset_create_and_add("devices", NULL,
						 &priv->subsys.kobj);
	if (!priv->devices_kset) {
		retval = -ENOMEM;
		goto bus_devices_fail;
	}

	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);
	if (!priv->drivers_kset) {
		retval = -ENOMEM;
		goto bus_drivers_fail;
	}

	INIT_LIST_HEAD(&priv->interfaces);
	__mutex_init(&priv->mutex, "subsys mutex", key);
	klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
	klist_init(&priv->klist_drivers, NULL, NULL);

	retval = add_probe_files(bus);
	if (retval)
		goto bus_probe_files_fail;

	retval = bus_add_attrs(bus);
	if (retval)
		goto bus_attrs_fail;

	pr_debug("bus: '%s': registered\n", bus->name);
	return 0;

bus_attrs_fail:
	remove_probe_files(bus);
bus_probe_files_fail:
	kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
	kset_unregister(bus->p->devices_kset);
bus_devices_fail:
	bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
	kset_unregister(&bus->p->subsys);
out:
	kfree(bus->p);
	bus->p = NULL;
	return retval;
}
EXPORT_SYMBOL_GPL(__bus_register);
這個函數進行了返回檢測,如果注冊識別,則進行與注冊相反的操作注銷device_unregister。這個函數位於linux-3.4\drivers\base\core.c中:

 

 

/**
 * device_unregister - unregister device from system.
 * @dev: device going away.
 *
 * We do this in two parts, like we do device_register(). First,
 * we remove it from all the subsystems with device_del(), then
 * we decrement the reference count via put_device(). If that
 * is the final reference count, the device will be cleaned up
 * via device_release() above. Otherwise, the structure will
 * stick around until the final reference to the device is dropped.
 */
void device_unregister(struct device *dev)
{
	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
	device_del(dev);
	put_device(dev);
}

附上了英文注釋,解釋很清楚了。

 

到這裡呢,platform總線初始化就結束了,沒有做更多的解釋,主要原因是我也在學習,還有就是每個函數的作用無論是見名知意還是查看函數說明,這個函數的功能是很明確的。
現在呢,platform總線已經初始化完成,下面就是把platform設備和驅動掛載到platform總線上了。

現在我們還是回到開始led驅動函數led.c中,了解過驅動的人都知道,驅動被加載到內核第一次被調用的函數就是其初始化函數,在led.c中:

 

module_init(led_init);
初始化函數led_init的源碼再寫一遍,如下:

 

 

static int __init led_init(void)
{	
	
     if (platform_device_register(&led_device)) {
        printk("%s: register gpio device failed\n", __func__);
    }
    if (platform_driver_register(&led_driver)) {
        printk("%s: register gpio driver failed\n", __func__);
    }

	return 0;
}

這裡第一步是設備注冊函數platform_device_register(&led_device),我們先看參數led_device:

 

 

struct platform_device led_device = {
	.name		= "led",	
};

led_device是一個platform_device類型的結構體,platform_device結構體的定義在linux-3.4\include\linux\platform_device.h中:

 

 

struct platform_device {
	const char	* name;//設備名
	int		id;//設備id
	struct device	dev;//包含設備結構體
	u32		num_resources;//資源個數
	struct resource	* resource;//資源結構體

	const struct platform_device_id	*id_entry;

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

這個結構體裡面封裝了device結構體,resource結構體,說明platform_device是device結構體派生出的一個結構體,platform_device是一個特殊的device。下面來看platform_device中最重要的結構體resource結構體,該結構體位於linux-3.4\include\linux\ioprt.h中:

 

 

struct resource {
	resource_size_t start;//資源起始地址
	resource_size_t end;//資源結束地址
	const char *name;//定義資源名稱
	unsigned long flags;//定義資源類型
	struct resource *parent, *sibling, *child;//資源樹
};

這個結構體表明了設備所擁有的資源。

 

platform_device結構體包含了device結構體,device結構體描述了設備的詳細情況,在面向對象編程中device是所有設備的基類。device結構體在platform總線初始化的時候已經說過了,這裡就不說了。

然後我們來看一下platform_device_register 函數,其位於linux-3.4\drivers\base\platform.c中:

 

/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
這裡首先對設備使用函數device_initalize進行初始化,這個函數在上面platform總線初始化的時候已經說過,這裡不說了。在這是不是可以發散一下,只要設備注冊進內核,無論是總線設備也好,其他設備也好,內核都把他們看成設備,使用同樣的方式初始化。

 

然後使用arch_setup_pdev_archdata(pdev),這個函數位於linux-3.4\drivers\base\platform.c中:

 

/**
 * arch_setup_pdev_archdata - Allow manipulation of archdata before its used
 * @pdev: platform device
 *
 * This is called before platform_device_add() such that any pdev_archdata may
 * be setup before the platform_notifier is called.  So if a user needs to
 * manipulate any relevant information in the pdev_archdata they can do:
 *
 * 	platform_devic_alloc()
 * 	... manipulate ...
 * 	platform_device_add()
 *
 * And if they don't care they can just call platform_device_register() and
 * everything will just work out.
 */
void __weak arch_setup_pdev_archdata(struct platform_device *pdev)
{
}

這個函數目前是一個空函數,根據注釋,這是留給使用者在添加設備前來操作設備相關結構體的,也就是留給用戶根據需要使用的。

最後使用了platform_device_add(pdev)來添加設備。該函數位於linux-3.4\drivers\base\platform.c中:

 

 

/**
 * platform_device_add - add a platform device to device hierarchy
 * @pdev: platform device we're adding
 *
 * This is part 2 of platform_device_register(), though may be called
 * separately _iff_ pdev was allocated by platform_device_alloc().
 */
int platform_device_add(struct platform_device *pdev)
{
	int i, ret = 0;

	if (!pdev)
		return -EINVAL;

	if (!pdev->dev.parent)
		pdev->dev.parent = &platform_bus;

	pdev->dev.bus = &platform_bus_type;

	if (pdev->id != -1)
		dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
	else
		dev_set_name(&pdev->dev, "%s", pdev->name);

	for (i = 0; i < pdev->num_resources; i++) {
		struct resource *p, *r = &pdev->resource[i];

		if (r->name == NULL)
			r->name = dev_name(&pdev->dev);

		p = r->parent;
		if (!p) {
			if (resource_type(r) == IORESOURCE_MEM)
				p = &iomem_resource;
			else if (resource_type(r) == IORESOURCE_IO)
				p = &ioport_resource;
		}

		if (p && insert_resource(p, r)) {
			printk(KERN_ERR
			       "%s: failed to claim resource %d\n",
			       dev_name(&pdev->dev), i);
			ret = -EBUSY;
			goto failed;
		}
	}

	pr_debug("Registering platform device '%s'. Parent at %s\n",
		 dev_name(&pdev->dev), dev_name(pdev->dev.parent));

	ret = device_add(&pdev->dev);
	if (ret == 0)
		return ret;

 failed:
	while (--i >= 0) {
		struct resource *r = &pdev->resource[i];
		unsigned long type = resource_type(r);

		if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
			release_resource(r);
	}

	return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

該函數有三個重點,一個是設備如果沒有父設備,則把platform_bus設置為其父設備,一個是插入資源,還有一個是調用了device_add來添加設備。這裡說明device_register和platform_device_register有很多相似之處。

 

下面我們回到led_init函數繼續往下看驅動注冊函數platform_driver_register(&led_driver),還是先看參數led_driver:

 

static struct platform_driver led_driver = {
	.probe		= led_probe,
	.remove		= __devexit_p(led_remove),
	.driver		= {
		.name	= "led",
		.owner	= THIS_MODULE,
	},
};

led_driver是一個platform_driver類型的結構體,裡面主要是指向了一些操作函數。我們來看一下platform_driver結構體,其位於linux-3.4\include\linux\platform_device.h中:

 

 

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
};
該結構體主要包含了設備操作的一些函數,並且包含了device_driver結構體,用面向對象的思想說明platform_driver繼承了device_driver結構體。也即是device_driver結構體派生了platform_driver結構體,device_driver是platform_driver的基類。結構體device_driver位於linux-3.4\include\linux中:

 

 

struct device_driver {
	const char		*name;
	struct bus_type		*bus;

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */

	const struct of_device_id	*of_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};
該結構體包含了設備驅動的相關數據,比如設備驅動名稱,總線類型,擁有者,操作函數等等。

這裡最重要的倆變量name和owner,name的主要作用是把platform驅動和對應的platform設備連接起來,在platform_device結構體裡也存在name成員。只有這兩個name的名稱一樣才能成功注冊設備的驅動。owner的作用是說明驅動的所有者,通常初始化為THIS_MODULE。

 

我們接下來看platform設備驅動注冊函數platform_driver_register,該函數位於linux-3.4\drivers\base\platform.c中:

 

/**
 * platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 */
int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);

該函數首先聲明定義自己所掛載的總線類型,這一點很重要,因為platform_driver和platform_device都是掛載到platform_bus中,platform_driver和platform_device是通過platform_bus_type中注冊的回調函數platform_match來完成的,所以一定要先注冊設備再注冊驅動,否則無法匹配成功,驅動也就無法使用;然後給探測(probe),移除(remove),關閉(shutdown)函數指針賦值,最後使用driver_register函數進行設備的驅動注冊。我們來看一下driver_register函數,其在linux-3.4\drivers\base\driver.c中:

 

 

/**
 * driver_register - register driver with bus
 * @drv: driver to register
 *
 * We pass off most of the work to the bus_add_driver() call,
 * since most of the things we have to do deal with the bus
 * structures.
 */
int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	other = driver_find(drv->name, drv->bus);
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret)
		bus_remove_driver(drv);
	return ret;
}
EXPORT_SYMBOL_GPL(driver_register);

首先如果總線的方法和設備自己的方法同時存在,則打印警告信息。如果設備驅動已經注冊,則返回-EBUSY,否則使用bus_add_driver(drv)向總線添加驅動。

 

下面來看一下bus_add_driver函數,這個函數位於linux-3.4\drivers\base\bus.c中:

 

/**
 * bus_add_driver - Add a driver to the bus.
 * @drv: driver.
 */
int bus_add_driver(struct device_driver *drv)
{
	struct bus_type *bus;
	struct driver_private *priv;
	int error = 0;

	bus = bus_get(drv->bus);
	if (!bus)
		return -EINVAL;

	pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		error = -ENOMEM;
		goto out_put_bus;
	}
	klist_init(&priv->klist_devices, NULL, NULL);
	priv->driver = drv;
	drv->p = priv;
	priv->kobj.kset = bus->p->drivers_kset;
	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
				     "%s", drv->name);
	if (error)
		goto out_unregister;

	if (drv->bus->p->drivers_autoprobe) {
		error = driver_attach(drv);
		if (error)
			goto out_unregister;
	}
	klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
	module_add_driver(drv->owner, drv);

	error = driver_create_file(drv, &driver_attr_uevent);
	if (error) {
		printk(KERN_ERR "%s: uevent attr (%s) failed\n",
			__func__, drv->name);
	}
	error = driver_add_attrs(bus, drv);
	if (error) {
		/* How the hell do we get out of this pickle? Give up */
		printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
			__func__, drv->name);
	}

	if (!drv->suppress_bind_attrs) {
		error = add_bind_files(drv);
		if (error) {
			/* Ditto */
			printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
				__func__, drv->name);
		}
	}

	kobject_uevent(&priv->kobj, KOBJ_ADD);
	return 0;

out_unregister:
	kobject_put(&priv->kobj);
	kfree(drv->p);
	drv->p = NULL;
out_put_bus:
	bus_put(bus);
	return error;
}

 

從上面的紅線部分,如果驅動是自動probe的話,將調用driver_attach來綁定設備和驅動。函數driver_attach位於linux-3.4\drivers\base\dd.c中:

 

/**
 * driver_attach - try to bind driver to devices.
 * @drv: driver.
 *
 * Walk the list of devices that the bus has on it and try to
 * match the driver with each one.  If driver_probe_device()
 * returns 0 and the @dev->driver is set, we've found a
 * compatible pair.
 */
int driver_attach(struct device_driver *drv)
{
	return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
EXPORT_SYMBOL_GPL(driver_attach);

這裡可以看到,調用了bus_for_each_dev函數,該函數位於linux-3.4\drivers\based\bus.c中:

 

 

/**
 * bus_for_each_dev - device iterator.
 * @bus: bus type.
 * @start: device to start iterating from.
 * @data: data for the callback.
 * @fn: function to be called for each device.
 *
 * Iterate over @bus's list of devices, and call @fn for each,
 * passing it @data. If @start is not NULL, we use that device to
 * begin iterating from.
 *
 * We check the return of @fn each time. If it returns anything
 * other than 0, we break out and return that value.
 *
 * NOTE: The device that returns a non-zero value is not retained
 * in any way, nor is its refcount incremented. If the caller needs
 * to retain this data, it should do so, and increment the reference
 * count in the supplied callback.
 */
int bus_for_each_dev(struct bus_type *bus, struct device *start,
		     void *data, int (*fn)(struct device *, void *))
{
	struct klist_iter i;
	struct device *dev;
	int error = 0;

	if (!bus || !bus->p)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_devices, &i,
			     (start ? &start->p->knode_bus : NULL));
	while ((dev = next_device(&i)) && !error)
		error = fn(dev, data);
	klist_iter_exit(&i);
	return error;
}
EXPORT_SYMBOL_GPL(bus_for_each_dev);

這裡可以發現該函數是遍歷總線上的每一個設備,並調用了函數fn,,這裡的fn函數就是__driver__attach函數。我們來看一下__driver__attach函數,該函數位於linux-3.4\drivers\base\dd.c中:

 

 

static int __driver_attach(struct device *dev, void *data)
{
	struct device_driver *drv = data;

	/*
	 * Lock device and try to bind to it. We drop the error
	 * here and always return 0, because we need to keep trying
	 * to bind to devices and some drivers will return an error
	 * simply if it didn't support the device.
	 *
	 * driver_probe_device() will spit a warning if there
	 * is an error.
	 */

	if (!driver_match_device(drv, dev))
		return 0;

	if (dev->parent)	/* Needed for USB */
		device_lock(dev->parent);
	device_lock(dev);
	if (!dev->driver)
		driver_probe_device(drv, dev);
	device_unlock(dev);
	if (dev->parent)
		device_unlock(dev->parent);

	return 0;
}

 

這裡首先是driver 匹配device,然後調用了driver_probe_device函數,該函數位於linux-3.4\drivers\base\dd.c中:

 

/**
 * driver_probe_device - attempt to bind device & driver together
 * @drv: driver to bind a device to
 * @dev: device to try to bind to the driver
 *
 * This function returns -ENODEV if the device is not registered,
 * 1 if the device is bound successfully and 0 otherwise.
 *
 * This function must be called with @dev lock held.  When called for a
 * USB interface, @dev->parent lock must be held as well.
 */
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (!device_is_registered(dev))
		return -ENODEV;

	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	pm_runtime_get_noresume(dev);
	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);
	pm_runtime_put_sync(dev);

	return ret;
}

這裡首先檢測了設備是否被注冊,然後調用了really_probe函數,這個函數位於linux-3.4\drivers\base\dd.c中:

 

 

static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = 0;

	atomic_inc(&probe_count);
	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	WARN_ON(!list_empty(&dev->devres_head));

	dev->driver = drv;
	if (driver_sysfs_add(dev)) {
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}

	driver_bound(dev);
	ret = 1;
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;

probe_failed:
	devres_release_all(dev);
	driver_sysfs_remove(dev);
	dev->driver = NULL;

	if (ret == -EPROBE_DEFER) {
		/* Driver requested deferred probing */
		dev_info(dev, "Driver %s requests probe deferral\n", drv->name);
		driver_deferred_probe_add(dev);
	} else if (ret != -ENODEV && ret != -ENXIO) {
		/* driver matched but the probe failed */
		printk(KERN_WARNING
		       "%s: probe of %s failed with error %d\n",
		       drv->name, dev_name(dev), ret);
	} else {
		pr_debug("%s: probe of %s rejects match %d\n",
		       drv->name, dev_name(dev), ret);
	}
	/*
	 * Ignore errors returned by ->probe so that the next driver can try
	 * its luck.
	 */
	ret = 0;
done:
	atomic_dec(&probe_count);
	wake_up(&probe_waitqueue);
	return ret;
}

這裡調用了drv->probe(dev),而這個就是我們定義platform_driver結構體裡聲明的probe函數,在驅動led.c中就是函數led_probe函數:

 

 

//led_probe
static int __devinit led_probe(struct platform_device *pdev)
{
	int led_used;
	script_item_u	val;
	script_item_value_type_e  type;
	
	int err;
	
    printk("led_para!\n");
	type = script_get_item("led_para", "led_used", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
		printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);
		return -1;
	}
	led_used = val.val;
	printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);

	if(!led_used) {
		printk("%s led_used is not used in config,  led_used=%d\n", __FUNCTION__,led_used);
		return -1;
	}

	err = led_gpio();
	if (err)
		return -1;

	sema_init(&lock, 1);
	err = misc_register(&leds_dev);
	printk("======= cqa83 led initialized ================\n");
	
	return err;
}
到這裡,platform設備的設備和驅動初始化和綁定,探測就結束了,其實也意味著驅動已經設備和注冊成功了。

 

我覺得有一點還是要說一下,那就是設備和驅動的匹配,在驅動注冊是會回調總線注冊的匹配函數platform_match,該函數位於linux-3.4\drivers\base\platform.c中:

 

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
 *
 * Platform device IDs are assumed to be encoded like this:
 * "", where  is a short description of the type of
 * device, like "pci" or "floppy", and  is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "".  So, extract the  from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
 */
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

這裡的匹配就是通過字符串對比來進行的,這就是開始說的platform_device的name成員和platform_driver的name成員要一樣。

 

 

在這裡簡單總結一下,platform設備加載驅動的過程。首先有一個platform總線,這個總線呢會在系統初始化的時候對其進行初始化。在總線初始化完成之後,如果你想要往總線上掛載platform設備,那麼這個要分為兩部分,一是設備,二是驅動,也即是片platform_device和platform_driver,這兩個都是要掛載到platform總線上。但是掛載有一個順序,一定要先掛載設備,再掛載驅動,因為驅動是遍歷總線上所有的設備節點來匹配的。那麼這倆東西靠什麼來匹配呢?他們靠的是其結構體下的name成員變量,如果名字一樣才能匹配成功,這也就是為什麼要求platform_device和platform_driver的名字要一樣的原因了。

platform_device結構體提供的是資源,而platform_driver結構體提供的是操作,也就是驅動操作設備。platform_driver主要完成了設備的注冊和初始化,還有移除是的資源釋放等。在驅動led.c中很容易可以看出來,led_probe調用了led_gpio函數。到platform設備驅動加載完成,其實是在目錄/dev/platform下會出現你的設備。然而這並不能做什麼,但是Linux裡有一句話“一切皆文件”,設備也是文件。那麼這些完成之後,下面就是文件操作了。
一個問題是,我們的應用程序如何去使用驅動程序中的函數?比如打開設備,關閉設備,使用設備等等。這裡就是說對應用程序來說需要一個入口,一個可以通過驅動程序控制設備的入口。這裡就引入了一個重要的數據結構file_operrations,這個結構體包含了一組函數指針,這些指針所指向的函數就是用來操作設備的。

這個結構體位於linux-3.4\include\linux\fs.h中:

 

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};

那麼我們來對比一下驅動代碼led.c中的file_operations結構體:

 

 

//file_operations
static struct file_operations leds_ops = {
	.owner			= THIS_MODULE,
	.open			= led_open,
	.release		= led_close, 
	.unlocked_ioctl		= led_ioctl,
};

 

 

這裡只定義了open函數,release函數,unlocked_ioctl函數,並且定義了其擁有者是THIS_MODULE。這幾個函數的源代碼分別是:

 

//led_open
static int led_open(struct inode *inode, struct file *file)
{
	if (!down_trylock(&lock))
		return 0;
	else
		return -EBUSY;
}

 

 

led_open函數是開始時對設備加鎖,防止多應用程序訪問。


//led_close
static int  led_close(struct inode *inode, struct file *file)
{
	up(&lock);
	return 0;
}

 

led_close函數是設備使用完之後對設備進行解鎖方便其他程序使用。


//led_ioctl
static long  led_ioctl(struct file *filep, unsigned int cmd,
		unsigned long arg)
{
	unsigned int n;
	n = (unsigned int)arg;
	switch (cmd) {
		case LED_IOCTL_SET_ON:
			if (n < 1)
				return -EINVAL;
			if(led_val[n-1].gpio.gpio != -1) {
				__gpio_set_value(led_val[n-1].gpio.gpio, 1);
				printk("led%d on !\n", n);
			}
			break;

		case LED_IOCTL_SET_OFF:
		default:
			if (n < 1)
				return -EINVAL;
			if(led_val[n-1].gpio.gpio != -1) {
				__gpio_set_value(led_val[n-1].gpio.gpio, 0);
				printk("led%d off !\n", n);
			}
			break;
	}

	return 0;
}

led_ioctl裡面主要是對設備的控制,這個在前面已經分析過了,這裡不再進行分析。

 

到此呢,驅動也加載了,應用程序也有了入口,但是還有一個重要問題沒有說,那就是它是何時加載到驅動的呢?

我們知道Linux系統驅動加載一般是兩種方式,一個是編譯成ko模塊加載,一個是編譯進內核,系統啟動時自動加載。模塊加載有兩種方式,一個是手動加載,一個是使用腳本在系統啟動時加載,但是這兩種方式都會使用到mknod,insmod命令等。

那麼這裡是怎麼加載的呢?我們先查看其系統啟動的配置文件init.sun8i.rc,裡面關於led的啟動設置是這樣的

# led
    chmod 777 /dev/led
而不像lcd,lcd是這樣的:
# lcd 
    insmod /system/vendor/modules/disp.ko
    insmod /system/vendor/modules/hdmi.ko

led的配置並沒有使用到insmod名令,並且在Android設備中也找不到其相應的ko文件。所以說這個led驅動應該是靜態編譯進內核的。一般字符驅動設備靜態編譯進內核都會在相應的makefile中加入mknod命令來創建節點。那麼我們就去led.c對應的makefile中找一下:

 

然而其關於led的只有:

obj-$(CONFIG_SUNXI_LED)		+= led.o

並沒有mknod命令。那這到底是咋回事呢?

 

其實我們再回到led.c代碼中的led_probe函數:

 

//led_probe
static int __devinit led_probe(struct platform_device *pdev)
{
	int led_used;
	script_item_u	val;
	script_item_value_type_e  type;
	
	int err;
	
    printk("led_para!\n");
	type = script_get_item("led_para", "led_used", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
		printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);
		return -1;
	}
	led_used = val.val;
	printk("%s script_get_item \"led_para\" led_used = %d\n",
				__FUNCTION__, val.val);

	if(!led_used) {
		printk("%s led_used is not used in config,  led_used=%d\n", __FUNCTION__,led_used);
		return -1;
	}

	err = led_gpio();
	if (err)
		return -1;

	sema_init(&lock, 1);
	err = misc_register(&leds_dev);
	printk("======= cqa83 led initialized ================\n");
	
	return err;
}

來看紅色的部分,再看參數leds_dev:
//miscdevice
static struct miscdevice leds_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "led",
	.fops = &leds_ops,
};

到這裡是否明白了呢?

 

這個led設備驅動呢是一個雜項字符驅動,這就是我開始說的那三點中的一點。那這個有什麼關系呢?

misc_device是特殊字符設備。注冊驅動程序時采用misc_register函數注冊,此函數中會自動創建設備節點,即設備文件。無需mknod指令創建設備文件。

因為misc_register()會調用class_device_creat或者device_creat().

關於雜項字符設備網上有很多資料,大家可以查一下。我會在後面的博文中寫一篇來說雜項字符設備。

 

到這裡我們解決了沒有mknod的疑問,但是我們是如何配置才能把驅動編譯進內核呢?

可以這樣做,在內核源代碼目錄下執行make menuconfig命令,這會彈出一個對話界面。找到對應的驅動,然後用空格把前面的尖括號裡變為*號,然後保存退出。編譯系統就可以了。由於我的電腦不能截屏,就不能給大家上圖了,抱歉。不過網上有很多資料。我下面會給出連接。

到這裡這個驅動的分析基本上就完成了。但是還有倆問題需要另外來寫一下,一個是LinuxGPIO驅動模型,一個是雜項字符設備。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved