Add SSIF Bridge Daemon
This commit adds SSIF bridge support adapted from BT Bridge (btbridged).
This daemon is standing between BMC SSIF kernel driver and DBUS IPMI
Interface for sending IPMI requests and return responses.
Signed-off-by: Thang Q. Nguyen <thang@os.amperecomputing.com>
Signed-off-by: Narayan Chavani <nchavani@amperecomputing.com>
Signed-off-by: Chuong Tran <chuong.tran@amperecomputing.com>
Change-Id: Ie445bce125658aafa1cb42e2165439ea3a08ef41
diff --git a/ssifbridged.c b/ssifbridged.c
new file mode 100644
index 0000000..305eec7
--- /dev/null
+++ b/ssifbridged.c
@@ -0,0 +1,713 @@
+/*
+ * Copyright (c) 2018-2020 Ampere Computing LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This is a daemon that forwards requests and receive responses from SSIF over
+ * the D-Bus IPMI Interface.
+ *
+ * This daemon is derived from btbridged from https://github.com/openbmc/btbridge.
+ * There is no need to queue messages for SSIF since they should not arrive out
+ * of sequence.
+ * The timer is used as we want to abort 'stuck' commands after an expiry time.
+ * Messages that are received out of order are discarded.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/mman.h>
+#include <linux/ioctl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <time.h>
+#include <unistd.h>
+#include <systemd/sd-bus.h>
+
+static const char *ssif_bmc_device = "/dev/ipmi-ssif-host";
+
+#define PREFIX "SSIF_BRIDGED"
+
+#define SSIF_BMC_PATH ssif_bmc_device
+#define SSIF_BMC_TIMEOUT_SEC 2
+#define SSIF_MAX_REQ_LEN 254
+#define SSIF_MAX_RESP_LEN 254
+
+/* Completion code specifies a command times out */
+#define IPMI_CC_CANNOT_PROVIDE_RESP 0xce
+
+
+#define DBUS_NAME "org.openbmc.HostIpmi"
+#define OBJ_NAME "/org/openbmc/HostIpmi/1"
+
+#define SD_BUS_FD 0
+#define SSIF_FD 1
+#define TIMER_FD 2
+#define TOTAL_FDS 3
+
+#define MSG_OUT(f_, ...) do { \
+ if (verbosity != SSIF_LOG_NONE) { \
+ ssif_log(LOG_INFO, f_, ##__VA_ARGS__); \
+ } \
+} while(0)
+#define MSG_ERR(f_, ...) do { \
+ if (verbosity != SSIF_LOG_NONE) { \
+ ssif_log(LOG_ERR, f_, ##__VA_ARGS__); \
+ } \
+} while(0)
+
+struct ipmi_msg {
+ uint8_t netfn;
+ uint8_t lun;
+ uint8_t seq;
+ uint8_t cmd;
+ uint8_t cc; /* Only used on responses */
+ uint8_t *data;
+ size_t data_len;
+};
+
+struct ssifbridged_context {
+ /* The file descriptors to poll */
+ struct pollfd fds[TOTAL_FDS];
+
+ /* Pointer to sdbus */
+ struct sd_bus *bus;
+
+ /* Tracking variable for a pending message so that if it times out,
+ * we can send a response using the correct lun, netfn and command to
+ * indicate to the host that we timed out waiting for a response
+ */
+ struct ipmi_msg ssif_pending_msg;
+
+ /* Flag to indicate whether we are awaiting a response */
+ int awaiting_response;
+};
+
+static void (*ssif_vlog)(int p, const char *fmt, va_list args);
+static int running = 1;
+static enum {
+ SSIF_LOG_NONE = 0,
+ SSIF_LOG_VERBOSE,
+ SSIF_LOG_DEBUG
+} verbosity;
+
+static void ssif_log_console(int p, const char *fmt, va_list args)
+{
+ struct timespec time;
+ FILE *s = (p < LOG_WARNING) ? stdout : stderr;
+
+ clock_gettime(CLOCK_REALTIME, &time);
+
+ fprintf(s, "[%s %ld.%.9ld] ", PREFIX, time.tv_sec, time.tv_nsec);
+
+ vfprintf(s, fmt, args);
+}
+
+ __attribute__((format(printf, 2, 3)))
+static void ssif_log(int p, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ ssif_vlog(p, fmt, args);
+ va_end(args);
+}
+
+static struct ipmi_msg *ssif_msg_create(struct ssifbridged_context *context,
+ uint8_t *ssif_data)
+{
+ int len;
+
+ assert(context && ssif_data);
+
+ /*
+ * len here is the length of the array.
+ * Helpfully SSIF doesn't count the length byte
+ */
+ len = ssif_data[0] + 1;
+
+ if (len < 3) {
+ MSG_ERR("Trying to get SSIF message with a short length (%d)\n",
+ len);
+ return NULL;
+ }
+
+ MSG_OUT("Trying to get SSIF message with len (%d)\n", len);
+
+ /* Don't count the lenfn/ln, seq and command */
+ context->ssif_pending_msg.data_len = len - 3;
+ context->ssif_pending_msg.netfn = ssif_data[1] >> 2;
+ context->ssif_pending_msg.lun = ssif_data[1] & 0x3;
+ /* Force sequence field = 0 for SSIF */
+ context->ssif_pending_msg.seq = 0;
+ context->ssif_pending_msg.cmd = ssif_data[2];
+
+ return &context->ssif_pending_msg;
+}
+
+/*
+ * Send request from the SSIF driver
+ */
+static int send_received_message_signal(struct ssifbridged_context *context,
+ struct ipmi_msg *req,
+ uint8_t *data, uint8_t data_len)
+{
+ sd_bus_message *msg = NULL;
+ int r = 0;
+
+ /* Notify sdbus for incoming message */
+ r = sd_bus_message_new_signal(context->bus,
+ &msg,
+ OBJ_NAME,
+ DBUS_NAME,
+ "ReceivedMessage");
+ if (r < 0) {
+ MSG_ERR("Failed to create signal: %s\n", strerror(-r));
+ return r;
+ }
+
+ r = sd_bus_message_append(msg, "yyyy",
+ req->seq,
+ req->netfn,
+ req->lun,
+ req->cmd);
+ if (r < 0) {
+ MSG_ERR("Couldn't append to signal: %s\n", strerror(-r));
+ return r;
+ }
+
+ r = sd_bus_message_append_array(msg, 'y', data, data_len);
+ if (r < 0) {
+ MSG_ERR("Couldn't append array to signal: %s\n", strerror(-r));
+ sd_bus_message_unref(msg);
+ return r;
+ }
+
+ MSG_OUT("Sending dbus signal with seq 0x%02x, netfn 0x%02x, "
+ "lun 0x%02x, cmd 0x%02x\n",
+ req->seq,
+ req->netfn,
+ req->lun,
+ req->cmd);
+
+ if (verbosity == SSIF_LOG_DEBUG) {
+ int i;
+ for (i = 0; i < req->data_len; i++) {
+ if (i % 8 == 0) {
+ if (i)
+ printf("\n");
+ MSG_OUT("\t");
+ }
+ printf("0x%02x ", data[i + 3]);
+ }
+ if (req->data_len)
+ printf("\n");
+ }
+
+ r = sd_bus_send(context->bus, msg, NULL);
+ if (r < 0) {
+ MSG_ERR("Couldn't emit dbus signal: %s\n", strerror(-r));
+ return r;
+ }
+
+ sd_bus_message_unref(msg);
+
+ return r;
+}
+
+/*
+ * Send a response on the SSIF driver
+ */
+static int ssif_send_response(struct ssifbridged_context *context,
+ struct ipmi_msg *ssif_resp_msg)
+{
+ uint8_t data[SSIF_MAX_RESP_LEN] = { 0 };
+ int r = 0;
+ int len = 0;
+
+ assert(context);
+
+ if (!ssif_resp_msg)
+ return -EINVAL;
+
+ /* netfn/lun + cmd + cc = 3 */
+ data[0] = ssif_resp_msg->data_len + 3;
+
+ /* Copy response message to data buffer */
+ if (ssif_resp_msg->data_len)
+ memcpy(data + 4, ssif_resp_msg->data, ssif_resp_msg->data_len);
+
+ /* Prepare Header */
+ data[1] = (ssif_resp_msg->netfn << 2) |
+ (ssif_resp_msg->lun & 0x3);
+ data[2] = ssif_resp_msg->cmd;
+ data[3] = ssif_resp_msg->cc;
+ if (ssif_resp_msg->data_len > sizeof(data) - 4) {
+ MSG_ERR("Response message size (%zu) too big, truncating\n",
+ ssif_resp_msg->data_len);
+ ssif_resp_msg->data_len = sizeof(data) - 4;
+ }
+
+ /* Write data kernel space via system calls */
+ len = write(context->fds[SSIF_FD].fd, data, data[0] + 1);
+
+ if (len < 0) {
+ MSG_ERR("Failed to write to driver (ret: %d, errno: %d)\n",
+ len,
+ errno);
+ r = -errno;
+ } else if (len != data[0] + 1) {
+ MSG_ERR("Possible short write to %s, desired len: %d, "
+ "written len: %d\n",
+ SSIF_BMC_PATH,
+ data[0] + 1,
+ len);
+ r = -EINVAL;
+ } else {
+ MSG_OUT("Successfully wrote %d of %d bytes to %s\n",
+ len,
+ data[0] + 1,
+ SSIF_BMC_PATH);
+ }
+
+ return r;
+}
+
+static int method_send_message(sd_bus_message *msg,
+ void *userdata,
+ sd_bus_error *ret_error)
+{
+ struct ssifbridged_context *context;
+ sd_bus_message* resp_msg = NULL;
+ struct ipmi_msg ssif_resp_msg;
+ int r = 1;
+
+ context = (struct ssifbridged_context *)userdata;
+ if (!context) {
+ sd_bus_error_set_const(ret_error,
+ "org.openbmc.error",
+ "Internal error");
+ return -EINVAL;
+ }
+
+ r = sd_bus_message_new_method_return(msg, &resp_msg);
+ if (r < 0) {
+ MSG_ERR("Failed to create method response (ret: %d)\n", r);
+ return r;
+ }
+
+ if (!context->awaiting_response) {
+ /* We are not expecting a response at this time */
+ MSG_ERR("Response message received when in wrong state. "
+ "Discarding\n");
+ r = -EBUSY;
+ } else {
+ uint8_t *data;
+ size_t data_sz;
+ uint8_t netfn, lun, seq, cmd, cc;
+ struct itimerspec ts;
+
+ context->awaiting_response = 0;
+
+ r = sd_bus_message_read(msg, "yyyyy",
+ &seq,
+ &netfn,
+ &lun,
+ &cmd,
+ &cc);
+ if (r < 0) {
+ MSG_ERR("Couldn't parse leading bytes of message: %s\n",
+ strerror(-r));
+ sd_bus_error_set_const(ret_error,
+ "org.openbmc.error",
+ "Bad message");
+ r = -EINVAL;
+ goto done;
+ }
+ r = sd_bus_message_read_array(msg, 'y',
+ (const void **)&data,
+ &data_sz);
+ if (r < 0) {
+ MSG_ERR("Couldn't parse data bytes of message: %s\n",
+ strerror(-r));
+ sd_bus_error_set_const(ret_error,
+ "org.openbmc.error",
+ "Bad message data");
+ r = -EINVAL;
+ goto done;
+ }
+
+ MSG_OUT("Received a dbus response for msg with seq 0x%02x\n",
+ seq);
+
+ ssif_resp_msg.netfn = netfn;
+ ssif_resp_msg.lun = lun;
+ ssif_resp_msg.seq = seq;
+ ssif_resp_msg.cmd = cmd;
+ ssif_resp_msg.cc = cc;
+ ssif_resp_msg.data_len = data_sz;
+ /* Because we've ref'ed the msg, don't need to memcpy data */
+ ssif_resp_msg.data = data;
+
+ /* Clear the timer */
+ ts.it_interval.tv_sec = 0;
+ ts.it_interval.tv_nsec = 0;
+ ts.it_value.tv_sec = 0;
+ ts.it_value.tv_nsec = 0;
+ r = timerfd_settime(context->fds[TIMER_FD].fd,
+ TFD_TIMER_ABSTIME,
+ &ts,
+ NULL);
+
+ if (r < 0) {
+ MSG_ERR("Failed to clear timer\n");
+ }
+
+ r = ssif_send_response(context, &ssif_resp_msg);
+ }
+
+done:
+ r = sd_bus_message_append(resp_msg, "x", r);
+ if (r < 0) {
+ MSG_ERR("Failed to add result to method (ret: %d)\n", r);
+ }
+
+ r = sd_bus_send(context->bus, resp_msg, NULL);
+ if (r < 0) {
+ MSG_ERR("Failed to send response (ret: %d)\n", r);
+ }
+
+ return r;
+}
+
+static int dispatch_timer(struct ssifbridged_context *context)
+{
+ int r = 0;
+
+ if (context->fds[TIMER_FD].revents & POLLIN) {
+ if(!context->awaiting_response) {
+ /* Got a timeout but not expecting a response */
+ MSG_ERR("Timeout but no pending message\n");
+ } else {
+ struct itimerspec ts;
+
+ /* Clear the timer */
+ ts.it_interval.tv_sec = 0;
+ ts.it_interval.tv_nsec = 0;
+ ts.it_value.tv_sec = 0;
+ ts.it_value.tv_nsec = 0;
+ r = timerfd_settime(context->fds[TIMER_FD].fd,
+ TFD_TIMER_ABSTIME,
+ &ts,
+ NULL);
+ if (r < 0) {
+ MSG_ERR("Failed to clear timer\n");
+ }
+
+ MSG_ERR("Timing out message\n");
+
+ /* Add one to the netfn - response netfn is always
+ * request netfn + 1
+ */
+ context->ssif_pending_msg.netfn += 1;
+ context->ssif_pending_msg.cc =
+ IPMI_CC_CANNOT_PROVIDE_RESP;
+ context->ssif_pending_msg.data = NULL;
+ context->ssif_pending_msg.data_len = 0;
+
+ r = ssif_send_response(context,
+ &context->ssif_pending_msg);
+ if (r < 0) {
+ MSG_ERR("Failed to send timeout message "
+ "(ret: %d, errno: %d)\n",
+ r,
+ errno);
+ }
+
+ context->awaiting_response = 0;
+ }
+ }
+
+ return r;
+}
+
+static int dispatch_sd_bus(struct ssifbridged_context *context)
+{
+ int r = 0;
+ if (context->fds[SD_BUS_FD].revents) {
+ r = sd_bus_process(context->bus, NULL);
+ if (r > 0)
+ MSG_OUT("Processed %d dbus events\n", r);
+ }
+
+ return r;
+}
+
+static int dispatch_ssif(struct ssifbridged_context *context)
+{
+ int r = 0;
+
+ assert(context);
+
+ if (context->fds[SSIF_FD].revents & POLLIN) {
+ /* We have received data on the driver */
+ struct itimerspec ts;
+ struct ipmi_msg *req;
+ uint8_t data[SSIF_MAX_REQ_LEN] = { 0 };
+
+ r = read(context->fds[SSIF_FD].fd, data, sizeof(data));
+ if (r < 0) {
+ MSG_ERR("Couldn't read from ssif: %s\n", strerror(-r));
+ return r;
+ }
+ if (r < data[0] + 1) {
+ MSG_ERR("Short read from ssif (%d vs %d)\n",
+ r,
+ data[1] + 2);
+ r = 0;
+ return r;
+ }
+
+ /* Check if response is still awaiting */
+ if (context->awaiting_response) {
+ MSG_ERR("Received SSIF message while awaiting response."
+ " Discarding\n");
+ } else {
+ /* Get SSIF request message that sent from
+ * kernel space
+ */
+ req = ssif_msg_create(context, data);
+ context->awaiting_response = 1;
+
+ if (!req) {
+ MSG_ERR("Can not create request\n");
+ r = -ENOMEM;
+ return r;
+ }
+
+ /* Set up the timer. We do this before sending
+ * the signal to avoid a race condition with
+ * the response
+ */
+ ts.it_interval.tv_sec = 0;
+ ts.it_interval.tv_nsec = 0;
+ ts.it_value.tv_nsec = 0;
+ ts.it_value.tv_sec = SSIF_BMC_TIMEOUT_SEC;
+ r = timerfd_settime(context->fds[TIMER_FD].fd,
+ 0,
+ &ts,
+ NULL);
+ if (r < 0)
+ MSG_ERR("Failed to set timer (ret: %d, "
+ "errno: %d)\n",
+ r,
+ errno);
+
+ r = send_received_message_signal(context,
+ req,
+ data + 3,
+ req->data_len);
+ if (r < 0) {
+ MSG_ERR("Failed to send Received Message "
+ "signal (ret: %d)\n",
+ r);
+ }
+ }
+ }
+
+ return r;
+}
+
+static void usage(const char *name)
+{
+ fprintf(stderr, "\
+ Usage %s [--v[v] | --syslog] [-d <DEVICE>]\n\
+ --v Be verbose\n\
+ --vv Be verbose and dump entire messages\n\
+ -s, --syslog Log output to syslog (pointless without --verbose)\n\
+ -d, --device <DEVICE> use <DEVICE> file. Default is '%s'\n\n",
+ name, ssif_bmc_device);
+}
+
+static const sd_bus_vtable ipmid_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("sendMessage",
+ "yyyyyay",
+ "x",
+ &method_send_message,
+ SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_SIGNAL("ReceivedMessage",
+ "yyyyay",
+ 0),
+ SD_BUS_VTABLE_END
+};
+
+int main(int argc, char *argv[]) {
+ struct ssifbridged_context *context;
+ const char *name = argv[0];
+ int opt, polled, r;
+
+ static const struct option long_options[] = {
+ { "device", required_argument, NULL, 'd' },
+ { "v", no_argument, (int *)&verbosity, SSIF_LOG_VERBOSE },
+ { "vv", no_argument, (int *)&verbosity, SSIF_LOG_DEBUG },
+ { "syslog", no_argument, 0, 's' },
+ { 0, 0, 0, 0 }
+ };
+
+ context = calloc(1, sizeof(*context));
+ if (context == NULL) {
+ MSG_ERR("Failed to allocate memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ ssif_vlog = &ssif_log_console;
+ while ((opt = getopt_long(argc, argv, "", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 0:
+ break;
+ case 'd':
+ ssif_bmc_device = optarg;
+ break;
+ case 's':
+ /* Avoid a double openlog() */
+ if (ssif_vlog != &vsyslog) {
+ openlog(PREFIX, LOG_ODELAY, LOG_DAEMON);
+ ssif_vlog = &vsyslog;
+ }
+ break;
+ default:
+ usage(name);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (verbosity == SSIF_LOG_VERBOSE)
+ MSG_OUT("Verbose logging\n");
+
+ if (verbosity == SSIF_LOG_DEBUG)
+ MSG_OUT("Debug logging\n");
+
+ MSG_OUT("Starting\n");
+ r = sd_bus_default_system(&context->bus);
+ if (r < 0) {
+ MSG_ERR("Failed to connect to system bus: %s\n", strerror(-r));
+ goto error;
+ }
+
+ MSG_OUT("Registering dbus methods/signals\n");
+ r = sd_bus_add_object_vtable(context->bus,
+ NULL,
+ OBJ_NAME,
+ DBUS_NAME,
+ ipmid_vtable,
+ context);
+ if (r < 0) {
+ MSG_ERR("Failed to issue method call: %s\n", strerror(-r));
+ goto deregister_sdbus;
+ }
+
+ MSG_OUT("Requesting dbus name: %s\n", DBUS_NAME);
+ r = sd_bus_request_name(context->bus, DBUS_NAME,
+ SD_BUS_NAME_ALLOW_REPLACEMENT | SD_BUS_NAME_REPLACE_EXISTING);
+ if (r < 0) {
+ MSG_ERR("Failed to acquire service name: %s\n", strerror(-r));
+ goto deregister_sdbus;
+ }
+
+ MSG_OUT("Getting dbus file descriptors\n");
+ context->fds[SD_BUS_FD].fd = sd_bus_get_fd(context->bus);
+ if (context->fds[SD_BUS_FD].fd < 0) {
+ r = -errno;
+ MSG_OUT("Couldn't get the bus file descriptor: %s\n",
+ strerror(errno));
+ goto deregister_sdbus;
+ }
+
+ MSG_OUT("Opening %s\n", SSIF_BMC_PATH);
+ context->fds[SSIF_FD].fd = open(SSIF_BMC_PATH, O_RDWR | O_NONBLOCK);
+ if (context->fds[SSIF_FD].fd < 0) {
+ r = -errno;
+ MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n",
+ SSIF_BMC_PATH,
+ strerror(errno));
+ goto free_sdbus_fd;
+ }
+
+ MSG_OUT("Creating timer fd\n");
+ context->fds[TIMER_FD].fd = timerfd_create(CLOCK_MONOTONIC, 0);
+ if (context->fds[TIMER_FD].fd < 0) {
+ r = -errno;
+ MSG_ERR("Couldn't create timer fd: %s\n", strerror(errno));
+ goto free_ssif_fd;
+ }
+ context->fds[SD_BUS_FD].events = POLLIN;
+ context->fds[SSIF_FD].events = POLLIN;
+ context->fds[TIMER_FD].events = POLLIN;
+
+ MSG_OUT("Entering polling loop\n");
+
+ while (running) {
+ polled = poll(context->fds, TOTAL_FDS, -1);
+ if (polled == 0)
+ continue;
+ if (polled < 0) {
+ r = -errno;
+ MSG_ERR("Error from poll(): %s\n",
+ strerror(errno));
+ goto finish;
+ }
+ r = dispatch_sd_bus(context);
+ if (r < 0) {
+ MSG_ERR("Error handling dbus event: %s\n",
+ strerror(-r));
+ goto finish;
+ }
+ r = dispatch_ssif(context);
+ if (r < 0) {
+ MSG_ERR("Error handling SSIF event: %s\n",
+ strerror(-r));
+ goto finish;
+ }
+ r = dispatch_timer(context);
+ if (r < 0) {
+ MSG_ERR("Error handling timer event: %s\n",
+ strerror(-r));
+ goto finish;
+ }
+ }
+
+finish:
+ close(context->fds[TIMER_FD].fd);
+free_ssif_fd:
+ close(context->fds[SSIF_FD].fd);
+free_sdbus_fd:
+ close(context->fds[SD_BUS_FD].fd);
+deregister_sdbus:
+ sd_bus_unref(context->bus);
+error:
+ free(context);
+
+ return r;
+}