Linux 下的notifier chain 机制的注册和触发讲解

Linux 下的notifier chain 机制的注册和触发讲解

 notifier_chain_register

本文以tegra jack 为例。讲解了notifier_cain

 

1 概 述

内核许多子系统之间关联紧密,因此在一个子系统发生或者检测到的事件信息很可能对其他子系统来说也是有价值的。为了满足其他子系统对这些事件信息的需求,即在某个子系统内发生或检测到事件时,其他对此感兴趣的子系统也能知道事件的发生,内核提供了notification chain机制。
注意:notification chain适用于内核子系统之间的信息传递,不涉及用户态。
Notification chain使用发布-订阅模型(publish-and-subscribemodel):在事件发生时,检测或产生事件的子系统作为主动一方通过通知函数来告知作为被动一方的订阅者(对此事件感兴趣的子系统)。这里有个额外要求,订阅一方要提供callback函数以供发布方调用,当然,提供什么样的callback函数完全由订阅方决定。
订阅者必须知道其他子系统提供了哪些事件通知支持,以选择可以订阅的事件通知;当然,订阅者本身也是一个子系统,因此也具有信息发布功能,因此它也要清楚本系统内哪些事件对其他子系统是有价值的,即有哪些本系统内的事件发生时需要通知订阅者,但是子系统对谁订阅了事件通知以及为什么要订阅一无所知。
从某种意义上来说,notification chain实现了事件信息的共享

 

snd_soc_jack_notifier_register(tegra_wired_jack,&wired_switch_nb);

看下wired_switch_nb的值:

 

staticstructnotifier_block wired_switch_nb = {

       .notifier_call = wired_swith_notify,

};

 

 

再看下struct notifier_bloack这个结构体:

 

structnotifier_block {

       int (*notifier_call)(structnotifier_block *, unsignedlong, void*);

       structnotifier_block *next;

       intpriority;

};

 

这里我补充下:

notifier chains 一共有四种类型:

Atomic notifier chains: chains callback run ininterrupt/atomic context.callouts are not allowed to block

Blocking notifier chains: Chain callbacks run in processcontext.Callouts are allowed to block.

Raw notifier chains: There are no restrictions oncallbacks,registration, or unregistration.All locking and protectionmust beprovided by the caller.

SRCU notifier chains: A variant of blocking notifierchains, withthe same restrictions.

 

/*atomic_notifier_chain_register() may be called from anatomic context,

 * butblocking_notifier_chain_register() and srcu_notifier_chain_register()

 * must be calledfrom a process context.  Ditto for thecorresponding

 * _unregister()routines.

 *

 *atomic_notifier_chain_unregister(), blocking_notifier_chain_unregister(),

 * andsrcu_notifier_chain_unregister() _must not_ be called from within

 * the call chain.

 *

 * SRCU notifierchains are an alternative form of blocking notifier chains.

 * They use SRCU(Sleepable Read-Copy Update) instead of rw-semaphores for

 * protection ofthe chain links.  This means there is_very_ low overhead

 * insrcu_notifier_call_chain(): no cache bounces and no memory barriers.

 * Ascompensation, srcu_notifier_chain_unregister() is rather expensive.

 * SRCU notifierchains should be used when the chain will be called very

 * often butnotifier_blocks will seldom be removed. Also, SRCU notifier

 * chains areslightly more difficult to use because they require special

 * runtimeinitialization.

 */

 

上面是Linux kernel 的注释

 

这四种类型的notifier chain 的结构如下:

 

structnotifier_block {

       int (*notifier_call)(structnotifier_block *, unsignedlong, void*);

       structnotifier_block *next;

       intpriority;

};

 

structatomic_notifier_head {

       spinlock_tlock;

       structnotifier_block *head;

};

 

structblocking_notifier_head {

       structrw_semaphorerwsem;

       structnotifier_block *head;

};

 

structraw_notifier_head {

       structnotifier_block *head;

};

 

structsrcu_notifier_head {

       structmutexmutex;

       structsrcu_structsrcu;

       structnotifier_block *head;

};

 

下面我们trace 下如何注册一个notifier chain

/**

 *snd_soc_jack_notifier_register - Register a notifier for jack status

 *

 * @jack:  ASoC jack

 * @nb:    Notifier block to register

 *

 * Register fornotification of the current status of the jack. Note

 * that it isnot possible to report additional jack events in the

 * callback fromthe notifier, this is intended to support

 * applicationssuch as enabling electrical detection only when a

 * mechanicaldetection event has occurred.

 */

void snd_soc_jack_notifier_register(struct snd_soc_jack *jack,

                               struct notifier_block *nb)

{

       blocking_notifier_chain_register(&jack->notifier, nb);

}

EXPORT_SYMBOL_GPL(snd_soc_jack_notifier_register);

 

continue trace code:

 

 

