@@ -589,6 +589,16 @@ config SUN_OPENPROMFS
Only choose N if you know in advance that you will not need to
modify
OpenPROM settings on the running system.
+config ILOM_SYSFS
+ tristate "ILOM support in sysfs"
+ depends on SPARC64 && SYSFS && IPMI_HANDLER && IPMI_SI
+ default y
+ help
+ Say Y or M here to enable the exporting of some hardware related
+ data from the ILOM service processor via sysfs. This is useful
+ to get DIMM details or ROM versions. Exported data is available
+ under /sys/firmware/ilom when this option is enabled and loaded.
+
# Makefile helpers
config SPARC64_PCI
bool
@@ -120,6 +120,8 @@ obj-$(CONFIG_COMPAT) += $(audit--y)
pc--$(CONFIG_PERF_EVENTS) := perf_event.o
obj-$(CONFIG_SPARC64) += $(pc--y)
+obj-$(CONFIG_ILOM_SYSFS) += ilom.o
+
obj-$(CONFIG_UPROBES) += uprobes.o
obj-$(CONFIG_SPARC64) += jump_label.o
new file mode 100644
@@ -0,0 +1,923 @@
+/*
+ * ilom.c - Expose hardware properties of the ILOM to userspace
+ *
+ * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/ratelimit.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/ipmi.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+
+#define COMMAND_STR "show -d properties -format nowrap -level all\n"
+
+/* The IPMI interface number can be changed while the module is loaded */
+static unsigned int ipmi_interface;
+module_param_named(interface, ipmi_interface, uint, S_IRUSR | S_IWUSR);
+MODULE_PARM_DESC(interface, "IPMI Interface number (default=0)");
+
+/* maximum number of IPMI retries */
+#define IPMI_RETRIES 3
+
+/* print on the console the time it took to retrieve data from ILOM */
+#define ILOM_PROFILING 0
+
+/* Number of seconds to pause before attempting again, when the IPMI
shell has
+ * been forcibly stolen */
+#define IPMI_STOLEN_RETRY_DELAY 60
+
+struct ilom_ipmi {
+ struct ipmi_user_hndl ipmi_hndlrs;
+
+ struct completion read_complete;
+ struct ipmi_addr address;
+ ipmi_user_t user;
+
+ struct kernel_ipmi_msg tx_message;
+ long tx_msgid;
+
+ void *rx_msg_data;
+ unsigned short rx_msg_len;
+ unsigned char rx_result;
+ int rx_recv_type;
+};
+
+/*
+ * Dispatch IPMI messages to caller(s)
+ */
+static void msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)
+{
+ unsigned short rx_len;
+ struct ilom_ipmi *data = (struct ilom_ipmi *) user_msg_data;
+
+ if (msg->msgid != data->tx_msgid) {
+ pr_err_ratelimited("ilom: mismatch between received msgid
(%02lx) and transmitted msgid (%02lx)!\n",
+ msg->msgid, data->tx_msgid);
+ goto handler_cleanup;
+ }
+
+ data->rx_recv_type = msg->recv_type;
+ if (msg->msg.data_len > 0) {
+ data->rx_result = msg->msg.data[0];
+ if (msg->msg.data_len > 1) {
+ rx_len = msg->msg.data_len - 1;
+ if (data->rx_msg_len < rx_len)
+ rx_len = data->rx_msg_len;
+ data->rx_msg_len = rx_len;
+ memcpy(data->rx_msg_data, msg->msg.data + 1,
+ data->rx_msg_len);
+ } else
+ data->rx_msg_len = 0;
+ } else {
+ data->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE;
+ }
+ complete(&data->read_complete);
+
+handler_cleanup:
+ ipmi_free_recv_msg(msg);
+}
+
+/*
+ * Initialize IPMI address, message buffers and user data
+ */
+static int init_ipmi_data(struct ilom_ipmi *data, int interface)
+{
+ int err;
+
+ data->ipmi_hndlrs.ipmi_recv_hndl = msg_handler;
+
+ init_completion(&data->read_complete);
+
+ /* Initialize IPMI address */
+ data->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ data->address.channel = IPMI_BMC_CHANNEL;
+ data->address.data[0] = 0;
+
+ /* Initialize message buffers */
+ data->tx_msgid = 0;
+
+ /* Create IPMI messaging interface user */
+ err = ipmi_create_user(interface, &data->ipmi_hndlrs,
+ data, &data->user);
+ if (err < 0)
+ pr_err_once("ilom: unable to register user with IPMI interface
%d\n",
+ interface);
+
+ return err;
+}
+
+/*
+ * Send an IPMI command
+ */
+static int send_message(struct ilom_ipmi *data)
+{
+ int err;
+
+ err = ipmi_validate_addr(&data->address, sizeof(data->address));
+ if (err) {
+ pr_debug("ilom: validate_addr=%x\n", err);
+ return err;
+ }
+
+ data->tx_msgid++;
+
+ err = ipmi_request_settime(data->user, &data->address, data->tx_msgid,
+ &data->tx_message, data, 0, 0, 0);
+ if (err) {
+ pr_debug("ilom: request_settime=%x\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+/********************
+ * SUNOEM IPMI Code *
+ ********************/
+
+/* Arbitrary timeout in seconds to allow a low-level IPMI command to
complete */
+#define IPMI_TIMEOUT (10 * HZ)
+
+#define IPMI_CC_TIMEOUT 0xc3 /* IPMI completion code */
+
+#define IPMI_NETFN_SUNOEM 0x2e
+#define IPMI_SUNOEM_CLI 0x19
+
+#define SUNOEM_CLI_CMD_OPEN 0 /* Open a new connection */
+#define SUNOEM_CLI_CMD_FORCE 1 /* Close any existing connection,
then open */
+#define SUNOEM_CLI_CMD_CLOSE 2 /* Close the current connection */
+#define SUNOEM_CLI_CMD_POLL 3 /* Poll for new data to/from the server */
+#define SUNOEM_CLI_CMD_EOF 4 /* Poll, client is out of data */
+
+#define SUNOEM_CLI_LEGACY_VERSION 1
+#define SUNOEM_CLI_SEQNUM_VERSION 2
+#define SUNOEM_CLI_VERSION SUNOEM_CLI_SEQNUM_VERSION
+#define SUNOEM_CLI_HEADER 8 /* command + spare + handle */
+#define SUNOEM_CLI_BUF_SIZE (80 - SUNOEM_CLI_HEADER) /* 80 bytes */
+
+#define SUNOEM_CLI_INVALID_VER_ERR "Invalid version"
+#define SUNOEM_CLI_BUSY_ERR "Busy"
+
+struct sunoem_cli_msg {
+ /* Set version to SUNOEM_CLI_VERSION. */
+ uint8_t version;
+
+ /* The command in a request or in a response indicates
+ * an error if != 0 */
+ uint8_t command_response;
+ uint8_t seqnum;
+ uint8_t spare;
+
+ /* Opaque 4-byte handle, supplied in the response to an OPEN request,
+ * and used in all subsequent POLL and CLOSE requests */
+ uint8_t handle[4];
+
+ /* The client data in a request, or the server data in a response. Must
+ * by null terminated, i.e., it must be at least one byte, but can be
+ * smaller if there's less data */
+ char buf[SUNOEM_CLI_BUF_SIZE];
+} __packed;
+
+static uint8_t sunoem_version = SUNOEM_CLI_VERSION;
+static int sunoem_seqnum;
+
+static int sunoem_open_shell(struct ilom_ipmi *data, int command,
+ struct sunoem_cli_msg *cli_req,
+ struct sunoem_cli_msg *cli_rsp)
+{
+ int err;
+
+ memset(cli_req, 0, sizeof(cli_req));
+ cli_req->command_response = command;
+ cli_req->seqnum = sunoem_seqnum;
+
+ data->tx_message.netfn = IPMI_NETFN_SUNOEM;
+ data->tx_message.cmd = IPMI_SUNOEM_CLI;
+ data->tx_message.data = (uint8_t *) cli_req;
+ data->tx_message.data_len = SUNOEM_CLI_HEADER + 1;
+
+ data->rx_msg_data = cli_rsp;
+ data->rx_msg_len = sizeof(*cli_rsp);
+
+ while (1) {
+ cli_req->version = sunoem_version;
+
+ err = send_message(data);
+ if (err)
+ return err;
+
+ err = wait_for_completion_timeout(&data->read_complete,
+ IPMI_TIMEOUT);
+ if (!err)
+ return -ETIMEDOUT;
+
+ if (data->rx_result)
+ return -ENOENT;
+
+ if (!cli_rsp->command_response)
+ break;
+
+ /* we're in an error condition */
+ if (!strncmp(cli_rsp->buf, SUNOEM_CLI_INVALID_VER_ERR,
sizeof(SUNOEM_CLI_INVALID_VER_ERR)-1) ||
+ !strncmp(cli_rsp->buf+1, SUNOEM_CLI_INVALID_VER_ERR,
sizeof(SUNOEM_CLI_INVALID_VER_ERR)-1)) {
+ if (sunoem_version == SUNOEM_CLI_VERSION) {
+ /* Server doesn't support version
+ SUNOEM_CLI_VERSION Fall back to
+ legacy version, and try again*/
+ sunoem_version = SUNOEM_CLI_LEGACY_VERSION;
+ continue;
+ }
+ /* Server doesn't support legacy ver either */
+ pr_err("ilom: failed to connect: %*pE\n",
+ (int) strlen(cli_rsp->buf), cli_rsp->buf);
+ return -ENODEV;
+ }
+
+ if (!strncmp(cli_rsp->buf, SUNOEM_CLI_BUSY_ERR,
+ sizeof(SUNOEM_CLI_BUSY_ERR)-1))
+ return -EBUSY;
+
+ pr_err("ilom: failed to connect: %*pE\n", (int)
strlen(cli_rsp->buf), cli_rsp->buf);
+ return -EINVAL;
+ }
+
+ /*
+ * Bit 1 of seqnum is used as an alternating sequence number
+ * to allow a server that supports it to detect when a retry is being
+ * sent from the host IPMI driver. Typically when this occurs, the
+ * server's last response message would have been dropped. Once the
+ * server detects this condition, it will know that it should retry
+ * sending the response */
+ if (sunoem_version == SUNOEM_CLI_SEQNUM_VERSION)
+ sunoem_seqnum ^= 0x1;
+
+ return 0;
+}
+
+static int sunoem_close_shell(struct ilom_ipmi *data,
+ struct sunoem_cli_msg *cli_req,
+ struct sunoem_cli_msg *cli_rsp)
+{
+ int err;
+
+ memset(cli_req, 0, sizeof(cli_req));
+ cli_req->command_response = SUNOEM_CLI_CMD_CLOSE;
+ cli_req->seqnum = sunoem_seqnum;
+
+ data->tx_message.netfn = IPMI_NETFN_SUNOEM;
+ data->tx_message.cmd = IPMI_SUNOEM_CLI;
+ data->tx_message.data = (uint8_t *) cli_req;
+ data->tx_message.data_len = SUNOEM_CLI_HEADER + 1;
+
+ data->rx_msg_data = cli_rsp;
+ data->rx_msg_len = sizeof(*cli_rsp);
+
+ err = send_message(data);
+ if (err)
+ return err;
+
+ err = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT);
+ if (!err)
+ return -ETIMEDOUT;
+
+ if (data->rx_result)
+ return -ENOENT;
+
+ return 0;
+}
+
+/*
+ * Sends the command onto the IPMI shell
+ * Commands longer than (SUNOEM_CLI_BUF_SIZE-1) chars are not handled
+ * Also collects a line fragment (console output)
+ */
+static int sunoem_send_recv(struct ilom_ipmi *data,
+ char *command_str,
+ struct sunoem_cli_msg *cli_req,
+ struct sunoem_cli_msg *cli_rsp)
+{
+ int err, retries;
+
+ strcpy(cli_req->buf, command_str);
+ cli_req->version = sunoem_version;
+ cli_req->seqnum = sunoem_seqnum;
+
+ data->tx_message.netfn = IPMI_NETFN_SUNOEM;
+ data->tx_message.cmd = IPMI_SUNOEM_CLI;
+ data->tx_message.data = (uint8_t *) cli_req;
+ data->tx_message.data_len = SUNOEM_CLI_HEADER + strlen(command_str)
+ 1;
+
+ data->rx_msg_data = cli_rsp;
+ data->rx_msg_len = sizeof(*cli_rsp);
+
+ retries = 0;
+
+ while (1) {
+ err = send_message(data);
+ if (err)
+ return err;
+
+ err = wait_for_completion_timeout(&data->read_complete,
+ IPMI_TIMEOUT);
+ if (!err)
+ return -ETIMEDOUT;
+
+ if (data->rx_result == IPMI_CC_TIMEOUT) {
+ pr_debug("ilom: failed to poll: %*pE",
+ (int) strlen(cli_rsp->buf), cli_rsp->buf);
+ if (retries++ < IPMI_RETRIES) {
+ pr_err("ilom: retrying\n");
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(2 * HZ);
+ continue;
+ }
+ return -EBUSY;
+ }
+ break;
+ }
+
+ if (sunoem_version == SUNOEM_CLI_SEQNUM_VERSION)
+ sunoem_seqnum ^= 0x1;
+ cli_req->seqnum = sunoem_seqnum;
+
+ return 0;
+}
+
+/***********************
+ * ILOM output parsing *
+ ***********************/
+
+/*
+ * Concatenate line to str, increasing allocation of *str if necessary
+ */
+static int concat_string(char **str, char *line, int *size)
+{
+ size_t len_str = strnlen(*str, *size);
+ size_t len_total = len_str + strlen(line) + 1;
+
+ if (len_total > *size) {
+ char *p = krealloc(*str, len_total, GFP_KERNEL);
+
+ if (!p)
+ return -ENOMEM;
+ *str = p;
+ }
+ strcpy(*str + len_str, line);
+ *size = len_total;
+
+ return 0;
+}
+
+/*
+ * Takes a line fragment and fills in a full line (if such has been found)
+ *
+ * line: The line fragment to parse
+ * line_is_valid: Indicates if line has a valid content
+ * (which can be NULL if we want to clean-up)
+ * parsed: If a full line has been found, it will be made available
+ * in parsed (to be kfreed by the caller)
+ *
+ * Return 0 or and error code
+ */
+static int get_next_line(char *line, int line_is_valid, char **parsed)
+{
+ static char *current_str;
+ static int current_size;
+ char *p;
+
+ if (line_is_valid) {
+ int err;
+
+ /* Clean and kfree things up */
+ if (!line) {
+ if (current_str) {
+ kfree(current_str);
+ current_str = NULL;
+ current_size = 0;
+ }
+ return 0;
+ }
+
+ /* Concatenate line to current_str */
+ err = concat_string(¤t_str, line, ¤t_size);
+ if (err)
+ return err;
+ }
+
+ /* Consume current_str */
+
+ *parsed = NULL;
+
+ /* Ensure first that we've got something to consume */
+ if (!current_str)
+ return 0;
+
+ /* Find the 1st carriage return
+ * and split strings, using a new allocation */
+ p = strnchr(current_str, current_size, '\n');
+ if (p) {
+ int position = p - current_str;
+ int size_left = current_size - position - 1;
+ char *new = kmalloc(size_left, GFP_KERNEL);
+
+ if (!new)
+ return -ENOMEM;
+
+ *p++ = '\0';
+ memcpy(new, p, size_left);
+ *parsed = current_str;
+ current_str = new;
+ current_size = size_left;
+ }
+ return 0;
+}
+
+/*
+ * Take a line fragment and calls cb() for every property it finds.
+ * The arguments given too cb() are not owned by the callback.
+ *
+ * Return 0 if parsing went okay, or an error otherwise
+ */
+static int parse_show_output(char *line, void (*cb)(char*, char*, char*))
+{
+ static int in_properties;
+ static char *key;
+ int line_is_valid = 1;
+ char *parsed = NULL;
+
+ do {
+ int result;
+ char *p;
+
+ result = get_next_line(line, line_is_valid, &parsed);
+
+ if (result) {
+ pr_debug("ilom: an error occured while parsing %*pE\n",
+ (int) strlen(parsed), parsed);
+ get_next_line(NULL, 1, NULL); /* cleanup */
+ return result;
+ }
+
+ line_is_valid = 0;
+
+ if (!parsed)
+ continue;
+
+ if (*parsed == ' ') {
+ /* skip leading spaces */
+ for (p = parsed; *p == ' '; )
+ p++;
+
+ if (*p == '/') {
+ key = kstrdup(p, GFP_KERNEL);
+ in_properties = 1;
+ } else if (!strncmp(p, "Properties", 10)) {
+ /* discard */
+ } else {
+ if (in_properties && (key)) {
+ char *sep = strstr(p, " = ");
+
+ if (sep) {
+ *sep = '\0';
+ cb(key, p, sep + 3);
+ /* above: 3 = strlen(" = ") */
+ }
+ } else {
+ pr_debug("ilom: property in invalid block: %s\n", p);
+ }
+ }
+ } else if (*parsed == '\0') {
+ in_properties = 0;
+ if (key) {
+ kfree(key);
+ key = NULL;
+ }
+ } else if (!strcmp(parsed, "Disconnected")) {
+ /* end of stream marker
+ * nothing to do, really */
+ } else if (!strncmp(parsed, "-> ", 3)) {
+ /* discard */
+ } else {
+ pr_debug("ilom: unparsed: %*pE\n",
+ (int) strlen(parsed), parsed);
+ }
+ kfree(parsed);
+ } while (parsed);
+
+ return 0;
+}
+
+/**************
+ * Sysfs Code *
+ **************/
+
+struct sysfs_property {
+ struct kobj_attribute kattr;
+ char *value;
+};
+
+ssize_t ilom_sysfs_show(struct kobject *object,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct sysfs_property *prop;
+
+ prop = container_of(attr, struct sysfs_property, kattr);
+ return sprintf(buf, "%s\n", prop->value);
+}
+
+struct kobject *ilom_kobj;
+static struct attribute **sysfs_attributes;
+static int sysfs_attributes_length;
+
+/*
+ * Append a property to the list that will be later
+ * passed to sysfs_create_files()
+ */
+static int add_property(struct sysfs_property *prop)
+{
+ static int size; /* how much we've allocated */
+ struct attribute **p;
+
+ if (size <= (sysfs_attributes_length + 1)) {
+ size += 16; /* pre-allocation to relieve the allocator */
+ p = krealloc(sysfs_attributes, size * sizeof(*sysfs_attributes),
+ GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ sysfs_attributes = p;
+ }
+ sysfs_attributes[sysfs_attributes_length++] = &prop->kattr.attr;
+ /* the array must be NULL terminated */
+ sysfs_attributes[sysfs_attributes_length] = NULL;
+ return 0;
+}
+
+/*
+ * Helper function to make populate_sysfs() more readable
+ * returns true if target starts with prefix
+ */
+static inline bool starts_with(char *target, char *prefix)
+{
+ return !strncmp(target, prefix, strlen(prefix));
+}
+
+/*
+ * Helper function to make populate_sysfs() more readable
+ * returns true if s1 and s2 are equal
+ */
+static inline bool equals(char *s1, char *s2)
+{
+ return !strcmp(s1, s2);
+}
+
+/*
+ * Helper function to make populate_sysfs() more readable
+ * return true if haystack contains needle
+ */
+static inline bool contains(char *haystack, char *needle)
+{
+ return strstr(haystack, needle) != NULL;
+}
+
+/*
+ * Match interesting entries and add them to sysfs_attributes
+ */
+void populate_sysfs(char *key, char *property, char *value)
+{
+ /* filter the keys we're interested in */
+ if (starts_with(key, "/System/Memory/DIMMs/DIMM_") ||
+ starts_with(key, "/System/Firmware/Other_Firmware/Firmware_") ||
+ starts_with(key, "/System/Processors/CPUs/CPU_") ||
+ equals(key, "/System") ||
+ equals(key, "/SYS") ||
+ equals(key, "/SYS/MB") ||
+ equals(key, "/SYS/MB/SP") ||
+ equals(key, "/SYS/MB_ENV"))
+ {
+ struct sysfs_property *prop;
+ char *sprop = NULL;
+
+ /* filter-out sensor type properties */
+ if (starts_with(property, "actual_") ||
+ starts_with(property, "health") ||
+ equals(property, "action") ||
+ equals(property, "temperature") ||
+ contains(property, "fault"))
+ return;
+
+ prop = kmalloc(sizeof(*prop), GFP_KERNEL);
+ if (prop)
+ sprop = kmalloc(strlen(key) + strlen(property) + 1,
+ GFP_KERNEL);
+ if (!sprop) {
+ kfree(prop);
+ return;
+ }
+
+ /* Set the name */
+ prop->kattr.attr.name = sprop;
+ for (key++; *key; key++)
+ *sprop++ = (*key == '/' ? '-' : *key);
+ *sprop++ = '-'; /* no worries there's enough space */
+ strcpy(sprop, property);
+
+ prop->kattr.attr.mode = VERIFY_OCTAL_PERMISSIONS(S_IRUSR);
+ prop->kattr.show = ilom_sysfs_show;
+ prop->kattr.store = NULL;
+ prop->value = kstrdup(value, GFP_KERNEL);
+
+ add_property(prop);
+ }
+}
+
+/*
+ * Create /sys/firmware/ilom and adds to it the attributes that we've been
+ * collecting while parsing the ILOM shell output.
+ * the argument passed is effectively sysfs_attributes
+ */
+void ilom_add_to_sysfs(struct attribute **attributes)
+{
+ const struct attribute **pattr = (const struct attribute **)
attributes;
+ int err;
+
+ ilom_kobj = kobject_create_and_add("ilom", firmware_kobj);
+ if (!ilom_kobj) {
+ pr_err("ilom: unable to create sysfs entry\n");
+ return;
+ }
+ if (attributes) {
+ err = sysfs_create_files(ilom_kobj, pattr);
+ if (err) {
+ pr_err("ilom: unable to create sysfs properties\n");
+ kobject_put(ilom_kobj);
+ }
+ }
+}
+
+/*
+ * Called when the IPMI shell has been interrupted, either
consecutively to the
+ * ipmi shell being stolen or when we quit the control loop (because
the module
+ * is unloaded or the IPMI layer returned an error
+ */
+static void cleanup_existing_attributes(struct attribute **attributes)
+{
+ struct attribute **attr;
+
+ if (!sysfs_attributes)
+ return;
+
+ for (attr = attributes; *attr; attr++) {
+ struct sysfs_property *prop;
+
+ prop = container_of(*attr, struct sysfs_property, kattr.attr);
+ kfree(prop);
+ sysfs_attributes_length--;
+ }
+ WARN_ON(sysfs_attributes_length != 0);
+}
+
+/*********************
+ * Top-level routine *
+ *********************/
+
+#define ILOM_IPMI_RUN_SUCCESS 0
+#define ILOM_IPMI_RUN_STOLEN -1
+#define ILOM_IPMI_RUN_FAILURE -2
+
+/*
+ * Run a command on the ilom (must be a show type of command)
+ * Collects and parse the output, creating an array of sysfs attribute
objects
+ * suitable for sysfs_create_files()
+ */
+static int ipmi_run_command(struct ilom_ipmi *data, const char
*command_str)
+{
+ struct sunoem_cli_msg cli_req;
+ struct sunoem_cli_msg cli_rsp;
+ int command = SUNOEM_CLI_CMD_OPEN;
+ char *str_command = (char *) command_str;
+ int err;
+
+ /* Open IPMI */
+ do {
+ /* pick-up the latest value for the `interface' module parameter
+ * written into sysfs */
+ err = init_ipmi_data(data, ipmi_interface);
+ if (kthread_should_stop())
+ goto ipmi_run_end;
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+ } while (err);
+
+ /* Create an ilom shell session */
+ do {
+ err = sunoem_open_shell(data, command, &cli_req, &cli_rsp);
+ if (kthread_should_stop()) {
+ err = ILOM_IPMI_RUN_FAILURE;
+ goto ipmi_run_cleanup;
+ }
+ /* sunoem CLI could be denied because all session slots appear
+ * to be busy. Starting with Oracle ILOM 3.0.10 the FORCE
+ * command closes any currently running IPMI sunoem CLI session
+ * in favor of the new one that is being invoked. See:
+ *
https://docs.oracle.com/cd/E19860-01/E21549/z400054d1023430.html */
+ command = SUNOEM_CLI_CMD_FORCE;
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+ } while (err);
+
+ /* Remember the handle provided in the response, and issue a
+ * series of "poll" commands to send and get data */
+ memcpy(cli_req.handle, cli_rsp.handle, 4);
+ cli_req.command_response = SUNOEM_CLI_CMD_POLL;
+ do {
+ if (kthread_should_stop()) {
+ err = sunoem_close_shell(data, &cli_req, &cli_rsp);
+ if (err) {
+ pr_debug("ilom: unable to close ilom shell\n");
+ /* indicate that we haven't done all the work */
+ err = ILOM_IPMI_RUN_FAILURE;
+ goto ipmi_run_cleanup;
+ }
+ }
+ err = sunoem_send_recv(data, str_command, &cli_req, &cli_rsp);
+ if (err) {
+ pr_err("ilom: communication issue: %d\n", err);
+ ipmi_destroy_user(data->user);
+ err = ILOM_IPMI_RUN_FAILURE;
+ goto ipmi_run_cleanup;
+ }
+ cli_rsp.buf[sizeof(cli_rsp.buf)-1] = '\0';
+ if (*cli_rsp.buf) {
+ err = parse_show_output(cli_rsp.buf, populate_sysfs);
+ if (err)
+ pr_err("ilom: parse error %d\n", err);
+ }
+ if ((cli_req.command_response == SUNOEM_CLI_CMD_EOF) &&
+ cli_rsp.command_response)
+ break;
+
+ str_command = "";
+ cli_req.command_response = SUNOEM_CLI_CMD_EOF;
+ } while (!cli_rsp.command_response);
+
+ err = ILOM_IPMI_RUN_SUCCESS;
+
+ /* clean-up allocations */
+ parse_show_output(NULL, populate_sysfs);
+ if (cli_rsp.command_response) {
+ if (cli_rsp.command_response == SUNOEM_CLI_CMD_CLOSE) {
+ pr_err("ilom: sunoem cli session stolen!\n");
+ err = ILOM_IPMI_RUN_STOLEN;
+ } else if (cli_rsp.command_response != 1) {
+ pr_err("ilom: failed to retrieve data\n");
+ err = ILOM_IPMI_RUN_FAILURE;
+ }
+ }
+
+ipmi_run_cleanup:
+ ipmi_destroy_user(data->user);
+
+ipmi_run_end:
+ return err;
+}
+
+/*****************
+ * Kernel Thread *
+ *****************/
+
+struct ilom_data {
+ struct task_struct *kworker;
+ struct ilom_ipmi ipmi_data;
+};
+
+static struct ilom_data priv_data;
+
+/*
+ * The kernel thread named "ilom"
+ */
+static int thread_function(void *thread_data)
+{
+#ifdef PROFILING_PARAM
+ struct timespec start_time = {0};
+#endif
+ struct ilom_ipmi *ipmi = (struct ilom_ipmi *) thread_data;
+ const char *command_str = COMMAND_STR;
+ int err = 0;
+
+#if ILOM_PROFILING
+ start_time = current_kernel_time();
+#endif
+
+ /* calling ipmi_run_command() with the string to execute
+ * we allocate a kobject beforehand, then insert it in sysfs if the
+ * call succeeds.
+ * If the IPMI console is stolen we wait IPMI_STOLEN_RETRY_DELAY
+ * seconds, allowing for rmmod to remove the module during the delay
+ * Any other error is deemed serious so we don't retry */
+ while (!kthread_should_stop()) {
+
+ cleanup_existing_attributes(sysfs_attributes);
+ err = ipmi_run_command(ipmi, command_str);
+ if (!err) {
+ ilom_add_to_sysfs(sysfs_attributes);
+ break;
+ }
+
+ if (err == ILOM_IPMI_RUN_STOLEN) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(IPMI_STOLEN_RETRY_DELAY * HZ);
+ }
+ }
+
+#if ILOM_PROFILING
+ {
+ struct timespec end_time = current_kernel_time();
+ struct timespec diff = timespec_sub(end_time, start_time);
+
+ pr_info("ilom: took %lds to retrieve data from ILOM\n",
+ diff.tv_sec);
+ }
+#endif
+
+ /* As commented in kthread_stop(), the thread function can call
+ * do_exit(), as long at it ensures the task_struct doesn't go away.
+ * Basically only one call to get_task_struct() call occured (in
+ * kthread_create()) but both do_exit() and kthread_stop() call
+ * put_task_struct()
+ *
+ * So what we need to do to make sure the task_struct is still there
+ * when we'll be calling kthread_stop() at module removal is bumping
+ * the task_struct usage count by calling get_task_struct() */
+ get_task_struct(priv_data.kworker);
+ do_exit(err);
+}
+
+/***********************
+ * Module related code *
+ ***********************/
+
+MODULE_DESCRIPTION("Expose hardware properties of the ILOM to userspace");
+MODULE_AUTHOR("Oracle");
+MODULE_LICENSE("GPL");
+
+#if !IPMI_STOLEN_RETRY_DELAY
+#error "IPMI_STOLEN_RETRY_DELAY can't be zero!"
+#endif
+
+static int __init ilom_init(void)
+{
+ struct task_struct *task;
+
+ if (strlen(COMMAND_STR) > SUNOEM_CLI_BUF_SIZE-1) {
+ pr_err("ilom: shell command string too big\n");
+ return -EINVAL;
+ }
+
+ memset(&priv_data, 0, sizeof(priv_data));
+ task = kthread_create(&thread_function, (void *) &priv_data.ipmi_data,
+ "ilom");
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+
+ priv_data.kworker = task;
+ wake_up_process(task);
+
+ return 0;
+}
+
+static void __exit ilom_cleanup(void)