@@ -21,14 +21,132 @@
#include "opal-poweroff-events.h"
-/* Poweroff timers */
+/* Kobject pointer for poweroff events platform device */
+static struct kobject *poweroff_dev_kobj;
+
+/* Poweroff event timer */
static struct timer_list poweroff_timer;
-static DEFINE_SPINLOCK(poweroff_timer_spinlock);
/* Converts OPAL event type into it's description. */
static const char *poweroff_events_map[POWEROFF_EVENTS]
= {"EPOW", "DPO"};
+/* Spinlock for poweroff events */
+unsigned long flags;
+static DEFINE_SPINLOCK(poweroff_event_spinlock);
+
+/* Poweroff event properties */
+enum props_power_supply {
+ POWER_SUPPLY_NORMAL,
+ POWER_SUPPLY_UPS,
+ POWER_SUPPLY_UPS_LOW,
+};
+
+enum props_thermal {
+ THERMAL_NORMAL,
+ THERMAL_HIGH_AMB_TEMP,
+ THERMAL_CRIT_AMB_TEMP,
+ THERMAL_HIGH_INT_TEMP,
+ THERMAL_CRIT_INT_TEMP,
+};
+
+/* Poweroff event property strings */
+static const char *poweroff_power_supply[] = {
+ [POWER_SUPPLY_NORMAL] = "Normal",
+ [POWER_SUPPLY_UPS] = "UPS",
+ [POWER_SUPPLY_UPS_LOW] = "UPS-battery-low",
+};
+static const char *poweroff_thermal[] = {
+ [THERMAL_NORMAL] = "Normal",
+ [THERMAL_HIGH_AMB_TEMP] = "High-ambient-temp",
+ [THERMAL_CRIT_AMB_TEMP] = "Critical-ambient-temp",
+ [THERMAL_HIGH_INT_TEMP] = "High-internal-temp",
+ [THERMAL_CRIT_INT_TEMP] = "Critical-internal-temp",
+};
+
+/* Global variable for poweroff event properties */
+static struct {
+ bool admin_shutdown;
+ enum props_power_supply power_supply;
+ enum props_thermal thermal;
+} poweroff_property = {
+ .admin_shutdown = false,
+ .power_supply = POWER_SUPPLY_NORMAL,
+ .thermal = THERMAL_NORMAL,
+};
+
+/* Poweroff events device attribute functions */
+static ssize_t thermal_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int index;
+
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ index = poweroff_property.thermal;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ return sprintf(buf, "%s\n", poweroff_thermal[index]);
+}
+
+static ssize_t power_supply_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int index;
+
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ index = poweroff_property.power_supply;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ return sprintf(buf, "%s\n", poweroff_power_supply[index]);
+}
+
+static ssize_t admin_shutdown_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int admin_shutdown;
+
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ admin_shutdown = poweroff_property.admin_shutdown;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ return sprintf(buf, "%s\n", admin_shutdown ? "Yes" : "No");
+}
+
+static ssize_t timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned long timeout;
+
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ if (time_after(poweroff_timer.expires, jiffies))
+ timeout = (poweroff_timer.expires - jiffies) / HZ;
+ else
+ timeout = 0;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ return sprintf(buf, "%lu\n", timeout);
+}
+
+/* Poweroff events device attributes */
+static struct device_attribute poweroff_attrs[] = {
+ __ATTR_RO(thermal), /* Thermal state of the machine */
+ __ATTR_RO(power_supply), /* Power supply state of the machine */
+ __ATTR_RO(admin_shutdown), /* Admin initated power off event */
+ __ATTR_RO(timeout), /* Timeout for system shutdown */
+};
+
+/* Workitem and callback function to notify udev */
+static void notify_udev(struct work_struct *work)
+{
+ int ret;
+
+ /* Trigger uevent to notify udev */
+ ret = kobject_uevent(poweroff_dev_kobj, KOBJ_CHANGE);
+ if (ret)
+ pr_info("Unable to publish uevent. Error = %d\n", ret);
+}
+static DECLARE_WORK(work_notify_udev, notify_udev);
+
/* Host poweroff function. */
static void poweroff_host(unsigned long event)
{
@@ -39,13 +157,11 @@ static void poweroff_host(unsigned long event)
/* Start poweroff timer */
static void start_poweroff_timer(unsigned long event, int32_t timeout)
{
- unsigned long flags;
-
/* Check if any poweroff timer is already active with lower timeout */
- spin_lock_irqsave(&poweroff_timer_spinlock, flags);
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
if (timer_pending(&poweroff_timer) &&
(poweroff_timer.expires < (jiffies + timeout * HZ))) {
- spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
pr_info("An earlier poweroff already scheduled due to %s "
"event\n", poweroff_events_map[poweroff_timer.data]);
return;
@@ -54,7 +170,7 @@ static void start_poweroff_timer(unsigned long event, int32_t timeout)
/* Start a new timer/modify existing timer with new timeout value */
poweroff_timer.data = event;
mod_timer(&poweroff_timer, jiffies + timeout * HZ);
- spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
pr_info("Scheduled poweroff due to %s event after %d seconds\n",
poweroff_events_map[event], timeout);
}
@@ -62,29 +178,26 @@ static void start_poweroff_timer(unsigned long event, int32_t timeout)
/* Cancel poweroff timer for EPOW event */
static void cancel_epow_poweroff_timer(void)
{
- unsigned long flags;
-
/* Check if poweroff time for epow event is running */
- spin_lock_irqsave(&poweroff_timer_spinlock, flags);
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
if (timer_pending(&poweroff_timer) &&
poweroff_timer.data == POWEROFF_EVENT_EPOW) {
del_timer_sync(&poweroff_timer);
- spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
pr_info("Poweroff timer for EPOW event deactivated\n");
return;
}
- spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
}
/* Stop poweroff timer */
static void stop_poweroff_timer(void)
{
int rc;
- unsigned long flags;
- spin_lock_irqsave(&poweroff_timer_spinlock, flags);
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
rc = del_timer_sync(&poweroff_timer);
- spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
if (rc)
pr_info("Poweroff timer deactivated\n");
@@ -155,14 +268,51 @@ void process_epow_event(struct epow_event *p_epow)
int guest_shutdown = 0;
int epow_delay = 0;
- /*
- * OPAL_EPOW_NONE shows that EPOW condition has returned
- * to normal and thus we need to cancel any EPOW poweroff
- * timer running.
- */
- if (p_epow->type == OPAL_EPOW_NONE) {
+ /* Check EPOW event type and process accordingly */
+ switch (p_epow->type) {
+ case OPAL_EPOW_NONE:
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ poweroff_property.power_supply = POWER_SUPPLY_NORMAL;
+ poweroff_property.thermal = THERMAL_NORMAL;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ /* Schedule async work to notify udev */
+ schedule_work(&work_notify_udev);
+
+ /* Cancel any running EPOW event poweroff timer */
cancel_epow_poweroff_timer();
return;
+ case OPAL_EPOW_EVENT_TYPE3:
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ switch (p_epow->reason_code) {
+ case OPAL_EPOW_UPS:
+ poweroff_property.power_supply = POWER_SUPPLY_UPS;
+ break;
+ case OPAL_EPOW_AMB_TEMP:
+ poweroff_property.thermal = THERMAL_HIGH_AMB_TEMP;
+ break;
+ case OPAL_EPOW_INT_TEMP:
+ poweroff_property.power_supply = THERMAL_HIGH_INT_TEMP;
+ }
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+ break;
+ case OPAL_EPOW_EVENT_TYPE4:
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ switch (p_epow->reason_code) {
+ case OPAL_EPOW_UPS:
+ poweroff_property.power_supply = POWER_SUPPLY_UPS_LOW;
+ break;
+ case OPAL_EPOW_AMB_TEMP:
+ poweroff_property.thermal = THERMAL_CRIT_AMB_TEMP;
+ break;
+ case OPAL_EPOW_INT_TEMP:
+ poweroff_property.power_supply = THERMAL_CRIT_INT_TEMP;
+ }
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+ break;
+ default:
+ pr_err("Unknown EPOW event\n");
+ return;
}
/*
@@ -191,6 +341,10 @@ void process_epow_event(struct epow_event *p_epow)
if (epow_delay)
timeout += POWEROFF_EPOW_DELAY;
+ /* Schedule async work to notify udev */
+ schedule_work(&work_notify_udev);
+
+ /* Start kernel poweroff timer */
start_poweroff_timer(POWEROFF_EVENT_EPOW, timeout);
}
@@ -208,6 +362,14 @@ void process_dpo_event(int64_t dpo_timeout)
else
dpo_timeout = POWEROFF_HOST_TIME;
+ spin_lock_irqsave(&poweroff_event_spinlock, flags);
+ poweroff_property.admin_shutdown = true;
+ spin_unlock_irqrestore(&poweroff_event_spinlock, flags);
+
+ /* Schedule async work to notify udev */
+ schedule_work(&work_notify_udev);
+
+ /* Start kernel poweroff timer */
start_poweroff_timer(POWEROFF_EVENT_DPO, dpo_timeout);
}
@@ -227,7 +389,7 @@ static void process_existing_poweroff_events(void)
if (epow.type != OPAL_EPOW_NONE) {
pr_info("Existing EPOW%d event detected. "
- "Timeout = %d seconds, Reason: %s\n",
+ "HW timeout = %d seconds, Reason: %s\n",
epow.type, epow.timeout,
get_epow_reason_string(epow.reason_code));
process_epow_event(&epow);
@@ -238,7 +400,7 @@ check_dpo:
/* Check for any existing DPO event */
get_dpo_timeout(&dpo_timeout);
if (dpo_timeout) {
- pr_info("Existing DPO event detected. Timeout = %lld seconds\n",
+ pr_info("Existing DPO event detected. HW timeout = %lld seconds\n",
dpo_timeout);
process_dpo_event(dpo_timeout);
} else
@@ -259,11 +421,11 @@ static int opal_epow_event_notifier(struct notifier_block *nb,
return 0;
}
- pr_info("EPOW%d event received. Timeout = %d seconds, Reason: %s\n",
+ pr_info("EPOW%d event received. HW timeout = %d seconds, Reason: %s\n",
epow.type, epow.timeout,
get_epow_reason_string(epow.reason_code));
- /* Processing EPOW event information */
+ /* Process EPOW event information */
process_epow_event(&epow);
return 0;
@@ -284,8 +446,9 @@ static int opal_dpo_event_notifier(struct notifier_block *nb,
return 0;
}
- pr_info("DPO event received. Timeout = %lld seconds\n", dpo_timeout);
+ pr_info("DPO event received. HW timeout = %lld seconds\n", dpo_timeout);
+ /* Process DPO event */
process_dpo_event(dpo_timeout);
return 0;
@@ -309,12 +472,24 @@ static struct notifier_block opal_dpo_nb = {
/* Platform driver probe */
static int opal_poweroff_events_probe(struct platform_device *pdev)
{
- int ret;
+ int i, ret;
/* Initialize poweroff timer */
init_timer(&poweroff_timer);
poweroff_timer.function = poweroff_host;
+ /* Kobject pointer for poweroff events platform device */
+ poweroff_dev_kobj = &(pdev->dev.kobj);
+
+ /* Create sysfs attribute files for device */
+ for (i=0; i<ARRAY_SIZE(poweroff_attrs); i++) {
+ ret = device_create_file(&pdev->dev, &poweroff_attrs[i]);
+ if (ret)
+ pr_err("Failed to create sysfs attr file '%s'. "
+ "Error = %d\n", poweroff_attrs[i].attr.name,
+ ret);
+ }
+
/*
* Check for any existing EPOW or DPO events. Host could have missed
* their notifications while booting.
@@ -339,12 +514,15 @@ static int opal_poweroff_events_probe(struct platform_device *pdev)
pr_info("DPO event notifier registered\n");
pr_info("OPAL poweroff events driver initialized\n");
+
return 0;
}
/* Platform driver remove */
static int opal_poweroff_events_remove(struct platform_device *pdev)
{
+ int i;
+
/* Unregister OPAL message notifiers */
opal_notifier_unregister(&opal_dpo_nb);
opal_notifier_unregister(&opal_epow_nb);
@@ -352,6 +530,13 @@ static int opal_poweroff_events_remove(struct platform_device *pdev)
/* Stop poweroff timer */
stop_poweroff_timer();
+ /* Cancel any async work scheduled */
+ cancel_work_sync(&work_notify_udev);
+
+ /* Remove sysfs attribute files */
+ for (i=0; i<ARRAY_SIZE(poweroff_attrs); i++)
+ device_remove_file(&pdev->dev, &poweroff_attrs[i]);
+
pr_info("OPAL poweroff events driver exited\n");
return 0;
}