/**

 *     blocking_notifier_chain_register - Addnotifier to a blocking notifier chain

 *     @nh: Pointer to head of the blockingnotifier chain

 *     @n: New entry in notifier chain

 *

 *     Adds a notifier to a blocking notifierchain.

 *     Must be called in process context.

 *

 *     Currently always returns zero.

 */

int blocking_notifier_chain_register(struct blocking_notifier_head *nh,

              struct notifier_block *n)

{

       int ret;

 

       /*

        * This code gets used during boot-up, whentask switching is

        * not yet working and interrupts must remaindisabled.  At

        * such times we must not call down_write().

        */

       if (unlikely(system_state ==SYSTEM_BOOTING))

              returnnotifier_chain_register(&nh->head, n);

 

       down_write(&nh->rwsem);

       ret =notifier_chain_register(&nh->head, n);

       up_write(&nh->rwsem);

       return ret;

}

EXPORT_SYMBOL_GPL(blocking_notifier_chain_register);

 

上面的代码会调用notifier_chain_register

/*

 *     Notifier chain core routines.  The exported routines below

 *     are layered on top of these, withappropriate locking added.

 */

 

staticint 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;

}

上面是在根据我们的添加的新建的notifier chain 和添加到这条cahin里面的所有notifer chain 进行优先级的判断,进行排序

 

rcu_assign_pointer:

#define rcu_assign_pointer(p, v) \

       ({ \

              if (!__builtin_constant_p(v) || \

                  ((v) != NULL)) \

                     smp_wmb();\

              (p) =(v); \

       })

buitin_constant_p是gcc 的内建函数,用于判断v是否为常熟,如果为常数就返回TRUE.如果不是就返回FALSE

 

我们将这个chain添加到jack里面后,我们在出发事件的时候,会调用出发函数,然后调用这个chain里面的notifier_call函数

 

因为我们这里的是jack ,所以当插入耳机的时候,就会出发事件

这个jack 的driver这里就不多讲解了,当插入耳机会调用中断处理函数

 

staticstructplatform_driver tegra_wired_jack_driver = {

       .probe = tegra_wired_jack_probe,

       .remove =__devexit_p(tegra_wired_jack_remove),

       .driver = {

              .name= "tegra_wired_jack",

              .owner= THIS_MODULE,

       },

};

 

 

/* platform driver */

staticint tegra_wired_jack_probe(struct platform_device *pdev)

{

       int ret;

       int hp_det_n;

       struct tegra_wired_jack_conf *pdata;

 

       pdata = (struct tegra_wired_jack_conf *)pdev->dev.platform_data;

       if (!pdata || !pdata->hp_det_n) {

              pr_err("Please set up gpio pins forjack.\n");

              return -EBUSY;

       }

 

       hp_det_n =pdata->hp_det_n;

       hs_jack_gpios[HEAD_DET_GPIO].gpio= hp_det_n;

#ifdef CONFIG_I_LOVE_PBJ30      

       // PBJ30 not use

#else

       ret =snd_soc_jack_add_gpios(tegra_wired_jack,

                                ARRAY_SIZE(hs_jack_gpios),

                                hs_jack_gpios);

       if (ret) {

              pr_err("Could NOT set up gpio pinsfor jack.\n");

              snd_soc_jack_free_gpios(tegra_wired_jack,

                                  ARRAY_SIZE(hs_jack_gpios),

                                  hs_jack_gpios);

              return ret;

       }

#endif

       return 0;

}

看下snd_soc_jack_add_gpios

int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,

                     struct snd_soc_jack_gpio *gpios)

{

….......................

…...................

 

INIT_WORK(&gpios[i].work, gpio_work);

ret = request_irq(gpio_to_irq(gpios[i].gpio),gpio_handler,IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING,jack->card->dev->driver->name,&gpios[i]);

}

就会调用这个gpio_work 函数

/* gpio work */

staticvoid gpio_work(struct work_struct *work)

{

       struct snd_soc_jack_gpio *gpio;

 

       gpio =container_of(work, struct snd_soc_jack_gpio, work);

       snd_soc_jack_gpio_detect(gpio);

}

 

snd_soc_jack_gpio_detect:

snd_soc_jack_report->>>>

    

blocking_notifier_call_chain(&jack->notifier, status, NULL);

----->__blocking_notifier_call_chain

----->notifier_call_chain

----->掉哟后那个notifier_call处理函数

这里就是上报事件的全部过程

这个notifier_call_chain 函数会

staticint wired_swith_notify(struct notifier_block *self,

                           unsignedlong action, void* dev)

{

       int state = 0;

 

       switch (action) {

       caseSND_JACK_HEADSET:

              state= 1;

              break;

       caseSND_JACK_HEADPHONE:

              state= 2;

              break;

       default:

              state= 0;

       }

 

       switch_set_state(&wired_switch_dev,state);

 

       return NOTIFY_OK;

}

会设置一些状态给上层使用

上层通过/sys/下面的interface 的状态进行一些的操作

你可能感兴趣的:(linux,struct,null,action,callback,locking)