linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] OPAL Poweroff events driver for PowerNV platform
@ 2015-04-10  6:52 Vipin K Parashar
  2015-04-10  6:52 ` [PATCH 1/2] powerpc/powernv: " Vipin K Parashar
  2015-04-10  6:52 ` [PATCH 2/2] powerpc/powernv: Add udev notification and poweroff event attributes Vipin K Parashar
  0 siblings, 2 replies; 3+ messages in thread
From: Vipin K Parashar @ 2015-04-10  6:52 UTC (permalink / raw)
  To: linuxppc-dev; +Cc: Vipin K Parashar

OPAL Poweroff events driver handles poweroff events on PowerNV platform.
Currently this driver supports EPOW (Early Power Off Warning) and
Delayed Power Off (DPO) events on FSP based systems.
	EPOW events are generated by SPCN/FSP due to various critical system
conditions that need system shutdown. Few examples of these conditions are
high ambient temperature or system running on UPS power with UPS battery low.
DPO event is generated in reponse to user initiated system shutdown request.
	OPAL Poweroff events driver handles OPAL notifications for these events,
processes them, starts kernel system poweroff timers and send uevents to notify
udev. Poweroff event attributes are exposed as attributes files for platform
device opal-poweroff-events. Host userspace should add udev scripts to perform
various actions like sending event info to guests for graceful guest shutdown
and host poweroff scripts.

Below attribute files are created for platform device opal-poweroff-events:

admin_shutdown - Poweroff needed due to admin requested shutdown
        Values as below:
        No
        Yes

power_supply - Poweroff needed due to UPS/internal battery
        Values as below:
        Normal
        UPS
        UPS-low

thermal - Poweroff needed due to high, critical ambient or internal temp
        Values as below:
        Normal
        High-ambient-temp
        Critical-ambient-temp
        High-internal-temp
        Critical-internal-temp

timeout: Time allowed for poweroff

Vipin K Parashar (2):
  powerpc/powernv: OPAL poweroff events driver for PowerNV platform
  powerpc/powernv: Add udev notification and poweroff event attributes

 arch/powerpc/include/asm/opal.h                    |  22 +
 arch/powerpc/platforms/powernv/Makefile            |   2 +-
 .../platforms/powernv/opal-poweroff-events.c       | 567 +++++++++++++++++++++
 .../platforms/powernv/opal-poweroff-events.h       |  35 ++
 arch/powerpc/platforms/powernv/opal-wrappers.S     |   3 +-
 arch/powerpc/platforms/powernv/opal.c              |   8 +-
 6 files changed, 634 insertions(+), 3 deletions(-)
 create mode 100644 arch/powerpc/platforms/powernv/opal-poweroff-events.c
 create mode 100644 arch/powerpc/platforms/powernv/opal-poweroff-events.h

--
1.9.3

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] powerpc/powernv: OPAL Poweroff events driver for PowerNV platform
  2015-04-10  6:52 [PATCH 0/2] OPAL Poweroff events driver for PowerNV platform Vipin K Parashar
@ 2015-04-10  6:52 ` Vipin K Parashar
  2015-04-10  6:52 ` [PATCH 2/2] powerpc/powernv: Add udev notification and poweroff event attributes Vipin K Parashar
  1 sibling, 0 replies; 3+ messages in thread
From: Vipin K Parashar @ 2015-04-10  6:52 UTC (permalink / raw)
  To: linuxppc-dev; +Cc: Vipin K Parashar

OPAL Poweroff events driver handles poweroff events on PowerNV platform.
Currently this driver supports EPOW (Early Power Off Warning) and
Delayed Power Off (DPO) events on FSP based systems.
	EPOW events are generated by SPCN/FSP due to various critical system
conditions that need system shutdown. Few examples of these conditions are
high ambient temperature or system running on UPS power with UPS battery low.
DPO event is generated in reponse to user initiated system shutdown request.
	OPAL Poweroff events driver handles OPAL notifications for these events,
processes these event notifications, starts kernel system poweroff timers and
send uevents for host user space. Host user space should add udev scripts to
perform various actions like host poweroff scripts and sending event into to
guests for graceful guest shutdown.
	This patch handles only host processing i.e. handle OPAL EPOW, DPO
event notifications, process event information and start kernel timers for
host shutdown. Subsequent patches will add support for sending uevent
notification to host userspace.

