Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/814466/?format=api
{ "id": 814466, "url": "http://patchwork.ozlabs.org/api/1.2/patches/814466/?format=api", "web_url": "http://patchwork.ozlabs.org/project/sparclinux/patch/201709160505.v8G55wMU011701@aserv0021.oracle.com/", "project": { "id": 10, "url": "http://patchwork.ozlabs.org/api/1.2/projects/10/?format=api", "name": "Linux SPARC Development ", "link_name": "sparclinux", "list_id": "sparclinux.vger.kernel.org", "list_email": "sparclinux@vger.kernel.org", "web_url": null, "scm_url": null, "webscm_url": null, "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<201709160505.v8G55wMU011701@aserv0021.oracle.com>", "list_archive_url": null, "date": "2017-09-16T05:05:58", "name": "sparc64: Expose some h/w info from ILOM to userspace via sysfs", "commit_ref": null, "pull_url": null, "state": "changes-requested", "archived": false, "hash": "4809dc793a63c69a7c4b06c4aadf4e391197d362", "submitter": { "id": 70919, "url": "http://patchwork.ozlabs.org/api/1.2/people/70919/?format=api", "name": "Eric Saint Etienne", "email": "eric.saint.etienne@oracle.com" }, "delegate": { "id": 34, "url": "http://patchwork.ozlabs.org/api/1.2/users/34/?format=api", "username": "davem", "first_name": "David", "last_name": "Miller", "email": "davem@davemloft.net" }, "mbox": "http://patchwork.ozlabs.org/project/sparclinux/patch/201709160505.v8G55wMU011701@aserv0021.oracle.com/mbox/", "series": [ { "id": 3416, "url": "http://patchwork.ozlabs.org/api/1.2/series/3416/?format=api", "web_url": "http://patchwork.ozlabs.org/project/sparclinux/list/?series=3416", "date": "2017-09-16T05:05:58", "name": "sparc64: Expose some h/w info from ILOM to userspace via sysfs", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/3416/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/814466/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/814466/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<sparclinux-owner@vger.kernel.org>", "X-Original-To": "patchwork-incoming@ozlabs.org", "Delivered-To": "patchwork-incoming@ozlabs.org", "Authentication-Results": "ozlabs.org;\n\tspf=none (mailfrom) smtp.mailfrom=vger.kernel.org\n\t(client-ip=209.132.180.67; helo=vger.kernel.org;\n\tenvelope-from=sparclinux-owner@vger.kernel.org;\n\treceiver=<UNKNOWN>)", "Received": [ "from vger.kernel.org (vger.kernel.org [209.132.180.67])\n\tby ozlabs.org (Postfix) with ESMTP id 3xvKwr3Tj1z9sRV\n\tfor <patchwork-incoming@ozlabs.org>;\n\tSat, 16 Sep 2017 15:06:04 +1000 (AEST)", "(majordomo@vger.kernel.org) by vger.kernel.org via listexpand\n\tid S1750796AbdIPFGD (ORCPT <rfc822;patchwork-incoming@ozlabs.org>);\n\tSat, 16 Sep 2017 01:06:03 -0400", "from aserp1040.oracle.com ([141.146.126.69]:48237 \"EHLO\n\taserp1040.oracle.com\" rhost-flags-OK-OK-OK-OK) by vger.kernel.org\n\twith ESMTP id S1750779AbdIPFGC (ORCPT\n\t<rfc822; sparclinux@vger.kernel.org>); Sat, 16 Sep 2017 01:06:02 -0400", "from aserv0021.oracle.com (aserv0021.oracle.com [141.146.126.233])\n\tby aserp1040.oracle.com (Sentrion-MTA-4.3.2/Sentrion-MTA-4.3.2)\n\twith ESMTP id v8G560m7023370\n\t(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256\n\tverify=OK); Sat, 16 Sep 2017 05:06:00 GMT", "from [127.0.1.1]\n\t(dhcp-ukc1-twvpn-2-vpnpool-10-175-193-35.vpn.oracle.com\n\t[10.175.193.35])\n\tby aserv0021.oracle.com (8.14.4/8.14.4) with ESMTP id v8G55wMU011701; \n\tSat, 16 Sep 2017 05:05:59 GMT" ], "Date": "Sat, 16 Sep 2017 05:05:58 GMT", "Message-Id": "<201709160505.v8G55wMU011701@aserv0021.oracle.com>", "Content-Type": "text/plain; charset=\"us-ascii\"", "MIME-Version": "1.0", "Content-Transfer-Encoding": "7bit", "from": "Eric Saint Etienne <eric.saint.etienne@oracle.com>", "to": "\"David S . Miller\" <davem@davemloft.net>", "cc": "sparclinux@vger.kernel.org", "x-mailer": "git-send-email 1.7.1", "subject": "[PATCH] sparc64: Expose some h/w info from ILOM to userspace via\n\tsysfs", "X-Source-IP": "aserv0021.oracle.com [141.146.126.233]", "Sender": "sparclinux-owner@vger.kernel.org", "Precedence": "bulk", "List-ID": "<sparclinux.vger.kernel.org>", "X-Mailing-List": "sparclinux@vger.kernel.org" }, "content": "(resent due to improper mail agent formatting)\n\nTo get a snapshot as complete as possible of what the system is composed of,\nthis commit adds the ability to retrieve a set of hardware details that are only\navailable to the ILOM and export them via sysfs to userspace programs and\nutilities. Not all the ILOM properties are exposed, only a subset useful to\nsystem utilities.\n\nCommunication with the service processor of the ILOM is done via IPMI and VLDC.\nThis module opens a shell session on the service processor and runs a \"show -t\"\ntype of command. The output is then parsed and filtered for the right entries to\npopulate in one go sysfs at /sys/firmware/ilom\n\nIf no shell is available to the module, it will forcibly request one to the\nservice processor. If the shell it is using got stolen (for example by running\nipmitool while the module is fetching data from the ilom), the module will wait\n60s (changeable parameter) and resume from start (it's not possible to pick up\nwhere we previously were).\n\nWhen the ipmi interface is wrong, the module won't be able to connect to the\nservice processor. In this case it is possible to amend the interface module\nparamenter by writing in sysfs (/sys/module/ilom/parameters/interface) and\nthe module will use it straight away. When the module is built into the kernel\nthis should still work.\n\nThe \"ilom\" kernel thread terminates either when data is retrieved from the ILOM\nand sysfs populated or when the ilom module is unloaded.\n\nThis module must be configured and loaded as module due to a dependency on ipmi\nand ipmi_si modules which can't be built into the kernel at the moment.\nIf this changes in the future the ilom module should build and work when part of\nthe kernel as well.\n\nSigned-off-by: Eric Saint Etienne <eric.saint.etienne@oracle.com>\nSigned-off-by: Dave Aldridge <david.j.aldridge@oracle.com>\n---\n arch/sparc/Kconfig | 10 +\n arch/sparc/kernel/Makefile | 2 +\n arch/sparc/kernel/ilom.c | 923 ++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 935 insertions(+), 0 deletions(-)\n create mode 100644 arch/sparc/kernel/ilom.c", "diff": "diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig\nindex cac67aa..0059f07 100644\n--- a/arch/sparc/Kconfig\n+++ b/arch/sparc/Kconfig\n@@ -589,6 +589,16 @@ config SUN_OPENPROMFS\n \t Only choose N if you know in advance that you will not need to modify\n \t OpenPROM settings on the running system.\n \n+config ILOM_SYSFS\n+\ttristate \"ILOM support in sysfs\"\n+\tdepends on SPARC64 && SYSFS && IPMI_HANDLER && IPMI_SI\n+\tdefault y\n+\thelp\n+\t Say Y or M here to enable the exporting of some hardware related\n+\t data from the ILOM service processor via sysfs. This is useful\n+\t to get DIMM details or ROM versions. Exported data is available\n+\t under /sys/firmware/ilom when this option is enabled and loaded.\n+\n # Makefile helpers\n config SPARC64_PCI\n \tbool\ndiff --git a/arch/sparc/kernel/Makefile b/arch/sparc/kernel/Makefile\nindex 5e6463d..42005c8 100644\n--- a/arch/sparc/kernel/Makefile\n+++ b/arch/sparc/kernel/Makefile\n@@ -120,6 +120,8 @@ obj-$(CONFIG_COMPAT) += $(audit--y)\n pc--$(CONFIG_PERF_EVENTS) := perf_event.o\n obj-$(CONFIG_SPARC64)\t+= $(pc--y)\n \n+obj-$(CONFIG_ILOM_SYSFS) += ilom.o\n+\n obj-$(CONFIG_UPROBES)\t+= uprobes.o\n \n obj-$(CONFIG_SPARC64)\t+= jump_label.o\ndiff --git a/arch/sparc/kernel/ilom.c b/arch/sparc/kernel/ilom.c\nnew file mode 100644\nindex 0000000..9de1d44\n--- /dev/null\n+++ b/arch/sparc/kernel/ilom.c\n@@ -0,0 +1,923 @@\n+/*\n+ * ilom.c - Expose hardware properties of the ILOM to userspace\n+ *\n+ * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.\n+ *\n+ * This program is free software; you can redistribute it and/or\n+ * modify it under the terms of the GNU General Public License version\n+ * 2 as published by the Free Software Foundation.\n+ *\n+ * This program is distributed in the hope that it will be useful, but\n+ * WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ * General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with this program; see the file COPYING. If not, write to\n+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,\n+ * USA.\n+ *\n+ */\n+\n+#include <linux/module.h>\n+#include <linux/ratelimit.h>\n+#include <linux/kthread.h>\n+#include <linux/slab.h>\n+#include <linux/delay.h>\n+#include <linux/ipmi.h>\n+#include <linux/device.h>\n+#include <linux/sysfs.h>\n+\n+#define COMMAND_STR \"show -d properties -format nowrap -level all\\n\"\n+\n+/* The IPMI interface number can be changed while the module is loaded */\n+static unsigned int ipmi_interface;\n+module_param_named(interface, ipmi_interface, uint, S_IRUSR | S_IWUSR);\n+MODULE_PARM_DESC(interface, \"IPMI Interface number (default=0)\");\n+\n+/* maximum number of IPMI retries */\n+#define IPMI_RETRIES 3\n+\n+/* print on the console the time it took to retrieve data from ILOM */\n+#define ILOM_PROFILING 0\n+\n+/* Number of seconds to pause before attempting again, when the IPMI shell has\n+ * been forcibly stolen */\n+#define IPMI_STOLEN_RETRY_DELAY 60\n+\n+struct ilom_ipmi {\n+\tstruct ipmi_user_hndl\tipmi_hndlrs;\n+\n+\tstruct completion\tread_complete;\n+\tstruct ipmi_addr\taddress;\n+\tipmi_user_t\t\tuser;\n+\n+\tstruct kernel_ipmi_msg\ttx_message;\n+\tlong\t\t\ttx_msgid;\n+\n+\tvoid\t\t\t*rx_msg_data;\n+\tunsigned short\t\trx_msg_len;\n+\tunsigned char\t\trx_result;\n+\tint\t\t\trx_recv_type;\n+};\n+\n+/*\n+ * Dispatch IPMI messages to caller(s)\n+ */\n+static void msg_handler(struct ipmi_recv_msg *msg, void *user_msg_data)\n+{\n+\tunsigned short rx_len;\n+\tstruct ilom_ipmi *data = (struct ilom_ipmi *) user_msg_data;\n+\n+\tif (msg->msgid != data->tx_msgid) {\n+\t\tpr_err_ratelimited(\"ilom: mismatch between received msgid (%02lx) and transmitted msgid (%02lx)!\\n\",\n+\t\t\t\t msg->msgid, data->tx_msgid);\n+\t\tgoto handler_cleanup;\n+\t}\n+\n+\tdata->rx_recv_type = msg->recv_type;\n+\tif (msg->msg.data_len > 0) {\n+\t\tdata->rx_result = msg->msg.data[0];\n+\t\tif (msg->msg.data_len > 1) {\n+\t\t\trx_len = msg->msg.data_len - 1;\n+\t\t\tif (data->rx_msg_len < rx_len)\n+\t\t\t\trx_len = data->rx_msg_len;\n+\t\t\tdata->rx_msg_len = rx_len;\n+\t\t\tmemcpy(data->rx_msg_data, msg->msg.data + 1,\n+\t\t\t data->rx_msg_len);\n+\t\t} else\n+\t\t\tdata->rx_msg_len = 0;\n+\t} else {\n+\t\tdata->rx_result = IPMI_UNKNOWN_ERR_COMPLETION_CODE;\n+\t}\n+\tcomplete(&data->read_complete);\n+\n+handler_cleanup:\n+\tipmi_free_recv_msg(msg);\n+}\n+\n+/*\n+ * Initialize IPMI address, message buffers and user data\n+ */\n+static int init_ipmi_data(struct ilom_ipmi *data, int interface)\n+{\n+\tint err;\n+\n+\tdata->ipmi_hndlrs.ipmi_recv_hndl = msg_handler;\n+\n+\tinit_completion(&data->read_complete);\n+\n+\t/* Initialize IPMI address */\n+\tdata->address.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;\n+\tdata->address.channel = IPMI_BMC_CHANNEL;\n+\tdata->address.data[0] = 0;\n+\n+\t/* Initialize message buffers */\n+\tdata->tx_msgid = 0;\n+\n+\t/* Create IPMI messaging interface user */\n+\terr = ipmi_create_user(interface, &data->ipmi_hndlrs,\n+\t\t\t data, &data->user);\n+\tif (err < 0)\n+\t\tpr_err_once(\"ilom: unable to register user with IPMI interface %d\\n\",\n+\t\t\t interface);\n+\n+\treturn err;\n+}\n+\n+/*\n+ * Send an IPMI command\n+ */\n+static int send_message(struct ilom_ipmi *data)\n+{\n+\tint err;\n+\n+\terr = ipmi_validate_addr(&data->address, sizeof(data->address));\n+\tif (err) {\n+\t\tpr_debug(\"ilom: validate_addr=%x\\n\", err);\n+\t\treturn err;\n+\t}\n+\n+\tdata->tx_msgid++;\n+\n+\terr = ipmi_request_settime(data->user, &data->address, data->tx_msgid,\n+\t\t\t&data->tx_message, data, 0, 0, 0);\n+\tif (err) {\n+\t\tpr_debug(\"ilom: request_settime=%x\\n\", err);\n+\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/********************\n+ * SUNOEM IPMI Code *\n+ ********************/\n+\n+/* Arbitrary timeout in seconds to allow a low-level IPMI command to complete */\n+#define IPMI_TIMEOUT\t\t(10 * HZ)\n+\n+#define IPMI_CC_TIMEOUT\t\t0xc3 /* IPMI completion code */\n+\n+#define IPMI_NETFN_SUNOEM\t0x2e\n+#define IPMI_SUNOEM_CLI\t\t0x19\n+\n+#define SUNOEM_CLI_CMD_OPEN\t0 /* Open a new connection */\n+#define SUNOEM_CLI_CMD_FORCE\t1 /* Close any existing connection, then open */\n+#define SUNOEM_CLI_CMD_CLOSE\t2 /* Close the current connection */\n+#define SUNOEM_CLI_CMD_POLL\t3 /* Poll for new data to/from the server */\n+#define SUNOEM_CLI_CMD_EOF\t4 /* Poll, client is out of data */\n+\n+#define SUNOEM_CLI_LEGACY_VERSION\t1\n+#define SUNOEM_CLI_SEQNUM_VERSION\t2\n+#define SUNOEM_CLI_VERSION\t\tSUNOEM_CLI_SEQNUM_VERSION\n+#define SUNOEM_CLI_HEADER\t\t8 /* command + spare + handle */\n+#define SUNOEM_CLI_BUF_SIZE\t\t(80 - SUNOEM_CLI_HEADER) /* 80 bytes */\n+\n+#define SUNOEM_CLI_INVALID_VER_ERR\t\"Invalid version\"\n+#define SUNOEM_CLI_BUSY_ERR\t\t\"Busy\"\n+\n+struct sunoem_cli_msg {\n+\t/* Set version to SUNOEM_CLI_VERSION. */\n+\tuint8_t version;\n+\n+\t/* The command in a request or in a response indicates\n+\t * an error if != 0 */\n+\tuint8_t command_response;\n+\tuint8_t seqnum;\n+\tuint8_t spare;\n+\n+\t/* Opaque 4-byte handle, supplied in the response to an OPEN request,\n+\t * and used in all subsequent POLL and CLOSE requests */\n+\tuint8_t handle[4];\n+\n+\t/* The client data in a request, or the server data in a response. Must\n+\t * by null terminated, i.e., it must be at least one byte, but can be\n+\t * smaller if there's less data */\n+\tchar buf[SUNOEM_CLI_BUF_SIZE];\n+} __packed;\n+\n+static uint8_t sunoem_version = SUNOEM_CLI_VERSION;\n+static int sunoem_seqnum;\n+\n+static int sunoem_open_shell(struct ilom_ipmi *data, int command,\n+\t\t\t struct sunoem_cli_msg *cli_req,\n+\t\t\t struct sunoem_cli_msg *cli_rsp)\n+{\n+\tint err;\n+\n+\tmemset(cli_req, 0, sizeof(cli_req));\n+\tcli_req->command_response = command;\n+\tcli_req->seqnum = sunoem_seqnum;\n+\n+\tdata->tx_message.netfn = IPMI_NETFN_SUNOEM;\n+\tdata->tx_message.cmd = IPMI_SUNOEM_CLI;\n+\tdata->tx_message.data = (uint8_t *) cli_req;\n+\tdata->tx_message.data_len = SUNOEM_CLI_HEADER + 1;\n+\n+\tdata->rx_msg_data = cli_rsp;\n+\tdata->rx_msg_len = sizeof(*cli_rsp);\n+\n+\twhile (1) {\n+\t\tcli_req->version = sunoem_version;\n+\n+\t\terr = send_message(data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n+\t\terr = wait_for_completion_timeout(&data->read_complete,\n+\t\t\t\t\t\t IPMI_TIMEOUT);\n+\t\tif (!err)\n+\t\t\treturn -ETIMEDOUT;\n+\n+\t\tif (data->rx_result)\n+\t\t\treturn -ENOENT;\n+\n+\t\tif (!cli_rsp->command_response)\n+\t\t\tbreak;\n+\n+\t\t/* we're in an error condition */\n+\t\tif (!strncmp(cli_rsp->buf, SUNOEM_CLI_INVALID_VER_ERR, sizeof(SUNOEM_CLI_INVALID_VER_ERR)-1) ||\n+\t\t !strncmp(cli_rsp->buf+1, SUNOEM_CLI_INVALID_VER_ERR, sizeof(SUNOEM_CLI_INVALID_VER_ERR)-1)) {\n+\t\t\tif (sunoem_version == SUNOEM_CLI_VERSION) {\n+\t\t\t\t/* Server doesn't support version\n+\t\t\t\t SUNOEM_CLI_VERSION Fall back to\n+\t\t\t\t legacy version, and try again*/\n+\t\t\t\tsunoem_version = SUNOEM_CLI_LEGACY_VERSION;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* Server doesn't support legacy ver either */\n+\t\t\tpr_err(\"ilom: failed to connect: %*pE\\n\",\n+\t\t\t (int) strlen(cli_rsp->buf), cli_rsp->buf);\n+\t\t\treturn -ENODEV;\n+\t\t}\n+\n+\t\tif (!strncmp(cli_rsp->buf, SUNOEM_CLI_BUSY_ERR,\n+\t\t\t\t\tsizeof(SUNOEM_CLI_BUSY_ERR)-1))\n+\t\t\treturn -EBUSY;\n+\n+\t\tpr_err(\"ilom: failed to connect: %*pE\\n\", (int) strlen(cli_rsp->buf), cli_rsp->buf);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Bit 1 of seqnum is used as an alternating sequence number\n+\t * to allow a server that supports it to detect when a retry is being\n+\t * sent from the host IPMI driver. Typically when this occurs, the\n+\t * server's last response message would have been dropped. Once the\n+\t * server detects this condition, it will know that it should retry\n+\t * sending the response */\n+\tif (sunoem_version == SUNOEM_CLI_SEQNUM_VERSION)\n+\t\tsunoem_seqnum ^= 0x1;\n+\n+\treturn 0;\n+}\n+\n+static int sunoem_close_shell(struct ilom_ipmi *data,\n+\t\t\t struct sunoem_cli_msg *cli_req,\n+\t\t\t struct sunoem_cli_msg *cli_rsp)\n+{\n+\tint err;\n+\n+\tmemset(cli_req, 0, sizeof(cli_req));\n+\tcli_req->command_response = SUNOEM_CLI_CMD_CLOSE;\n+\tcli_req->seqnum = sunoem_seqnum;\n+\n+\tdata->tx_message.netfn = IPMI_NETFN_SUNOEM;\n+\tdata->tx_message.cmd = IPMI_SUNOEM_CLI;\n+\tdata->tx_message.data = (uint8_t *) cli_req;\n+\tdata->tx_message.data_len = SUNOEM_CLI_HEADER + 1;\n+\n+\tdata->rx_msg_data = cli_rsp;\n+\tdata->rx_msg_len = sizeof(*cli_rsp);\n+\n+\terr = send_message(data);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = wait_for_completion_timeout(&data->read_complete, IPMI_TIMEOUT);\n+\tif (!err)\n+\t\treturn -ETIMEDOUT;\n+\n+\tif (data->rx_result)\n+\t\treturn -ENOENT;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Sends the command onto the IPMI shell\n+ * Commands longer than (SUNOEM_CLI_BUF_SIZE-1) chars are not handled\n+ * Also collects a line fragment (console output)\n+ */\n+static int sunoem_send_recv(struct ilom_ipmi *data,\n+\t\t\t char *command_str,\n+\t\t\t struct sunoem_cli_msg *cli_req,\n+\t\t\t struct sunoem_cli_msg *cli_rsp)\n+{\n+\tint err, retries;\n+\n+\tstrcpy(cli_req->buf, command_str);\n+\tcli_req->version = sunoem_version;\n+\tcli_req->seqnum = sunoem_seqnum;\n+\n+\tdata->tx_message.netfn = IPMI_NETFN_SUNOEM;\n+\tdata->tx_message.cmd = IPMI_SUNOEM_CLI;\n+\tdata->tx_message.data = (uint8_t *) cli_req;\n+\tdata->tx_message.data_len = SUNOEM_CLI_HEADER + strlen(command_str) + 1;\n+\n+\tdata->rx_msg_data = cli_rsp;\n+\tdata->rx_msg_len = sizeof(*cli_rsp);\n+\n+\tretries = 0;\n+\n+\twhile (1) {\n+\t\terr = send_message(data);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n+\t\terr = wait_for_completion_timeout(&data->read_complete,\n+\t\t\t\t\t\t IPMI_TIMEOUT);\n+\t\tif (!err)\n+\t\t\treturn -ETIMEDOUT;\n+\n+\t\tif (data->rx_result == IPMI_CC_TIMEOUT) {\n+\t\t\tpr_debug(\"ilom: failed to poll: %*pE\",\n+\t\t\t (int) strlen(cli_rsp->buf), cli_rsp->buf);\n+\t\t\tif (retries++ < IPMI_RETRIES) {\n+\t\t\t\tpr_err(\"ilom: retrying\\n\");\n+\t\t\t\t__set_current_state(TASK_INTERRUPTIBLE);\n+\t\t\t\tschedule_timeout(2 * HZ);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\treturn -EBUSY;\n+\t\t}\n+\t\tbreak;\n+\t}\n+\n+\tif (sunoem_version == SUNOEM_CLI_SEQNUM_VERSION)\n+\t\tsunoem_seqnum ^= 0x1;\n+\tcli_req->seqnum = sunoem_seqnum;\n+\n+\treturn 0;\n+}\n+\n+/***********************\n+ * ILOM output parsing *\n+ ***********************/\n+\n+/*\n+ * Concatenate line to str, increasing allocation of *str if necessary\n+ */\n+static int concat_string(char **str, char *line, int *size)\n+{\n+\tsize_t len_str = strnlen(*str, *size);\n+\tsize_t len_total = len_str + strlen(line) + 1;\n+\n+\tif (len_total > *size) {\n+\t\tchar *p = krealloc(*str, len_total, GFP_KERNEL);\n+\n+\t\tif (!p)\n+\t\t\treturn -ENOMEM;\n+\t\t*str = p;\n+\t}\n+\tstrcpy(*str + len_str, line);\n+\t*size = len_total;\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Takes a line fragment and fills in a full line (if such has been found)\n+ *\n+ * line: The line fragment to parse\n+ * line_is_valid: Indicates if line has a valid content\n+ * (which can be NULL if we want to clean-up)\n+ * parsed: If a full line has been found, it will be made available\n+ * in parsed (to be kfreed by the caller)\n+ *\n+ * Return 0 or and error code\n+ */\n+static int get_next_line(char *line, int line_is_valid, char **parsed)\n+{\n+\tstatic char *current_str;\n+\tstatic int current_size;\n+\tchar *p;\n+\n+\tif (line_is_valid) {\n+\t\tint err;\n+\n+\t\t/* Clean and kfree things up */\n+\t\tif (!line) {\n+\t\t\tif (current_str) {\n+\t\t\t\tkfree(current_str);\n+\t\t\t\tcurrent_str = NULL;\n+\t\t\t\tcurrent_size = 0;\n+\t\t\t}\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\t/* Concatenate line to current_str */\n+\t\terr = concat_string(¤t_str, line, ¤t_size);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\t/* Consume current_str */\n+\n+\t*parsed = NULL;\n+\n+\t/* Ensure first that we've got something to consume */\n+\tif (!current_str)\n+\t\treturn 0;\n+\n+\t/* Find the 1st carriage return\n+\t * and split strings, using a new allocation */\n+\tp = strnchr(current_str, current_size, '\\n');\n+\tif (p) {\n+\t\tint position = p - current_str;\n+\t\tint size_left = current_size - position - 1;\n+\t\tchar *new = kmalloc(size_left, GFP_KERNEL);\n+\n+\t\tif (!new)\n+\t\t\treturn -ENOMEM;\n+\n+\t\t*p++ = '\\0';\n+\t\tmemcpy(new, p, size_left);\n+\t\t*parsed = current_str;\n+\t\tcurrent_str = new;\n+\t\tcurrent_size = size_left;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Take a line fragment and calls cb() for every property it finds.\n+ * The arguments given too cb() are not owned by the callback.\n+ *\n+ * Return 0 if parsing went okay, or an error otherwise\n+ */\n+static int parse_show_output(char *line, void (*cb)(char*, char*, char*))\n+{\n+\tstatic int in_properties;\n+\tstatic char *key;\n+\tint line_is_valid = 1;\n+\tchar *parsed = NULL;\n+\n+\tdo {\n+\t\tint result;\n+\t\tchar *p;\n+\n+\t\tresult = get_next_line(line, line_is_valid, &parsed);\n+\n+\t\tif (result) {\n+\t\t\tpr_debug(\"ilom: an error occured while parsing %*pE\\n\",\n+\t\t\t\t (int) strlen(parsed), parsed);\n+\t\t\tget_next_line(NULL, 1, NULL); /* cleanup */\n+\t\t\treturn result;\n+\t\t}\n+\n+\t\tline_is_valid = 0;\n+\n+\t\tif (!parsed)\n+\t\t\tcontinue;\n+\n+\t\tif (*parsed == ' ') {\n+\t\t\t/* skip leading spaces */\n+\t\t\tfor (p = parsed; *p == ' '; )\n+\t\t\t\tp++;\n+\n+\t\t\tif (*p == '/') {\n+\t\t\t\tkey = kstrdup(p, GFP_KERNEL);\n+\t\t\t\tin_properties = 1;\n+\t\t\t} else if (!strncmp(p, \"Properties\", 10)) {\n+\t\t\t\t/* discard */\n+\t\t\t} else {\n+\t\t\t\tif (in_properties && (key)) {\n+\t\t\t\t\tchar *sep = strstr(p, \" = \");\n+\n+\t\t\t\t\tif (sep) {\n+\t\t\t\t\t\t*sep = '\\0';\n+\t\t\t\t\t\tcb(key, p, sep + 3);\n+\t\t\t\t\t\t/* above: 3 = strlen(\" = \") */\n+\t\t\t\t\t}\n+\t\t\t\t} else {\n+\t\t\t\t\tpr_debug(\"ilom: property in invalid block: %s\\n\", p);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} else if (*parsed == '\\0') {\n+\t\t\tin_properties = 0;\n+\t\t\tif (key) {\n+\t\t\t\tkfree(key);\n+\t\t\t\tkey = NULL;\n+\t\t\t}\n+\t\t} else if (!strcmp(parsed, \"Disconnected\")) {\n+\t\t\t/* end of stream marker\n+\t\t\t * nothing to do, really */\n+\t\t} else if (!strncmp(parsed, \"-> \", 3)) {\n+\t\t\t/* discard */\n+\t\t} else {\n+\t\t\tpr_debug(\"ilom: unparsed: %*pE\\n\",\n+\t\t\t (int) strlen(parsed), parsed);\n+\t\t}\n+\t\tkfree(parsed);\n+\t} while (parsed);\n+\n+\treturn 0;\n+}\n+\n+/**************\n+ * Sysfs Code *\n+ **************/\n+\n+struct sysfs_property {\n+\tstruct kobj_attribute kattr;\n+\tchar *value;\n+};\n+\n+ssize_t ilom_sysfs_show(struct kobject *object,\n+\t\t\tstruct kobj_attribute *attr, char *buf)\n+{\n+\tstruct sysfs_property *prop;\n+\n+\tprop = container_of(attr, struct sysfs_property, kattr);\n+\treturn sprintf(buf, \"%s\\n\", prop->value);\n+}\n+\n+struct kobject *ilom_kobj;\n+static struct attribute **sysfs_attributes;\n+static int sysfs_attributes_length;\n+\n+/*\n+ * Append a property to the list that will be later\n+ * passed to sysfs_create_files()\n+ */\n+static int add_property(struct sysfs_property *prop)\n+{\n+\tstatic int size; /* how much we've allocated */\n+\tstruct attribute **p;\n+\n+\tif (size <= (sysfs_attributes_length + 1)) {\n+\t\tsize += 16; /* pre-allocation to relieve the allocator */\n+\t\tp = krealloc(sysfs_attributes, size * sizeof(*sysfs_attributes),\n+\t\t\t GFP_KERNEL);\n+\t\tif (!p)\n+\t\t\treturn -ENOMEM;\n+\n+\t\tsysfs_attributes = p;\n+\t}\n+\tsysfs_attributes[sysfs_attributes_length++] = &prop->kattr.attr;\n+\t/* the array must be NULL terminated */\n+\tsysfs_attributes[sysfs_attributes_length] = NULL;\n+\treturn 0;\n+}\n+\n+/*\n+ * Helper function to make populate_sysfs() more readable\n+ * returns true if target starts with prefix\n+ */\n+static inline bool starts_with(char *target, char *prefix)\n+{\n+\treturn !strncmp(target, prefix, strlen(prefix));\n+}\n+\n+/*\n+ * Helper function to make populate_sysfs() more readable\n+ * returns true if s1 and s2 are equal\n+ */\n+static inline bool equals(char *s1, char *s2)\n+{\n+\treturn !strcmp(s1, s2);\n+}\n+\n+/*\n+ * Helper function to make populate_sysfs() more readable\n+ * return true if haystack contains needle\n+ */\n+static inline bool contains(char *haystack, char *needle)\n+{\n+\treturn strstr(haystack, needle) != NULL;\n+}\n+\n+/*\n+ * Match interesting entries and add them to sysfs_attributes\n+ */\n+void populate_sysfs(char *key, char *property, char *value)\n+{\n+\t/* filter the keys we're interested in */\n+\tif (starts_with(key, \"/System/Memory/DIMMs/DIMM_\") ||\n+\t starts_with(key, \"/System/Firmware/Other_Firmware/Firmware_\") ||\n+\t starts_with(key, \"/System/Processors/CPUs/CPU_\") ||\n+\t equals(key, \"/System\") ||\n+\t equals(key, \"/SYS\") ||\n+\t equals(key, \"/SYS/MB\") ||\n+\t equals(key, \"/SYS/MB/SP\") ||\n+\t equals(key, \"/SYS/MB_ENV\"))\n+\t{\n+\t\tstruct sysfs_property *prop;\n+\t\tchar *sprop = NULL;\n+\n+\t\t/* filter-out sensor type properties */\n+\t\tif (starts_with(property, \"actual_\") ||\n+\t\t starts_with(property, \"health\") ||\n+\t\t equals(property, \"action\") ||\n+\t\t equals(property, \"temperature\") ||\n+\t\t contains(property, \"fault\"))\n+\t\t\treturn;\n+\n+\t\tprop = kmalloc(sizeof(*prop), GFP_KERNEL);\n+\t\tif (prop)\n+\t\t\tsprop = kmalloc(strlen(key) + strlen(property) + 1,\n+\t\t\t\t\tGFP_KERNEL);\n+\t\tif (!sprop) {\n+\t\t\tkfree(prop);\n+\t\t\treturn;\n+\t\t}\n+\n+\t\t/* Set the name */\n+\t\tprop->kattr.attr.name = sprop;\n+\t\tfor (key++; *key; key++)\n+\t\t\t*sprop++ = (*key == '/' ? '-' : *key);\n+\t\t*sprop++ = '-'; /* no worries there's enough space */\n+\t\tstrcpy(sprop, property);\n+\n+\t\tprop->kattr.attr.mode = VERIFY_OCTAL_PERMISSIONS(S_IRUSR);\n+\t\tprop->kattr.show = ilom_sysfs_show;\n+\t\tprop->kattr.store = NULL;\n+\t\tprop->value = kstrdup(value, GFP_KERNEL);\n+\n+\t\tadd_property(prop);\n+\t}\n+}\n+\n+/*\n+ * Create /sys/firmware/ilom and adds to it the attributes that we've been\n+ * collecting while parsing the ILOM shell output.\n+ * the argument passed is effectively sysfs_attributes\n+ */\n+void ilom_add_to_sysfs(struct attribute **attributes)\n+{\n+\tconst struct attribute **pattr = (const struct attribute **) attributes;\n+\tint err;\n+\n+\tilom_kobj = kobject_create_and_add(\"ilom\", firmware_kobj);\n+\tif (!ilom_kobj) {\n+\t\tpr_err(\"ilom: unable to create sysfs entry\\n\");\n+\t\treturn;\n+\t}\n+\tif (attributes) {\n+\t\terr = sysfs_create_files(ilom_kobj, pattr);\n+\t\tif (err) {\n+\t\t\tpr_err(\"ilom: unable to create sysfs properties\\n\");\n+\t\t\tkobject_put(ilom_kobj);\n+\t\t}\n+\t}\n+}\n+\n+/*\n+ * Called when the IPMI shell has been interrupted, either consecutively to the\n+ * ipmi shell being stolen or when we quit the control loop (because the module\n+ * is unloaded or the IPMI layer returned an error\n+ */\n+static void cleanup_existing_attributes(struct attribute **attributes)\n+{\n+\tstruct attribute **attr;\n+\n+\tif (!sysfs_attributes)\n+\t\treturn;\n+\n+\tfor (attr = attributes; *attr; attr++) {\n+\t\tstruct sysfs_property *prop;\n+\n+\t\tprop = container_of(*attr, struct sysfs_property, kattr.attr);\n+\t\tkfree(prop);\n+\t\tsysfs_attributes_length--;\n+\t}\n+\tWARN_ON(sysfs_attributes_length != 0);\n+}\n+\n+/*********************\n+ * Top-level routine *\n+ *********************/\n+\n+#define ILOM_IPMI_RUN_SUCCESS\t0\n+#define ILOM_IPMI_RUN_STOLEN\t-1\n+#define ILOM_IPMI_RUN_FAILURE\t-2\n+\n+/*\n+ * Run a command on the ilom (must be a show type of command)\n+ * Collects and parse the output, creating an array of sysfs attribute objects\n+ * suitable for sysfs_create_files()\n+ */\n+static int ipmi_run_command(struct ilom_ipmi *data, const char *command_str)\n+{\n+\tstruct sunoem_cli_msg cli_req;\n+\tstruct sunoem_cli_msg cli_rsp;\n+\tint command = SUNOEM_CLI_CMD_OPEN;\n+\tchar *str_command = (char *) command_str;\n+\tint err;\n+\n+\t/* Open IPMI */\n+\tdo {\n+\t\t/* pick-up the latest value for the `interface' module parameter\n+\t\t * written into sysfs */\n+\t\terr = init_ipmi_data(data, ipmi_interface);\n+\t\tif (kthread_should_stop())\n+\t\t\tgoto ipmi_run_end;\n+\t\t__set_current_state(TASK_INTERRUPTIBLE);\n+\t\tschedule_timeout(HZ);\n+\t} while (err);\n+\n+\t/* Create an ilom shell session */\n+\tdo {\n+\t\terr = sunoem_open_shell(data, command, &cli_req, &cli_rsp);\n+\t\tif (kthread_should_stop()) {\n+\t\t\terr = ILOM_IPMI_RUN_FAILURE;\n+\t\t\tgoto ipmi_run_cleanup;\n+\t\t}\n+\t\t/* sunoem CLI could be denied because all session slots appear\n+\t\t * to be busy. Starting with Oracle ILOM 3.0.10 the FORCE\n+\t\t * command closes any currently running IPMI sunoem CLI session\n+\t\t * in favor of the new one that is being invoked. See:\n+\t\t * https://docs.oracle.com/cd/E19860-01/E21549/z400054d1023430.html */\n+\t\tcommand = SUNOEM_CLI_CMD_FORCE;\n+\t\t__set_current_state(TASK_INTERRUPTIBLE);\n+\t\tschedule_timeout(HZ);\n+\t} while (err);\n+\n+\t/* Remember the handle provided in the response, and issue a\n+\t * series of \"poll\" commands to send and get data */\n+\tmemcpy(cli_req.handle, cli_rsp.handle, 4);\n+\tcli_req.command_response = SUNOEM_CLI_CMD_POLL;\n+\tdo {\n+\t\tif (kthread_should_stop()) {\n+\t\t\terr = sunoem_close_shell(data, &cli_req, &cli_rsp);\n+\t\t\tif (err) {\n+\t\t\t\tpr_debug(\"ilom: unable to close ilom shell\\n\");\n+\t\t\t\t/* indicate that we haven't done all the work */\n+\t\t\t\terr = ILOM_IPMI_RUN_FAILURE;\n+\t\t\t\tgoto ipmi_run_cleanup;\n+\t\t\t}\n+\t\t}\n+\t\terr = sunoem_send_recv(data, str_command, &cli_req, &cli_rsp);\n+\t\tif (err) {\n+\t\t\tpr_err(\"ilom: communication issue: %d\\n\", err);\n+\t\t\tipmi_destroy_user(data->user);\n+\t\t\terr = ILOM_IPMI_RUN_FAILURE;\n+\t\t\tgoto ipmi_run_cleanup;\n+\t\t}\n+\t\tcli_rsp.buf[sizeof(cli_rsp.buf)-1] = '\\0';\n+\t\tif (*cli_rsp.buf) {\n+\t\t\terr = parse_show_output(cli_rsp.buf, populate_sysfs);\n+\t\t\tif (err)\n+\t\t\t\tpr_err(\"ilom: parse error %d\\n\", err);\n+\t\t}\n+\t\tif ((cli_req.command_response == SUNOEM_CLI_CMD_EOF) &&\n+\t\t cli_rsp.command_response)\n+\t\t\tbreak;\n+\n+\t\tstr_command = \"\";\n+\t\tcli_req.command_response = SUNOEM_CLI_CMD_EOF;\n+\t} while (!cli_rsp.command_response);\n+\n+\terr = ILOM_IPMI_RUN_SUCCESS;\n+\n+\t/* clean-up allocations */\n+\tparse_show_output(NULL, populate_sysfs);\n+\tif (cli_rsp.command_response) {\n+\t\tif (cli_rsp.command_response == SUNOEM_CLI_CMD_CLOSE) {\n+\t\t\tpr_err(\"ilom: sunoem cli session stolen!\\n\");\n+\t\t\terr = ILOM_IPMI_RUN_STOLEN;\n+\t\t} else if (cli_rsp.command_response != 1) {\n+\t\t\tpr_err(\"ilom: failed to retrieve data\\n\");\n+\t\t\terr = ILOM_IPMI_RUN_FAILURE;\n+\t\t}\n+\t}\n+\n+ipmi_run_cleanup:\n+\tipmi_destroy_user(data->user);\n+\n+ipmi_run_end:\n+\treturn err;\n+}\n+\n+/*****************\n+ * Kernel Thread *\n+ *****************/\n+\n+struct ilom_data {\n+\tstruct task_struct *kworker;\n+\tstruct ilom_ipmi ipmi_data;\n+};\n+\n+static struct ilom_data priv_data;\n+\n+/*\n+ * The kernel thread named \"ilom\"\n+ */\n+static int thread_function(void *thread_data)\n+{\n+#ifdef PROFILING_PARAM\n+\tstruct timespec start_time = {0};\n+#endif\n+\tstruct ilom_ipmi *ipmi = (struct ilom_ipmi *) thread_data;\n+\tconst char *command_str = COMMAND_STR;\n+\tint err = 0;\n+\n+#if ILOM_PROFILING\n+\tstart_time = current_kernel_time();\n+#endif\n+\n+\t/* calling ipmi_run_command() with the string to execute\n+\t * we allocate a kobject beforehand, then insert it in sysfs if the\n+\t * call succeeds.\n+\t * If the IPMI console is stolen we wait IPMI_STOLEN_RETRY_DELAY\n+\t * seconds, allowing for rmmod to remove the module during the delay\n+\t * Any other error is deemed serious so we don't retry */\n+\twhile (!kthread_should_stop()) {\n+\n+\t\tcleanup_existing_attributes(sysfs_attributes);\n+\t\terr = ipmi_run_command(ipmi, command_str);\n+\t\tif (!err) {\n+\t\t\tilom_add_to_sysfs(sysfs_attributes);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (err == ILOM_IPMI_RUN_STOLEN) {\n+\t\t\t__set_current_state(TASK_INTERRUPTIBLE);\n+\t\t\tschedule_timeout(IPMI_STOLEN_RETRY_DELAY * HZ);\n+\t\t}\n+\t}\n+\n+#if ILOM_PROFILING\n+\t{\n+\t\tstruct timespec end_time = current_kernel_time();\n+\t\tstruct timespec diff = timespec_sub(end_time, start_time);\n+\n+\t\tpr_info(\"ilom: took %lds to retrieve data from ILOM\\n\",\n+\t\t diff.tv_sec);\n+\t}\n+#endif\n+\n+\t/* As commented in kthread_stop(), the thread function can call\n+\t * do_exit(), as long at it ensures the task_struct doesn't go away.\n+\t * Basically only one call to get_task_struct() call occured (in\n+\t * kthread_create()) but both do_exit() and kthread_stop() call\n+\t * put_task_struct()\n+\t *\n+\t * So what we need to do to make sure the task_struct is still there\n+\t * when we'll be calling kthread_stop() at module removal is bumping\n+\t * the task_struct usage count by calling get_task_struct() */\n+\tget_task_struct(priv_data.kworker);\n+\tdo_exit(err);\n+}\n+\n+/***********************\n+ * Module related code *\n+ ***********************/\n+\n+MODULE_DESCRIPTION(\"Expose hardware properties of the ILOM to userspace\");\n+MODULE_AUTHOR(\"Oracle\");\n+MODULE_LICENSE(\"GPL\");\n+\n+#if !IPMI_STOLEN_RETRY_DELAY\n+#error \"IPMI_STOLEN_RETRY_DELAY can't be zero!\"\n+#endif\n+\n+static int __init ilom_init(void)\n+{\n+\tstruct task_struct *task;\n+\n+\tif (strlen(COMMAND_STR) > SUNOEM_CLI_BUF_SIZE-1) {\n+\t\tpr_err(\"ilom: shell command string too big\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmemset(&priv_data, 0, sizeof(priv_data));\n+\ttask = kthread_create(&thread_function, (void *) &priv_data.ipmi_data,\n+\t\t\t \"ilom\");\n+\tif (IS_ERR(task))\n+\t\treturn PTR_ERR(task);\n+\n+\tpriv_data.kworker = task;\n+\twake_up_process(task);\n+\n+\treturn 0;\n+}\n+\n+static void __exit ilom_cleanup(void)\n+{\n+\tkthread_stop(priv_data.kworker);\n+\tif (ilom_kobj) {\n+\t\tkobject_del(ilom_kobj);\n+\t\tkobject_put(ilom_kobj);\n+\t}\n+\n+\tif (sysfs_attributes) {\n+\t\tcleanup_existing_attributes(sysfs_attributes);\n+\t\tkfree(sysfs_attributes);\n+\t}\n+}\n+\n+module_init(ilom_init);\n+module_exit(ilom_cleanup);\n", "prefixes": [] }