device_init_wakeup(struct device *dev, bool enable) - Device wakeup initialization.
→ device_set_wakeup_capable(dev, true); - Set/reset device wakeup capability flag.
→ → wakeup_sysfs_add(dev);
→ → → sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group);
→ → → kobject_uevent(&dev->kobj, KOBJ_CHANGE);
→ device_wakeup_enable(dev);
→ → wakeup_source_register(dev, dev_name(dev));
→ → → wakeup_source_create(name); - Create a struct wakeup_source object.
→ → → wakeup_source_sysfs_add(dev, ws); - Add wakeup_source attributes to sysfs.
→ → → → wakeup_source_device_create(parent, ws);
→ → → → → dev_set_name(dev, "wakeup%d", ws->id);
→ → → wakeup_source_add(ws); - Add given object to the list of wakeup sources.
→ → device_wakeup_attach(dev, ws);- Attach a wakeup source object to a device object.
device结构体,里面有一个成员struct dev_pm_info power,函数device_init_wakeup围绕can_wakeup和wakeup_source这两个概念进行一系列的逻辑处理。
can_wakeup,标识本设备是否具有唤醒能力。只有具备唤醒能力的设备,才会在sysfs中有一个power目录,用于提供所有的wakeup信息,这些信息是以struct wakeup_source的形式组织起来的。
struct dev_pm_info {
:
unsigned int can_wakeup:1;
:
#ifdef CONFIG_PM_SLEEP
:
struct wakeup_source *wakeup;
:
#else
unsigned int should_wakeup:1;
#endif
:
};
/**
* struct wakeup_source - Representation of wakeup sources
*
* @name: Name of the wakeup source
* @id: Wakeup source id
* @entry: Wakeup source list entry
* @lock: Wakeup source lock
* @wakeirq: Optional device specific wakeirq
* @timer: Wakeup timer list
* @timer_expires: Wakeup timer expiration
* @total_time: Total time this wakeup source has been active.
* @max_time: Maximum time this wakeup source has been continuously active.
* @last_time: Monotonic clock when the wakeup source's was touched last time.
* @prevent_sleep_time: Total time this source has been preventing autosleep.
* @event_count: Number of signaled wakeup events.
* @active_count: Number of times the wakeup source was activated.
* @relax_count: Number of times the wakeup source was deactivated.
* @expire_count: Number of times the wakeup source's timeout has expired.
* @wakeup_count: Number of times the wakeup source might abort suspend.
* @dev: Struct device for sysfs statistics about the wakeup source.
* @active: Status of the wakeup source.
* @autosleep_enabled: Autosleep is active, so update @prevent_sleep_time.
*/
struct wakeup_source { //代表了一个具有唤醒能力的设备,也称该设备为一个wakeup source。
const char *name;
int id;
struct list_head entry;
spinlock_t lock;
struct wake_irq *wakeirq;
struct timer_list timer;
unsigned long timer_expires;
ktime_t total_time;
ktime_t max_time;
ktime_t last_time;
ktime_t start_prevent_time;
ktime_t prevent_sleep_time;
unsigned long event_count;
unsigned long active_count;
unsigned long relax_count;
unsigned long expire_count;
unsigned long wakeup_count;
struct device *dev;
bool active:1;
bool autosleep_enabled:1;
};
struct device {
struct kobject kobj;
struct device *parent;
:
struct dev_pm_info power;
:
}
/**
* device_init_wakeup - Device wakeup initialization.
* @dev: Device to handle.
* @enable: Whether or not to enable @dev as a wakeup device.
*
* By default, most devices should leave wakeup disabled. The exceptions are
* devices that everyone expects to be wakeup sources: keyboards, power buttons,
* possibly network interfaces, etc. Also, devices that don't generate their
* own wakeup requests but merely forward requests from one bus to another
* (like PCI bridges) should have wakeup enabled by default.
*/
static inline int device_init_wakeup(struct device *dev, bool enable)
{
if (enable) {
device_set_wakeup_capable(dev, true);
return device_wakeup_enable(dev);
} else {
device_wakeup_disable(dev);
device_set_wakeup_capable(dev, false);
return 0;
}
}
/**
* device_set_wakeup_capable - Set/reset device wakeup capability flag.
* @dev: Device to handle.
* @capable: Whether or not @dev is capable of waking up the system from sleep.
*
* If @capable is set, set the @dev's power.can_wakeup flag and add its
* wakeup-related attributes to sysfs. Otherwise, unset the @dev's
* power.can_wakeup flag and remove its wakeup-related attributes from sysfs.
*
* This function may sleep and it can't be called from any context where
* sleeping is not allowed.
*/
void device_set_wakeup_capable(struct device *dev, bool capable)
{
if (!!dev->power.can_wakeup == !!capable)
return;
dev->power.can_wakeup = capable;
if (device_is_registered(dev) && !list_empty(&dev->power.entry)) {
if (capable) {
int ret = wakeup_sysfs_add(dev);
if (ret)
dev_info(dev, "Wakeup sysfs attributes not added\n");
} else {
wakeup_sysfs_remove(dev);
}
}
}
/**
* device_wakeup_enable - Enable given device to be a wakeup source.
* @dev: Device to handle.
*
* Create a wakeup source object, register it and attach it to @dev.
*/
int device_wakeup_enable(struct device *dev)
{
struct wakeup_source *ws;
int ret;
if (!dev || !dev->power.can_wakeup)
return -EINVAL;
if (pm_suspend_target_state != PM_SUSPEND_ON)
dev_dbg(dev, "Suspicious %s() during system transition!\n", __func__);
ws = wakeup_source_register(dev, dev_name(dev));
if (!ws)
return -ENOMEM;
ret = device_wakeup_attach(dev, ws);
if (ret)
wakeup_source_unregister(ws);
return ret;
}
/**
* wakeup_source_register - Create wakeup source and add it to the list.
* @dev: Device this wakeup source is associated with (or NULL if virtual).
* @name: Name of the wakeup source to register.
*/
struct wakeup_source *wakeup_source_register(struct device *dev,
const char *name)
{
struct wakeup_source *ws;
int ret;
ws = wakeup_source_create(name);
if (ws) {
if (!dev || device_is_registered(dev)) {
ret = wakeup_source_sysfs_add(dev, ws);
if (ret) {
wakeup_source_free(ws);
return NULL;
}
}
wakeup_source_add(ws);
}
return ws;
}
/**
* wakeup_source_create - Create a struct wakeup_source object.
* @name: Name of the new wakeup source.
*/
struct wakeup_source *wakeup_source_create(const char *name)
{
struct wakeup_source *ws;
const char *ws_name;
int id;
ws = kzalloc(sizeof(*ws), GFP_KERNEL);
if (!ws)
goto err_ws;
ws_name = kstrdup_const(name, GFP_KERNEL);
if (!ws_name)
goto err_name;
ws->name = ws_name;
id = ida_alloc(&wakeup_ida, GFP_KERNEL);
if (id < 0)
goto err_id;
ws->id = id;
return ws;
err_id:
kfree_const(ws->name);
err_name:
kfree(ws);
err_ws:
return NULL;
}
/**
* wakeup_source_sysfs_add - Add wakeup_source attributes to sysfs.
* @parent: Device given wakeup source is associated with (or NULL if virtual).
* @ws: Wakeup source to be added in sysfs.
*/
int wakeup_source_sysfs_add(struct device *parent, struct wakeup_source *ws)
{
struct device *dev;
dev = wakeup_source_device_create(parent, ws);
if (IS_ERR(dev))
return PTR_ERR(dev);
ws->dev = dev;
return 0;
}
static struct device *wakeup_source_device_create(struct device *parent,
struct wakeup_source *ws)
{
struct device *dev = NULL;
int retval;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
device_initialize(dev);
dev->devt = MKDEV(0, 0);
dev->class = wakeup_class;
dev->parent = parent;
dev->groups = wakeup_source_groups;
dev->release = device_create_release;
dev_set_drvdata(dev, ws);
device_set_pm_not_required(dev);
retval = dev_set_name(dev, "wakeup%d", ws->id);
if (retval)
goto error;
retval = device_add(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
int wakeup_sysfs_add(struct device *dev)
{
int ret = sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group);
if (!ret)
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
return ret;
}
/**
* wakeup_source_add - Add given object to the list of wakeup sources.
* @ws: Wakeup source object to add to the list.
*/
void wakeup_source_add(struct wakeup_source *ws)
{
unsigned long flags;
if (WARN_ON(!ws))
return;
spin_lock_init(&ws->lock);
timer_setup(&ws->timer, pm_wakeup_timer_fn, 0);
ws->active = false;
raw_spin_lock_irqsave(&events_lock, flags);
list_add_rcu(&ws->entry, &wakeup_sources);
raw_spin_unlock_irqrestore(&events_lock, flags);
}
/**
* device_wakeup_attach - Attach a wakeup source object to a device object.
* @dev: Device to handle.
* @ws: Wakeup source object to attach to @dev.
*
* This causes @dev to be treated as a wakeup device.
*/
static int device_wakeup_attach(struct device *dev, struct wakeup_source *ws)
{
spin_lock_irq(&dev->power.lock);
if (dev->power.wakeup) {
spin_unlock_irq(&dev->power.lock);
return -EEXIST;
}
dev->power.wakeup = ws;
if (dev->power.wakeirq)
device_wakeup_attach_irq(dev, dev->power.wakeirq);
spin_unlock_irq(&dev->power.lock);
return 0;
}