作爲一個驅動工程師,每每遇到問題,總是抓耳撓腮,查找許久。是否有一些本質的特性,能讓工作變得輕鬆? 如果有,那可能是對驅動本質的理解,對器件工作特性的熟悉,首先了解本質基於2.6.3內核的led驅動框架來分析,並記錄。
以下是瀏覽led驅動的記錄,首先按照驅動模塊理解的慣例,先看看init函數
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
leds_class->dev_attrs = led_class_attrs;
return 0;
}
函數 class_create() 創建一個新的類在"/sys"目錄下,類名叫 ”leds”,並返回這個新生成的類。然後在對該類的的電源管理函數、類的屬性做初始化。關於類的初始化,led 的定義是:
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
正如註釋所陳述的那樣,結構體 “device_attribute” 是用來導出設備屬性的
/* interface for exporting device attributes */
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
對於一切設備皆文件的linux來說,設備的屬性無非就是讀寫,所以,導出設備屬性的結構體有讀函數 [ssize_t (*show)(struct device *dev, struct_device_attribute *attr, chat *buf)] 和寫函數 [ssize_t (*store)(struct device *dev, struct_device_attribute *attr, chat *buf, size_t count)]。其中 dev,對應要被操作的dev, attr 對應要讀寫的屬性,buf對應需要設備做什麼,具體的功能還需要設備驅動開發者實現。比如,輸入1 或者on 對應led的亮,這裏,1/on 就是 buf 的內容,dev 是led, 亮是attr。
結構體 struct attribute 是 用戶的操作權限,由於linux是多文件系統(儘管嵌入式很多設備都是單用戶——root),所有這個屬性規定者不同用戶、用戶組對設備的訪問權限。
看完init,接着看看exit函數
static void __exit leds_exit(void)
{
class_destroy(leds_class);
}
即爲註銷已經註冊了的led類。
設備驅動接口函數:
(1)led 設備結構體
struct led_classdev {
const char *name;
int brightness;
int max_brightness;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16)
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);
/* Get LED brightness level */
enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
/* Activate hardware accelerated blink, delays are in
* miliseconds and if none is provided then a sensible default
* should be chosen. The call can adjust the timings if it can't
* match the values specified exactly. */
int (*blink_set)(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off);
struct device *dev;
struct list_head node; /* LED Device list */
const char *default_trigger; /* Trigger to use */
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
struct rw_semaphore trigger_lock;
struct led_trigger *trigger;
struct list_head trig_list;
void *trigger_data;
#endif
};
設備結構體體,是內核驅動開發者(開發總線、驅動架構者)開放給設備驅動工程師的設備接口,用以抽象出一個設備在內核中,或者用以連接設備驅動程序到內核驅動中的,從而使得應用開發者編寫應用程序時,能準確無誤的調用到驅動中相關的函數,從而操作硬件。比如 struct led_class中, 函數指針 “void (*brightness_set)(struct led_classdev *led_cdev, enum led_brightness brightness) ”是led驅動實現後,當用戶空間操作前文所述的 store(寫方法時),就會調用,調用關係如下:
static ssize_t led_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
char *after;
unsigned long state = simple_strtoul(buf, &after, 10);
size_t count = after - buf;
if (isspace(*after))
count++;
if (count == size) {
ret = count;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
}
return ret;
}
static inline void led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value > led_cdev->max_brightness)
value = led_cdev->max_brightness;
led_cdev->brightness = value;
if (!(led_cdev->flags & LED_SUSPENDED))
led_cdev->brightness_set(led_cdev, value);
}
關於 trigger 還沒有接觸過,猜想它的應用場景是諸如 當有設備接入,顯示狀態這一類的,比如網口的燈那種。
(2) 註冊設備
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
{
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev))
return PTR_ERR(led_cdev->dev);
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
up_write(&leds_list_lock);
if (!led_cdev->max_brightness)
led_cdev->max_brightness = LED_FULL;
led_update_brightness(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
printk(KERN_DEBUG "Registered led device: %s\n",
led_cdev->name);
return 0;
}
首先調用 device_create ()創建設備、註冊在sys下並返回。
(3)led設備註銷
void led_classdev_unregister(struct led_classdev *led_cdev)
{
#ifdef CONFIG_LEDS_TRIGGERS
down_write(&led_cdev->trigger_lock);
if (led_cdev->trigger)
led_trigger_set(led_cdev, NULL);
up_write(&led_cdev->trigger_lock);
#endif
device_unregister(led_cdev->dev);
down_write(&leds_list_lock);
list_del(&led_cdev->node);
up_write(&leds_list_lock);
}
基本上來說,led的框架就這些,主要的是設備驅動的實現、讀寫方法的定義。深入分析就是內核機制的東西了。