162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Device wakeirq helper functions */ 362306a36Sopenharmony_ci#include <linux/device.h> 462306a36Sopenharmony_ci#include <linux/interrupt.h> 562306a36Sopenharmony_ci#include <linux/irq.h> 662306a36Sopenharmony_ci#include <linux/slab.h> 762306a36Sopenharmony_ci#include <linux/pm_runtime.h> 862306a36Sopenharmony_ci#include <linux/pm_wakeirq.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "power.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci/** 1362306a36Sopenharmony_ci * dev_pm_attach_wake_irq - Attach device interrupt as a wake IRQ 1462306a36Sopenharmony_ci * @dev: Device entry 1562306a36Sopenharmony_ci * @wirq: Wake irq specific data 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Internal function to attach a dedicated wake-up interrupt as a wake IRQ. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_cistatic int dev_pm_attach_wake_irq(struct device *dev, struct wake_irq *wirq) 2062306a36Sopenharmony_ci{ 2162306a36Sopenharmony_ci unsigned long flags; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci if (!dev || !wirq) 2462306a36Sopenharmony_ci return -EINVAL; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci spin_lock_irqsave(&dev->power.lock, flags); 2762306a36Sopenharmony_ci if (dev_WARN_ONCE(dev, dev->power.wakeirq, 2862306a36Sopenharmony_ci "wake irq already initialized\n")) { 2962306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->power.lock, flags); 3062306a36Sopenharmony_ci return -EEXIST; 3162306a36Sopenharmony_ci } 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci dev->power.wakeirq = wirq; 3462306a36Sopenharmony_ci device_wakeup_attach_irq(dev, wirq); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->power.lock, flags); 3762306a36Sopenharmony_ci return 0; 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/** 4162306a36Sopenharmony_ci * dev_pm_set_wake_irq - Attach device IO interrupt as wake IRQ 4262306a36Sopenharmony_ci * @dev: Device entry 4362306a36Sopenharmony_ci * @irq: Device IO interrupt 4462306a36Sopenharmony_ci * 4562306a36Sopenharmony_ci * Attach a device IO interrupt as a wake IRQ. The wake IRQ gets 4662306a36Sopenharmony_ci * automatically configured for wake-up from suspend based 4762306a36Sopenharmony_ci * on the device specific sysfs wakeup entry. Typically called 4862306a36Sopenharmony_ci * during driver probe after calling device_init_wakeup(). 4962306a36Sopenharmony_ci */ 5062306a36Sopenharmony_ciint dev_pm_set_wake_irq(struct device *dev, int irq) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci struct wake_irq *wirq; 5362306a36Sopenharmony_ci int err; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (irq < 0) 5662306a36Sopenharmony_ci return -EINVAL; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci wirq = kzalloc(sizeof(*wirq), GFP_KERNEL); 5962306a36Sopenharmony_ci if (!wirq) 6062306a36Sopenharmony_ci return -ENOMEM; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci wirq->dev = dev; 6362306a36Sopenharmony_ci wirq->irq = irq; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci err = dev_pm_attach_wake_irq(dev, wirq); 6662306a36Sopenharmony_ci if (err) 6762306a36Sopenharmony_ci kfree(wirq); 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci return err; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dev_pm_set_wake_irq); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci/** 7462306a36Sopenharmony_ci * dev_pm_clear_wake_irq - Detach a device IO interrupt wake IRQ 7562306a36Sopenharmony_ci * @dev: Device entry 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * Detach a device wake IRQ and free resources. 7862306a36Sopenharmony_ci * 7962306a36Sopenharmony_ci * Note that it's OK for drivers to call this without calling 8062306a36Sopenharmony_ci * dev_pm_set_wake_irq() as all the driver instances may not have 8162306a36Sopenharmony_ci * a wake IRQ configured. This avoid adding wake IRQ specific 8262306a36Sopenharmony_ci * checks into the drivers. 8362306a36Sopenharmony_ci */ 8462306a36Sopenharmony_civoid dev_pm_clear_wake_irq(struct device *dev) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci struct wake_irq *wirq = dev->power.wakeirq; 8762306a36Sopenharmony_ci unsigned long flags; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (!wirq) 9062306a36Sopenharmony_ci return; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci spin_lock_irqsave(&dev->power.lock, flags); 9362306a36Sopenharmony_ci device_wakeup_detach_irq(dev); 9462306a36Sopenharmony_ci dev->power.wakeirq = NULL; 9562306a36Sopenharmony_ci spin_unlock_irqrestore(&dev->power.lock, flags); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (wirq->status & WAKE_IRQ_DEDICATED_ALLOCATED) { 9862306a36Sopenharmony_ci free_irq(wirq->irq, wirq); 9962306a36Sopenharmony_ci wirq->status &= ~WAKE_IRQ_DEDICATED_MASK; 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci kfree(wirq->name); 10262306a36Sopenharmony_ci kfree(wirq); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dev_pm_clear_wake_irq); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/** 10762306a36Sopenharmony_ci * handle_threaded_wake_irq - Handler for dedicated wake-up interrupts 10862306a36Sopenharmony_ci * @irq: Device specific dedicated wake-up interrupt 10962306a36Sopenharmony_ci * @_wirq: Wake IRQ data 11062306a36Sopenharmony_ci * 11162306a36Sopenharmony_ci * Some devices have a separate wake-up interrupt in addition to the 11262306a36Sopenharmony_ci * device IO interrupt. The wake-up interrupt signals that a device 11362306a36Sopenharmony_ci * should be woken up from it's idle state. This handler uses device 11462306a36Sopenharmony_ci * specific pm_runtime functions to wake the device, and then it's 11562306a36Sopenharmony_ci * up to the device to do whatever it needs to. Note that as the 11662306a36Sopenharmony_ci * device may need to restore context and start up regulators, we 11762306a36Sopenharmony_ci * use a threaded IRQ. 11862306a36Sopenharmony_ci * 11962306a36Sopenharmony_ci * Also note that we are not resending the lost device interrupts. 12062306a36Sopenharmony_ci * We assume that the wake-up interrupt just needs to wake-up the 12162306a36Sopenharmony_ci * device, and then device's pm_runtime_resume() can deal with the 12262306a36Sopenharmony_ci * situation. 12362306a36Sopenharmony_ci */ 12462306a36Sopenharmony_cistatic irqreturn_t handle_threaded_wake_irq(int irq, void *_wirq) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct wake_irq *wirq = _wirq; 12762306a36Sopenharmony_ci int res; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci /* Maybe abort suspend? */ 13062306a36Sopenharmony_ci if (irqd_is_wakeup_set(irq_get_irq_data(irq))) { 13162306a36Sopenharmony_ci pm_wakeup_event(wirq->dev, 0); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci return IRQ_HANDLED; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* We don't want RPM_ASYNC or RPM_NOWAIT here */ 13762306a36Sopenharmony_ci res = pm_runtime_resume(wirq->dev); 13862306a36Sopenharmony_ci if (res < 0) 13962306a36Sopenharmony_ci dev_warn(wirq->dev, 14062306a36Sopenharmony_ci "wake IRQ with no resume: %i\n", res); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci return IRQ_HANDLED; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic int __dev_pm_set_dedicated_wake_irq(struct device *dev, int irq, unsigned int flag) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct wake_irq *wirq; 14862306a36Sopenharmony_ci int err; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (irq < 0) 15162306a36Sopenharmony_ci return -EINVAL; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci wirq = kzalloc(sizeof(*wirq), GFP_KERNEL); 15462306a36Sopenharmony_ci if (!wirq) 15562306a36Sopenharmony_ci return -ENOMEM; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci wirq->name = kasprintf(GFP_KERNEL, "%s:wakeup", dev_name(dev)); 15862306a36Sopenharmony_ci if (!wirq->name) { 15962306a36Sopenharmony_ci err = -ENOMEM; 16062306a36Sopenharmony_ci goto err_free; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci wirq->dev = dev; 16462306a36Sopenharmony_ci wirq->irq = irq; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* Prevent deferred spurious wakeirqs with disable_irq_nosync() */ 16762306a36Sopenharmony_ci irq_set_status_flags(irq, IRQ_DISABLE_UNLAZY); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci /* 17062306a36Sopenharmony_ci * Consumer device may need to power up and restore state 17162306a36Sopenharmony_ci * so we use a threaded irq. 17262306a36Sopenharmony_ci */ 17362306a36Sopenharmony_ci err = request_threaded_irq(irq, NULL, handle_threaded_wake_irq, 17462306a36Sopenharmony_ci IRQF_ONESHOT | IRQF_NO_AUTOEN, 17562306a36Sopenharmony_ci wirq->name, wirq); 17662306a36Sopenharmony_ci if (err) 17762306a36Sopenharmony_ci goto err_free_name; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci err = dev_pm_attach_wake_irq(dev, wirq); 18062306a36Sopenharmony_ci if (err) 18162306a36Sopenharmony_ci goto err_free_irq; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci wirq->status = WAKE_IRQ_DEDICATED_ALLOCATED | flag; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci return err; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cierr_free_irq: 18862306a36Sopenharmony_ci free_irq(irq, wirq); 18962306a36Sopenharmony_cierr_free_name: 19062306a36Sopenharmony_ci kfree(wirq->name); 19162306a36Sopenharmony_cierr_free: 19262306a36Sopenharmony_ci kfree(wirq); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci return err; 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci/** 19862306a36Sopenharmony_ci * dev_pm_set_dedicated_wake_irq - Request a dedicated wake-up interrupt 19962306a36Sopenharmony_ci * @dev: Device entry 20062306a36Sopenharmony_ci * @irq: Device wake-up interrupt 20162306a36Sopenharmony_ci * 20262306a36Sopenharmony_ci * Unless your hardware has separate wake-up interrupts in addition 20362306a36Sopenharmony_ci * to the device IO interrupts, you don't need this. 20462306a36Sopenharmony_ci * 20562306a36Sopenharmony_ci * Sets up a threaded interrupt handler for a device that has 20662306a36Sopenharmony_ci * a dedicated wake-up interrupt in addition to the device IO 20762306a36Sopenharmony_ci * interrupt. 20862306a36Sopenharmony_ci */ 20962306a36Sopenharmony_ciint dev_pm_set_dedicated_wake_irq(struct device *dev, int irq) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci return __dev_pm_set_dedicated_wake_irq(dev, irq, 0); 21262306a36Sopenharmony_ci} 21362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dev_pm_set_dedicated_wake_irq); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci/** 21662306a36Sopenharmony_ci * dev_pm_set_dedicated_wake_irq_reverse - Request a dedicated wake-up interrupt 21762306a36Sopenharmony_ci * with reverse enable ordering 21862306a36Sopenharmony_ci * @dev: Device entry 21962306a36Sopenharmony_ci * @irq: Device wake-up interrupt 22062306a36Sopenharmony_ci * 22162306a36Sopenharmony_ci * Unless your hardware has separate wake-up interrupts in addition 22262306a36Sopenharmony_ci * to the device IO interrupts, you don't need this. 22362306a36Sopenharmony_ci * 22462306a36Sopenharmony_ci * Sets up a threaded interrupt handler for a device that has a dedicated 22562306a36Sopenharmony_ci * wake-up interrupt in addition to the device IO interrupt. It sets 22662306a36Sopenharmony_ci * the status of WAKE_IRQ_DEDICATED_REVERSE to tell rpm_suspend() 22762306a36Sopenharmony_ci * to enable dedicated wake-up interrupt after running the runtime suspend 22862306a36Sopenharmony_ci * callback for @dev. 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ciint dev_pm_set_dedicated_wake_irq_reverse(struct device *dev, int irq) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci return __dev_pm_set_dedicated_wake_irq(dev, irq, WAKE_IRQ_DEDICATED_REVERSE); 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dev_pm_set_dedicated_wake_irq_reverse); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci/** 23762306a36Sopenharmony_ci * dev_pm_enable_wake_irq_check - Checks and enables wake-up interrupt 23862306a36Sopenharmony_ci * @dev: Device 23962306a36Sopenharmony_ci * @can_change_status: Can change wake-up interrupt status 24062306a36Sopenharmony_ci * 24162306a36Sopenharmony_ci * Enables wakeirq conditionally. We need to enable wake-up interrupt 24262306a36Sopenharmony_ci * lazily on the first rpm_suspend(). This is needed as the consumer device 24362306a36Sopenharmony_ci * starts in RPM_SUSPENDED state, and the first pm_runtime_get() would 24462306a36Sopenharmony_ci * otherwise try to disable already disabled wakeirq. The wake-up interrupt 24562306a36Sopenharmony_ci * starts disabled with IRQ_NOAUTOEN set. 24662306a36Sopenharmony_ci * 24762306a36Sopenharmony_ci * Should be only called from rpm_suspend() and rpm_resume() path. 24862306a36Sopenharmony_ci * Caller must hold &dev->power.lock to change wirq->status 24962306a36Sopenharmony_ci */ 25062306a36Sopenharmony_civoid dev_pm_enable_wake_irq_check(struct device *dev, 25162306a36Sopenharmony_ci bool can_change_status) 25262306a36Sopenharmony_ci{ 25362306a36Sopenharmony_ci struct wake_irq *wirq = dev->power.wakeirq; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (!wirq || !(wirq->status & WAKE_IRQ_DEDICATED_MASK)) 25662306a36Sopenharmony_ci return; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (likely(wirq->status & WAKE_IRQ_DEDICATED_MANAGED)) { 25962306a36Sopenharmony_ci goto enable; 26062306a36Sopenharmony_ci } else if (can_change_status) { 26162306a36Sopenharmony_ci wirq->status |= WAKE_IRQ_DEDICATED_MANAGED; 26262306a36Sopenharmony_ci goto enable; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci return; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_cienable: 26862306a36Sopenharmony_ci if (!can_change_status || !(wirq->status & WAKE_IRQ_DEDICATED_REVERSE)) { 26962306a36Sopenharmony_ci enable_irq(wirq->irq); 27062306a36Sopenharmony_ci wirq->status |= WAKE_IRQ_DEDICATED_ENABLED; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci} 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci/** 27562306a36Sopenharmony_ci * dev_pm_disable_wake_irq_check - Checks and disables wake-up interrupt 27662306a36Sopenharmony_ci * @dev: Device 27762306a36Sopenharmony_ci * @cond_disable: if set, also check WAKE_IRQ_DEDICATED_REVERSE 27862306a36Sopenharmony_ci * 27962306a36Sopenharmony_ci * Disables wake-up interrupt conditionally based on status. 28062306a36Sopenharmony_ci * Should be only called from rpm_suspend() and rpm_resume() path. 28162306a36Sopenharmony_ci */ 28262306a36Sopenharmony_civoid dev_pm_disable_wake_irq_check(struct device *dev, bool cond_disable) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci struct wake_irq *wirq = dev->power.wakeirq; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (!wirq || !(wirq->status & WAKE_IRQ_DEDICATED_MASK)) 28762306a36Sopenharmony_ci return; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (cond_disable && (wirq->status & WAKE_IRQ_DEDICATED_REVERSE)) 29062306a36Sopenharmony_ci return; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (wirq->status & WAKE_IRQ_DEDICATED_MANAGED) { 29362306a36Sopenharmony_ci wirq->status &= ~WAKE_IRQ_DEDICATED_ENABLED; 29462306a36Sopenharmony_ci disable_irq_nosync(wirq->irq); 29562306a36Sopenharmony_ci } 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci/** 29962306a36Sopenharmony_ci * dev_pm_enable_wake_irq_complete - enable wake IRQ not enabled before 30062306a36Sopenharmony_ci * @dev: Device using the wake IRQ 30162306a36Sopenharmony_ci * 30262306a36Sopenharmony_ci * Enable wake IRQ conditionally based on status, mainly used if want to 30362306a36Sopenharmony_ci * enable wake IRQ after running ->runtime_suspend() which depends on 30462306a36Sopenharmony_ci * WAKE_IRQ_DEDICATED_REVERSE. 30562306a36Sopenharmony_ci * 30662306a36Sopenharmony_ci * Should be only called from rpm_suspend() path. 30762306a36Sopenharmony_ci */ 30862306a36Sopenharmony_civoid dev_pm_enable_wake_irq_complete(struct device *dev) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci struct wake_irq *wirq = dev->power.wakeirq; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci if (!wirq || !(wirq->status & WAKE_IRQ_DEDICATED_MASK)) 31362306a36Sopenharmony_ci return; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci if (wirq->status & WAKE_IRQ_DEDICATED_MANAGED && 31662306a36Sopenharmony_ci wirq->status & WAKE_IRQ_DEDICATED_REVERSE) { 31762306a36Sopenharmony_ci enable_irq(wirq->irq); 31862306a36Sopenharmony_ci wirq->status |= WAKE_IRQ_DEDICATED_ENABLED; 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci/** 32362306a36Sopenharmony_ci * dev_pm_arm_wake_irq - Arm device wake-up 32462306a36Sopenharmony_ci * @wirq: Device wake-up interrupt 32562306a36Sopenharmony_ci * 32662306a36Sopenharmony_ci * Sets up the wake-up event conditionally based on the 32762306a36Sopenharmony_ci * device_may_wake(). 32862306a36Sopenharmony_ci */ 32962306a36Sopenharmony_civoid dev_pm_arm_wake_irq(struct wake_irq *wirq) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci if (!wirq) 33262306a36Sopenharmony_ci return; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (device_may_wakeup(wirq->dev)) { 33562306a36Sopenharmony_ci if (wirq->status & WAKE_IRQ_DEDICATED_ALLOCATED && 33662306a36Sopenharmony_ci !(wirq->status & WAKE_IRQ_DEDICATED_ENABLED)) 33762306a36Sopenharmony_ci enable_irq(wirq->irq); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci enable_irq_wake(wirq->irq); 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci} 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci/** 34462306a36Sopenharmony_ci * dev_pm_disarm_wake_irq - Disarm device wake-up 34562306a36Sopenharmony_ci * @wirq: Device wake-up interrupt 34662306a36Sopenharmony_ci * 34762306a36Sopenharmony_ci * Clears up the wake-up event conditionally based on the 34862306a36Sopenharmony_ci * device_may_wake(). 34962306a36Sopenharmony_ci */ 35062306a36Sopenharmony_civoid dev_pm_disarm_wake_irq(struct wake_irq *wirq) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci if (!wirq) 35362306a36Sopenharmony_ci return; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (device_may_wakeup(wirq->dev)) { 35662306a36Sopenharmony_ci disable_irq_wake(wirq->irq); 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci if (wirq->status & WAKE_IRQ_DEDICATED_ALLOCATED && 35962306a36Sopenharmony_ci !(wirq->status & WAKE_IRQ_DEDICATED_ENABLED)) 36062306a36Sopenharmony_ci disable_irq_nosync(wirq->irq); 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci} 363