notifier主要用于核心間的各個子產品的通信
- (通知源)子系統A進行定義初始化和回調函數的調用
-
(被通知)子系統B進行回調函數的注冊和登出
當A系統發生某種事件時,就調用通知鍊中的所有回調函數,B系統中注冊的回調函數就會得到執行。一旦執行回調函數,他會從連結清單頭依次執行每一個回調函數,那麼依次執行是依次性執行完,執行過程中任意時刻都可睡眠?這些需求也就産生了4種類型的notifier_chain。
struct notifier_block { /* chain的基本機關 */
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block __rcu *next;
int priority;
};
struct atomic_notifier_head {/* atmoic context; 執行(rcu_read_lock);回調函數執行不能阻塞;實時性高 */
spinlock_t lock;
struct notifier_block __rcu *head;
};
struct blocking_notifier_head { /* process context;執行(rw_semaphore) ;回調函數執行可以阻塞;實時性相對低*/
struct rw_semaphore rwsem;
struct notifier_block __rcu *head;
};
struct raw_notifier_head { /* 原始連結清單操作,注冊,執行過程無任何保護,完全有驅動人員控制 */
struct notifier_block __rcu *head;
};
struct srcu_notifier_head { /* process context;可阻塞通知鍊的變體(Sleepable Read-Copy-Update),回調函數執行可以阻塞 */
struct mutex mutex;
struct srcu_struct srcu;
struct notifier_block __rcu *head;
};
notifer_chain的api使用的四大基本步驟,定義初始化,注冊,調用和登出,而這些操作的基本機關是notifier_block,這個基本機關中包含了回調函數的notifier_call,後繼notifier_block指針,優先級priority(預設0,數字越大優先級越大,越靠近連結清單的頭節點,越優先得到執行)。
- 初始化
#include <linux/notifier.h>
#define ATOMIC_NOTIFIER_HEAD(name) \
struct atomic_notifier_head name = \
ATOMIC_NOTIFIER_INIT(name)
#define BLOCKING_NOTIFIER_HEAD(name) \
struct blocking_notifier_head name = \
BLOCKING_NOTIFIER_INIT(name)
#define RAW_NOTIFIER_HEAD(name) \
struct raw_notifier_head name = \
RAW_NOTIFIER_INIT(name)
/* srcu_notifier_heads must be initialized and cleaned up dynamically */
extern void srcu_init_notifier_head(struct srcu_notifier_head *nh);
#define srcu_cleanup_notifier_head(name) \
cleanup_srcu_struct(&(name)->srcu);
經過定義初始化後,連結清單的頭就形成了,注冊就是增加節點,執行就是周遊節點,唯一要說明的就是,srcu鍊需要動态的定義,其他三種不需要
int atomic_notifier_chain_register(struct atomic_notifier_head *nh, struct notifier_block *n);
int atomic_notifier_chain_unregister(struct atomic_notifier_head *nh, struct notifier_block *n)
int blocking_notifier_chain_register(struct blocking_notifier_head *nh, struct notifier_block *n);
/* 注冊的notifier_block不重複*/
int blocking_notifier_chain_cond_register(struct blocking_notifier_head *nh, struct notifier_block *n);
int blocking_notifier_chain_unregister(struct blocking_notifier_head *nh, struct notifier_block *n);
int raw_notifier_chain_register(struct raw_notifier_head *nh, struct notifier_block *n);
int raw_notifier_chain_unregister(struct raw_notifier_head *nh, struct notifier_block *n);
int srcu_notifier_chain_register(struct srcu_notifier_head *nh, struct notifier_block *n);
int srcu_notifier_chain_unregister(struct srcu_notifier_head *nh, struct notifier_block *n);
其中注冊和登出都調用了最基本的函數如下:
/*
* Notifier chain core routines. The exported routines below
* are layered on top of these, with appropriate locking added.
*/
static int notifier_chain_register(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
static int notifier_chain_cond_register(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n)
return 0;
if (n->priority > (*nl)->priority)
break;
nl = &((*nl)->next);
}
n->next = *nl;
rcu_assign_pointer(*nl, n);
return 0;
}
static int notifier_chain_unregister(struct notifier_block **nl,
struct notifier_block *n)
{
while ((*nl) != NULL) {
if ((*nl) == n) {
rcu_assign_pointer(*nl, n->next);
return 0;
}
nl = &((*nl)->next);
}
return -ENOENT;
}
調用:
當A系統未發生事件時,會調用下面的函數之一的方法來執行通知鍊中的所有回調函數,那麼所有注冊的地方都會得到執行,除前一個函數執行傳回值含有NOTIFY_STOP_MASK标志。其中參數val是一個整形,使用者自定義含義的傳入值,參數v是一個void*類型,使用者自定義任何類型含義,一般為平台結構體指針,使用的時候需要強制轉換。
/*調用了__atomic_notifier_call_chain,且nr_to_call=-1表示回調函數調用個數不限,nr_calls=NULL表示不關心已執行的回調函數個數*/
extern int atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v);
extern int __atomic_notifier_call_chain(struct atomic_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
extern int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v);
extern int __blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
extern int raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v);
extern int __raw_notifier_call_chain(struct raw_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
extern int srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v);
extern int __srcu_notifier_call_chain(struct srcu_notifier_head *nh, unsigned long val, void *v, int nr_to_call, int *nr_calls);
回調函數的傳回值:
#define NOTIFY_DONE 0x0000 /* Don't care回調函數不關心傳回值*/
#define NOTIFY_OK 0x0001 /* Suits me 回調函數調用順利完成*/
#define NOTIFY_STOP_MASK 0x8000 /* Don't call further 回調函數鍊禁止繼續調用的掩碼*/
#define NOTIFY_BAD (NOTIFY_STOP_MASK|0x0002)
/* Bad/Veto action 回調函數執行有錯*/
/*
* Clean way to return from the notifier and stop further calls.目前順利調用,禁止繼續調用
*/
#define NOTIFY_STOP (NOTIFY_OK|NOTIFY_STOP_MASK)
在notifier_call_chain函數中去依次執行每一個注冊的回調函數,并以前一個回調函數的傳回值為判斷依據,是否繼續調用,最後一個執行函數的傳回值作為notifier_call_chain的傳回值。目前标準API中,隻關注了NOTIFY_STOP_MASK,其他的在notifier_call_chain中未做處理。
典型用例:
在Linux中,液晶顯示其會提供一個fb_notify,當顯示器發生某種事件時,會調用notifier_call_chain,這樣注冊的回調函數得到執行,回調函數根據顯示的framebuffer狀态執行你預想的動作。
他選用的通知鍊類型是blocking_notifier_chain.
/*
* linux/drivers/video/fb_notify.c
*
* Copyright (C) 2006 Antonino Daplas <[email protected]>
*
* 2001 - Documented with DocBook
* - Brad Douglas <[email protected]>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/fb.h>
#include <linux/notifier.h>
#include <linux/export.h>
/*靜态定義并初始化通知鍊頭*/
static BLOCKING_NOTIFIER_HEAD(fb_notifier_list);
/** 注冊回調函數,加入一個回調函數節點
* fb_register_client - register a client notifier
* @nb: notifier block to callback on events
*/
int fb_register_client(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_register_client);
/** 登出回調函數,删除一個回調函數節點
* fb_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*/
int fb_unregister_client(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&fb_notifier_list, nb);
}
EXPORT_SYMBOL(fb_unregister_client);
/** 當源即framebuffer發生某種事件,調用該函數執行所有回調函數,通知其他子系統
* fb_notifier_call_chain - notify clients of fb_events
*
*/
int fb_notifier_call_chain(unsigned long val, void *v)
{
return blocking_notifier_call_chain(&fb_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(fb_notifier_call_chain);
假如framenbuffer為A子系統,觸屏ft5x06為B子系統,現想要做到觸屏伴随顯示屏息屏而休眠。亮屏而喚醒。
B子系統(觸屏)/被通知者,代碼如下:
kernel\drivers\input\touchscreen\ft5x06_ts.c
#if defined(CONFIG_FB)
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct fb_event *evdata = data;
int *blank;
struct ft5x06_ts_data *ft5x06_data =
container_of(self, struct ft5x06_ts_data, fb_notif);
/* 檢測是否是顯示器BLANK改變事件 */
if (evdata && evdata->data && event == FB_EVENT_BLANK &&
ft5x06_data && ft5x06_data->client) {
blank = evdata->data;
if (*blank == FB_BLANK_UNBLANK) /*是BLANK事件中的LCD亮屏事件,喚醒觸屏*/
ft5x06_ts_resume(&ft5x06_data->client->dev);
else if (*blank == FB_BLANK_POWERDOWN) /*是BLANK事件中的LCD滅屏事件,讓觸屏休眠 */
ft5x06_ts_suspend(&ft5x06_data->client->dev);
}
return 0;
}
#elif defined(CONFIG_HAS_EARLYSUSPEND)
//......
#endif
static int ft5x06_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
//......
#if defined(CONFIG_FB)
data->fb_notif.notifier_call = fb_notifier_callback;
/*注冊fb回調函數*/
err = fb_register_client(&data->fb_notif);
if (err)
dev_err(&client->dev, "Unable to register fb_notifier: %d\n",
err);
#endif
//......
}
static int __devexit ft5x06_ts_remove(struct i2c_client *client)
{
//......
#if defined(CONFIG_FB)
/*登出fb回調函數*/
if (fb_unregister_client(&data->fb_notif))
dev_err(&client->dev, "Error occurred while unregistering fb_notifier.\n");
#elif defined(CONFIG_HAS_EARLYSUSPEND)
unregister_early_suspend(&data->early_suspend);
#endif
//......
}
A子系統(framebuffer)/通知者,代碼如下:
int fb_blank(struct fb_info *info, int blank)
{
int ret = -EINVAL;
if (blank > FB_BLANK_POWERDOWN)
blank = FB_BLANK_POWERDOWN;
if (info->fbops->fb_blank) /*硬體執行亮屏還是滅屏的操作*/
ret = info->fbops->fb_blank(blank, info);
if (!ret) {
struct fb_event event;
event.info = info;
event.data = ␣
/*硬體BLANK操作成功後,調用所有的注冊回調函數,比如通知給觸屏*/
fb_notifier_call_chain(FB_EVENT_BLANK, &event);
}
return ret;
}
/*fbmem中的ioctl*/
static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
//.......
case FBIOBLANK: //由上層發下來的亮屏還是息屏的IO指令
if (!lock_fb_info(info))
return -ENODEV;
console_lock();
info->flags |= FBINFO_MISC_USEREVENT;
ret = fb_blank(info, arg);
info->flags &= ~FBINFO_MISC_USEREVENT;
console_unlock();
unlock_fb_info(info);
break;
//......
}
至于framebuffer中的notifie_call_chain的第二,第三個參數,詳見linux\fb.h,fbmem.c,fbcon.c。