Signed-off-by: Vipin K Parashar <vipin@linux.vnet.ibm.com>
---
 arch/powerpc/include/asm/opal.h                    |  22 ++
 arch/powerpc/platforms/powernv/Makefile            |   2 +-
 .../platforms/powernv/opal-poweroff-events.c       | 382 +++++++++++++++++++++
 .../platforms/powernv/opal-poweroff-events.h       |  35 ++
 arch/powerpc/platforms/powernv/opal-wrappers.S     |   3 +-
 arch/powerpc/platforms/powernv/opal.c              |   8 +-
 6 files changed, 449 insertions(+), 3 deletions(-)
 create mode 100644 arch/powerpc/platforms/powernv/opal-poweroff-events.c
 create mode 100644 arch/powerpc/platforms/powernv/opal-poweroff-events.h

diff --git a/arch/powerpc/include/asm/opal.h b/arch/powerpc/include/asm/opal.h
index 9ee0a30..d2fcb5e 100644
--- a/arch/powerpc/include/asm/opal.h
+++ b/arch/powerpc/include/asm/opal.h
@@ -166,9 +166,11 @@ struct opal_sg_list {
 #define OPAL_UNREGISTER_DUMP_REGION		102
 #define OPAL_WRITE_TPO				103
 #define OPAL_READ_TPO				104
+#define OPAL_GET_DPO_STATUS			105
 #define OPAL_IPMI_SEND				107
 #define OPAL_IPMI_RECV				108
 #define OPAL_I2C_REQUEST			109
+#define OPAL_GET_EPOW_EVENT_INFO                116
 
 /* Device tree flags */
 
@@ -306,6 +308,7 @@ enum OpalMessageType {
 	OPAL_MSG_EPOW,
 	OPAL_MSG_SHUTDOWN,		/* params[0] = 1 reboot, 0 shutdown */
 	OPAL_MSG_HMI_EVT,
+	OPAL_MSG_DPO,
 	OPAL_MSG_TYPE_MAX,
 };
 
@@ -759,6 +762,23 @@ struct opal_i2c_request {
 	__be64 buffer_ra;		/* Buffer real address */
 };
 
+/* EPOW event types */
+#define OPAL_EPOW_EVENT_TYPE3   3       /* EPOW3 event */
+#define OPAL_EPOW_EVENT_TYPE4   4       /* EPOW4 event */
+
+/* EPOW reason code values */
+#define OPAL_EPOW_NONE		0	/* EPOW normal/reset */
+#define OPAL_EPOW_ON_UPS	1	/* System on UPS */
+#define OPAL_EPOW_AMB_TEMP	2	/* Over ambient temperature */
+#define OPAL_EPOW_INT_TEMP	3	/* Over internal temperature */
+
+/* EPOW event information */
+struct opal_epow_event {
+	__be32 type;		/* EPOW event types */
+	__be32 reason_code;	/* EPOW reason */
+	__be32 timeout;		/* Time allowed for EPOW event */
+};
+
 /* /sys/firmware/opal */
 extern struct kobject *opal_kobj;
 
@@ -880,6 +900,8 @@ int64_t opal_pci_reinit(uint64_t phb_id, uint64_t reinit_scope, uint64_t data);
 int64_t opal_pci_mask_pe_error(uint64_t phb_id, uint16_t pe_number, uint8_t error_type, uint8_t mask_action);
 int64_t opal_set_slot_led_status(uint64_t phb_id, uint64_t slot_id, uint8_t led_type, uint8_t led_action);
 int64_t opal_get_epow_status(__be64 *status);
+int64_t opal_get_epow_event_info(struct opal_epow_event *);
+int64_t opal_get_dpo_status(int64_t *dpo_timeout);
 int64_t opal_set_system_attention_led(uint8_t led_action);
 int64_t opal_pci_next_error(uint64_t phb_id, __be64 *first_frozen_pe,
 			    __be16 *pci_error_type, __be16 *severity);
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile
index 6f3c5d3..1680d42 100644
--- a/arch/powerpc/platforms/powernv/Makefile
+++ b/arch/powerpc/platforms/powernv/Makefile
@@ -1,7 +1,7 @@
 obj-y			+= setup.o opal-wrappers.o opal.o opal-async.o
 obj-y			+= opal-rtc.o opal-nvram.o opal-lpc.o opal-flash.o
 obj-y			+= rng.o opal-elog.o opal-dump.o opal-sysparam.o opal-sensor.o
-obj-y			+= opal-msglog.o opal-hmi.o opal-power.o
+obj-y			+= opal-msglog.o opal-hmi.o opal-power.o opal-poweroff-events.o
 
 obj-$(CONFIG_SMP)	+= smp.o subcore.o subcore-asm.o
 obj-$(CONFIG_PCI)	+= pci.o pci-p5ioc2.o pci-ioda.o
diff --git a/arch/powerpc/platforms/powernv/opal-poweroff-events.c b/arch/powerpc/platforms/powernv/opal-poweroff-events.c
new file mode 100644
index 0000000..f1669e0
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-poweroff-events.c
@@ -0,0 +1,382 @@
+/*
+ * PowerNV OPAL poweroff events driver
+ *
+ * Copyright 2015 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#define pr_fmt(fmt)	"OPAL_POWEROFF_EVENT: "    fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <asm/opal.h>
+
+#include "opal-poweroff-events.h"
+
+/* Poweroff timers */
+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"};
+
+/* Host poweroff function. */
+static void poweroff_host(unsigned long event)
+{
+	pr_info("Powering off system\n");
+	orderly_poweroff(true);
+}
+
+/* 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);
+	if (timer_pending(&poweroff_timer) &&
+			(poweroff_timer.expires < (jiffies + timeout * HZ))) {
+		spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+		pr_info("An earlier poweroff already scheduled due to %s "
+			"event\n", poweroff_events_map[poweroff_timer.data]);
+		return;
+	}
+
+	/* 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);
+	pr_info("Scheduled poweroff due to %s event after %d seconds\n",
+			poweroff_events_map[event], 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);
+	if (timer_pending(&poweroff_timer) &&
+		poweroff_timer.data ==  POWEROFF_EVENT_EPOW) {
+		del_timer_sync(&poweroff_timer);
+		spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+		pr_info("Poweroff timer for EPOW event deactivated\n");
+		return;
+	}
+	spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+}
+
+/* Stop poweroff timer */
+static void stop_poweroff_timer(void)
+{
+	int rc;
+	unsigned long flags;
+
+	spin_lock_irqsave(&poweroff_timer_spinlock, flags);
+	rc = del_timer_sync(&poweroff_timer);
+	spin_unlock_irqrestore(&poweroff_timer_spinlock, flags);
+
+	if (rc)
+		pr_info("Poweroff timer deactivated\n");
+}
+
+/* Determine EPOW reason text from EPOW reason code */
+static const char *get_epow_reason_string(int code)
+{
+	switch (code) {
+	case OPAL_EPOW_NONE:
+		return "EPOW condition not present/cleared";
+	case OPAL_EPOW_ON_UPS:
+		return "System on UPS/internal battery";
+	case OPAL_EPOW_AMB_TEMP:
+		return "High ambient temperature";
+	case OPAL_EPOW_INT_TEMP:
+		return "High internal temperature";
+	}
+
+	return "Unknown";
+}
+
+/* Get EPOW event information */
+static int get_epow_event_info(struct epow_event *p_epow)
+{
+	int rc;
+	struct opal_epow_event opal_epow;
+
+	/* Get EPOW event information from OPAL */
+	rc = opal_get_epow_event_info(&opal_epow);
+	if (rc == OPAL_PARAMETER) {
+		pr_err("opal_get_epow_event_info: Invalid parameter\n");
+		return -1;
+	}
+
+	if (rc) {
+		pr_err("opal_get_epow_event_info failed\n");
+		return -1;
+	}
+
+	/* Endian conversions */
+	p_epow->type = be32_to_cpu(opal_epow.type);
+	p_epow->reason_code = be32_to_cpu(opal_epow.reason_code);
+	p_epow->timeout = be32_to_cpu(opal_epow.timeout);
+
+	return 0;
+}
+
+/* Get DPO timeout */
+static void get_dpo_timeout(int64_t *dpo_timeout)
+{
+	int rc;
+	__be64 timeout;
+
+	rc = opal_get_dpo_status(&timeout);
+
+	if (rc == OPAL_WRONG_STATE) {
+		pr_info("DPO not initiated by OPAL\n");
+		*dpo_timeout = 0;
+	} else
+		*dpo_timeout = be64_to_cpu(timeout);
+}
+
+/* Process EPOW event information */
+void process_epow_event(struct epow_event *p_epow)
+{
+	int32_t timeout;
+	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) {
+		cancel_epow_poweroff_timer();
+		return;
+	}
+
+	/*
+	 * Proceed with timeout calculations to perform graceful system
+	 * shutdown. First allow enough time for graceful guest shutdown,
+	 * and there after allow time to wait for EPOW condition to return
+	 * to normal.
+	 */
+	timeout = p_epow->timeout;
+
+	timeout = timeout - POWEROFF_HOST_TIME;
+	if (timeout > POWEROFF_GUEST_TIME) {
+		timeout = timeout - POWEROFF_GUEST_TIME;
+		guest_shutdown = 1;
+	}
+
+	if ((timeout > POWEROFF_EPOW_DELAY) && guest_shutdown) {
+		timeout = timeout - POWEROFF_EPOW_DELAY;
+		epow_delay = 1;
+	}
+
+	/* Calculate poweroff timeout value */
+	timeout = POWEROFF_HOST_TIME;
+	if (guest_shutdown)
+		timeout += POWEROFF_GUEST_TIME;
+	if (epow_delay)
+		timeout += POWEROFF_EPOW_DELAY;
+
+	start_poweroff_timer(POWEROFF_EVENT_EPOW, timeout);
+}
+
+
+/* Process DPO event information */
+void process_dpo_event(int64_t dpo_timeout)
+{
+	/*
+	 * Calculate poweroff timeout value.
+	 * Allow enough time for guests VM to shutdown
+	 * before proceeding with host shutdown.
+	 */
+	if (dpo_timeout > (POWEROFF_GUEST_TIME + POWEROFF_HOST_TIME))
+		dpo_timeout = POWEROFF_GUEST_TIME + POWEROFF_HOST_TIME;
+	else
+		dpo_timeout = POWEROFF_HOST_TIME;
+
+	start_poweroff_timer(POWEROFF_EVENT_DPO, dpo_timeout);
+}
+
+/* Check for any existing EPOW, DPO events and process them, if existing */
+static void process_existing_poweroff_events(void)
+{
+	int rc;
+	int64_t dpo_timeout;
+	struct epow_event epow;
+
+	/* Check for any existing EPOW event */
+	rc = get_epow_event_info(&epow);
+	if (rc) {
+		pr_err("Failed to get EPOW event info\n");
+		goto check_dpo;
+	}
+
+	if (epow.type != OPAL_EPOW_NONE) {
+		pr_info("Existing EPOW%d event detected. "
+			"Timeout = %d seconds, Reason: %s\n",
+			epow.type, epow.timeout,
+			get_epow_reason_string(epow.reason_code));
+		process_epow_event(&epow);
+	} else
+		pr_info("No existing EPOW event detected\n");
+
+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",
+				dpo_timeout);
+		process_dpo_event(dpo_timeout);
+	 } else
+		pr_info("No existing DPO event detected\n");
+}
+
+/* Platform EPOW message received */
+static int opal_epow_event_notifier(struct notifier_block *nb,
+			unsigned long msg_type, void *msg)
+{
+	int rc;
+	struct epow_event epow;
+
+	/* Get EPOW event details */
+	rc = get_epow_event_info(&epow);
+	if (rc) {
+		pr_err("Failed to get EPOW event info\n");
+		return 0;
+	}
+
+	pr_info("EPOW%d event received. Timeout = %d seconds, Reason: %s\n",
+		epow.type, epow.timeout,
+		get_epow_reason_string(epow.reason_code));
+
+	/* Processing EPOW event information */
+	process_epow_event(&epow);
+
+	return 0;
+}
+
+
+/* Platform DPO message received */
+static int opal_dpo_event_notifier(struct notifier_block *nb,
+				unsigned long msg_type, void *msg)
+{
+	int64_t dpo_timeout;
+
+	/* Get DPO timeout */
+	get_dpo_timeout(&dpo_timeout);
+
+	if (!dpo_timeout) {
+		pr_err("Failed to get DPO event timeout\n");
+		return 0;
+	}
+
+	pr_info("DPO event received. Timeout = %lld seconds\n", dpo_timeout);
+
+	process_dpo_event(dpo_timeout);
+
+	return 0;
+}
+
+
+/* OPAL EPOW event notifier block */
+static struct notifier_block opal_epow_nb = {
+	.notifier_call  = opal_epow_event_notifier,
+	.next           = NULL,
+	.priority       = 0,
+};
+
+/* OPAL DPO event notifier block */
+static struct notifier_block opal_dpo_nb = {
+	.notifier_call  = opal_dpo_event_notifier,
+	.next           = NULL,
+	.priority       = 0,
+};
+
+/* Platform driver probe */
+static int opal_poweroff_events_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	/* Initialize poweroff timer */
+	init_timer(&poweroff_timer);
+	poweroff_timer.function = poweroff_host;
+
+	/*
+	 * Check for any existing EPOW or DPO events. Host could have missed
+	 * their notifications while booting.
+	 */
+	process_existing_poweroff_events();
+
+	/* Register EPOW event notifier */
+	ret = opal_message_notifier_register(OPAL_MSG_EPOW, &opal_epow_nb);
+	if (ret) {
+		pr_err("EPOW event notifier registration failed\n");
+		return ret;
+	}
+	pr_info("EPOW event notifier registered\n");
+
+	/* Register DPO event notifier */
+	ret = opal_message_notifier_register(OPAL_MSG_DPO, &opal_dpo_nb);
+	if (ret) {
+		pr_err("DPO event notifier registration failed\n");
+		opal_notifier_unregister(&opal_epow_nb);
+		return ret;
+	}
+	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)
+{
+	/* Unregister OPAL message notifiers */
+	opal_notifier_unregister(&opal_dpo_nb);
+	opal_notifier_unregister(&opal_epow_nb);
+
+	/* Stop poweroff timer */
+	stop_poweroff_timer();
+
+	pr_info("OPAL poweroff events driver exited\n");
+	return 0;
+}
+
+/* Platform driver property match */
+static const struct of_device_id opal_poweroff_events_match[] = {
+	{
+		.compatible     = "ibm,opal-v3-epow",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, opal_poweroff_events_match);
+
+static struct platform_driver opal_poweroff_events_driver = {
+	.probe  = opal_poweroff_events_probe,
+	.remove = opal_poweroff_events_remove,
+	.driver = {
+		.name = "opal-poweroff-events-driver",
+		.owner = THIS_MODULE,
+		.of_match_table = opal_poweroff_events_match,
+	},
+};
+
+module_platform_driver(opal_poweroff_events_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Vipin K Parashar <vipin@linux.vnet.ibm.com>");
+MODULE_DESCRIPTION("PowerNV OPAL poweroff events driver");
diff --git a/arch/powerpc/platforms/powernv/opal-poweroff-events.h b/arch/powerpc/platforms/powernv/opal-poweroff-events.h
new file mode 100644
index 0000000..dfc9381
--- /dev/null
+++ b/arch/powerpc/platforms/powernv/opal-poweroff-events.h
@@ -0,0 +1,35 @@
+/*
+ * PowerNV OPAL poweroff events driver definitions
+ *
+ * Copyright 2015 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _OPAL_POWEROFF_EVENTS_H
+#define _OPAL_POWEROFF_EVENTS_H
+
+/*
+ * EPOW event information
+ * This structure is almost same as struct opal_epow_event, defined in
+ * asm/opal.h. which uses big endian data types.
+ */
+
+struct epow_event {
+	int32_t type;		/* EPOW event types */
+	int32_t reason_code;	/* EPOW reason */
+	int32_t timeout;	/* Time allowed for EPOW event */
+};
+
+#define POWEROFF_EVENT_EPOW	0
+#define POWEROFF_EVENT_DPO	1
+#define POWEROFF_EVENTS		2
+
+#define	POWEROFF_HOST_TIME	10	/* Max time for host shutdown */
+#define	POWEROFF_GUEST_TIME	300	/* Max time for guests shutdown */
+#define POWEROFF_EPOW_DELAY	300	/* Wait time for EPOW condition
+					 * to return to normal */
+#endif /* _OPAL_POWEROFF_EVENTS_H */
diff --git a/arch/powerpc/platforms/powernv/opal-wrappers.S b/arch/powerpc/platforms/powernv/opal-wrappers.S
index 0509bca..1df509a 100644
--- a/arch/powerpc/platforms/powernv/opal-wrappers.S
+++ b/arch/powerpc/platforms/powernv/opal-wrappers.S
@@ -248,7 +248,6 @@ OPAL_CALL(opal_pci_fence_phb,			OPAL_PCI_FENCE_PHB);
 OPAL_CALL(opal_pci_reinit,			OPAL_PCI_REINIT);
 OPAL_CALL(opal_pci_mask_pe_error,		OPAL_PCI_MASK_PE_ERROR);
 OPAL_CALL(opal_set_slot_led_status,		OPAL_SET_SLOT_LED_STATUS);
-OPAL_CALL(opal_get_epow_status,			OPAL_GET_EPOW_STATUS);
 OPAL_CALL(opal_set_system_attention_led,	OPAL_SET_SYSTEM_ATTENTION_LED);
 OPAL_CALL(opal_pci_next_error,			OPAL_PCI_NEXT_ERROR);
 OPAL_CALL(opal_pci_poll,			OPAL_PCI_POLL);
@@ -292,3 +291,5 @@ OPAL_CALL(opal_tpo_read,			OPAL_READ_TPO);
 OPAL_CALL(opal_ipmi_send,			OPAL_IPMI_SEND);
 OPAL_CALL(opal_ipmi_recv,			OPAL_IPMI_RECV);
 OPAL_CALL(opal_i2c_request,			OPAL_I2C_REQUEST);
+OPAL_CALL(opal_get_epow_event_info,		OPAL_GET_EPOW_EVENT_INFO);
+OPAL_CALL(opal_get_dpo_status,			OPAL_GET_DPO_STATUS);
diff --git a/arch/powerpc/platforms/powernv/opal.c b/arch/powerpc/platforms/powernv/opal.c
index 18fd4e7..29f80f4 100644
--- a/arch/powerpc/platforms/powernv/opal.c
+++ b/arch/powerpc/platforms/powernv/opal.c
@@ -743,7 +743,7 @@ static void __init opal_irq_init(struct device_node *dn)
 
 static int __init opal_init(void)
 {
-	struct device_node *np, *consoles;
+	struct device_node *np, *consoles, *epow;
 	int rc;
 
 	opal_node = of_find_node_by_path("/ibm,opal");
@@ -791,6 +791,12 @@ static int __init opal_init(void)
 		opal_msglog_init();
 	}
 
+	epow = of_find_node_by_path("/ibm,opal/epow");
+	if (epow) {
+		of_platform_device_create(epow, "opal-poweroff-events", NULL);
+		of_node_put(epow);
+	}
+
 	opal_ipmi_init(opal_node);
 
 	return 0;
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] powerpc/powernv: Add udev notification and poweroff event attributes
  2015-04-10  6:52 [PATCH 0/2] OPAL Poweroff events driver for PowerNV platform Vipin K Parashar
  2015-04-10  6:52 ` [PATCH 1/2] powerpc/powernv: " Vipin K Parashar
@ 2015-04-10  6:52 ` Vipin K Parashar
  1 sibling, 0 replies; 3+ messages in thread
From: Vipin K Parashar @ 2015-04-10  6:52 UTC (permalink / raw)
  To: linuxppc-dev; +Cc: Vipin K Parashar, Vaibhav Jain

This patch adds udev notification and poweroff event attributes for
EPOW, DPO poweroff events. OPAL Poweroff events driver exposes these
poweroff event attributes as attribute files for platform device
opal-poweroff-events. Upon receiving OPAL notifications for EPOW, DPO
events, driver sends uevents to notify udev and sets event attributes
accordingly.

Below attribute files are created for platform device opal-poweroff-events:

admin_shutdown - Poweroff needed due to admin requested shutdown
	Values as below:
	No
	Yes

power_supply - Poweroff needed due to UPS/internal battery
	Values as below:
	Normal
	UPS
	UPS-low

thermal - Poweroff needed due to abnormal ambient or internal temp
	Values as below:
	Normal
	High-ambient-temp
	Critical-ambient-temp
	High-internal-temp
	Critical-internal-temp

timeout - Time allowed for poweroff

Signed-off-by: Vaibhav Jain <vaibhav@linux.vnet.ibm.com>
Signed-off-by: Vipin K Parashar <vipin@linux.vnet.ibm.com>
---
 .../platforms/powernv/opal-poweroff-events.c       | 239 ++++++++++++++++++---
 1 file changed, 212 insertions(+), 27 deletions(-)

diff --git a/arch/powerpc/platforms/powernv/opal-poweroff-events.c b/arch/powerpc/platforms/powernv/opal-poweroff-events.c
index f1669e0..757ed83 100644
--- a/arch/powerpc/platforms/powernv/opal-poweroff-events.c
+++ b/arch/powerpc/platforms/powernv/opal-poweroff-events.c
@@ -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;
 }
-- 
1.9.3

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2015-04-10  6:53 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-04-10  6:52 [PATCH 0/2] OPAL Poweroff events driver for PowerNV platform Vipin K Parashar
2015-04-10  6:52 ` [PATCH 1/2] powerpc/powernv: " Vipin K Parashar
2015-04-10  6:52 ` [PATCH 2/2] powerpc/powernv: Add udev notification and poweroff event attributes Vipin K Parashar

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).