| /* Copyright 2017 - 2018 Intel |
| * |
| * 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. |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <linux/ipmi_bmc.h> |
| #include <poll.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <sys/mman.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/timerfd.h> |
| #include <systemd/sd-bus.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #define DBUS_ERR "org.openbmc.error" |
| #define DBUS_NAME "org.openbmc.HostIpmi" |
| #define OBJ_NAME "/org/openbmc/HostIpmi/1" |
| |
| #define LOG_PREFIX "KCSBRIDGED" |
| |
| #define KCS_TIMEOUT_IN_SEC 5 |
| #define KCS_MESSAGE_SIZE 256 |
| |
| #define SD_BUS_FD 0 |
| #define KCS_FD 1 |
| #define TIMER_FD 2 |
| #define TOTAL_FDS 3 |
| |
| struct kcs_msg_req { |
| uint8_t netfn; |
| uint8_t lun; |
| uint8_t cmd; |
| uint8_t *data; |
| size_t data_len; |
| }; |
| |
| struct kcsbridged_context { |
| struct pollfd fds[TOTAL_FDS]; |
| struct sd_bus *bus; |
| |
| /* |
| * Request and Response Messages are paired together as a Write Transfer |
| * to the BMC to send the request followed by a Read Transfer from the |
| * BMC to get the response. |
| */ |
| int expired; |
| uint8_t seqnum; |
| struct kcs_msg_req req; |
| }; |
| |
| enum { KCS_LOG_NONE = 0, KCS_LOG_VERBOSE, KCS_LOG_DEBUG }; |
| |
| static void (*kcs_vlog)(int p, const char *fmt, va_list args); |
| static int verbosity = KCS_LOG_NONE; |
| |
| #define MSG_OUT(f_, ...) \ |
| do { \ |
| if (verbosity != KCS_LOG_NONE) \ |
| kcs_log(LOG_INFO, f_, ##__VA_ARGS__); \ |
| } while (0) |
| |
| #define MSG_ERR(f_, ...) \ |
| do { \ |
| if (verbosity != KCS_LOG_NONE) \ |
| kcs_log(LOG_ERR, f_, ##__VA_ARGS__); \ |
| } while (0) |
| |
| static void kcs_log_console(int p, const char *fmt, va_list args) |
| { |
| vfprintf(stderr, fmt, args); |
| } |
| |
| __attribute__((format(printf, 2, 3))) static void kcs_log(int p, |
| const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| kcs_vlog(p, fmt, args); |
| va_end(args); |
| } |
| |
| static void kcs_dump_data(uint8_t *data, size_t data_len) |
| { |
| size_t i; |
| int str_len; |
| char str[64]; |
| |
| str_len = 0; |
| for (i = 0; i < data_len; i++) { |
| if (i % 8 == 0) { |
| if (i != 0) { |
| kcs_log(LOG_INFO, "%s\n", str); |
| str_len = 0; |
| } |
| str_len += sprintf(&str[str_len], "\t"); |
| } |
| |
| str_len += sprintf(&str[str_len], "0x%02x ", data[i]); |
| } |
| |
| if (str_len != 0) |
| kcs_log(LOG_INFO, "%s\n", str); |
| } |
| |
| static void kcs_set_timer(struct kcsbridged_context *context, int seconds) |
| { |
| struct itimerspec ts; |
| int r; |
| |
| ts.it_interval.tv_sec = 0; |
| ts.it_interval.tv_nsec = 0; |
| ts.it_value.tv_nsec = 0; |
| ts.it_value.tv_sec = seconds; |
| |
| r = timerfd_settime(context->fds[TIMER_FD].fd, 0, &ts, NULL); |
| if (r == -1) |
| MSG_ERR("Couldn't set timerfd: %s\n", strerror(errno)); |
| } |
| |
| static int handle_kcs_request(struct kcsbridged_context *context, uint8_t *msg, |
| size_t msglen) |
| { |
| struct kcs_msg_req *req; |
| |
| if (msglen < 2) { |
| MSG_ERR("KCS message with a short length (%zd)\n", msglen); |
| return -1; |
| } |
| |
| context->expired = 0; |
| context->seqnum++; |
| |
| req = &context->req; |
| req->netfn = msg[0] >> 2; |
| req->lun = msg[0] & 0x3; |
| req->cmd = msg[1]; |
| req->data = msg + 2; |
| req->data_len = msglen - 2; |
| |
| return 0; |
| } |
| |
| static int method_send_message(sd_bus_message *msg, void *userdata, |
| sd_bus_error *err) |
| { |
| struct kcsbridged_context *context = userdata; |
| uint8_t netfn, lun, seqnum, cmd, cc; |
| struct kcs_msg_req *req; |
| uint8_t *data; |
| size_t data_sz; |
| int r; |
| uint8_t rsp[KCS_MESSAGE_SIZE]; |
| |
| if (!context || context->expired) { |
| sd_bus_error_set_const(err, DBUS_ERR, "Internal error"); |
| r = 0; |
| goto out; |
| } |
| |
| r = sd_bus_message_read(msg, "yyyyy", &seqnum, &netfn, &lun, &cmd, &cc); |
| if (r < 0) { |
| sd_bus_error_set_const(err, DBUS_ERR, "Bad message"); |
| r = -EINVAL; |
| goto out; |
| } |
| |
| req = &context->req; |
| if (context->seqnum != seqnum || (req->netfn | 1) != netfn |
| || req->lun != lun || req->cmd != cmd) { |
| sd_bus_error_set_const(err, DBUS_ERR, "No matching request"); |
| r = -EINVAL; |
| goto out; |
| } |
| |
| kcs_set_timer(context, 0); /* Stop the timer. */ |
| |
| r = sd_bus_message_read_array(msg, 'y', (const void **)&data, &data_sz); |
| if (r < 0 || data_sz > sizeof(rsp) - 3) { |
| sd_bus_error_set_const(err, DBUS_ERR, "Bad message data"); |
| r = -EINVAL; |
| goto out; |
| } |
| |
| rsp[0] = (netfn << 2) | (lun & 0x3); |
| rsp[1] = cmd; |
| rsp[2] = cc; |
| if (data_sz) |
| memcpy(rsp + 3, data, data_sz); |
| |
| r = write(context->fds[KCS_FD].fd, rsp, 3 + data_sz); |
| if (r > 0) |
| r = 0; |
| |
| MSG_OUT("Send rsp msg <- seq=0x%02x netfn=0x%02x lun=0x%02x cmd=0x%02x cc=0x%02x\n", |
| seqnum, netfn, lun, cmd, cc); |
| |
| if (verbosity == KCS_LOG_DEBUG && data_sz != 0) |
| kcs_dump_data(data, data_sz); |
| |
| out: |
| return sd_bus_reply_method_return(msg, "x", r); |
| } |
| |
| static int method_set_sms_atn(sd_bus_message *msg, void *userdata, |
| sd_bus_error *err) |
| { |
| struct kcsbridged_context *context = userdata; |
| int r; |
| |
| MSG_OUT("Sending SET_SMS_ATN\n"); |
| |
| r = ioctl(context->fds[KCS_FD].fd, IPMI_BMC_IOCTL_SET_SMS_ATN); |
| if (r == -1) { |
| r = errno; |
| MSG_ERR("Couldn't SET_SMS_ATN: %s\n", strerror(r)); |
| return sd_bus_reply_method_errno(msg, errno, err); |
| } |
| |
| r = 0; |
| return sd_bus_reply_method_return(msg, "x", r); |
| } |
| |
| static int method_clear_sms_atn(sd_bus_message *msg, void *userdata, |
| sd_bus_error *err) |
| { |
| struct kcsbridged_context *context = userdata; |
| int r; |
| |
| MSG_OUT("Sending CLEAR_SMS_ATN\n"); |
| |
| r = ioctl(context->fds[KCS_FD].fd, IPMI_BMC_IOCTL_CLEAR_SMS_ATN); |
| if (r == -1) { |
| r = errno; |
| MSG_ERR("Couldn't CLEAR_SMS_ATN: %s\n", strerror(r)); |
| return sd_bus_reply_method_errno(msg, errno, err); |
| } |
| |
| r = 0; |
| return sd_bus_reply_method_return(msg, "x", r); |
| } |
| |
| static int method_force_abort(sd_bus_message *msg, void *userdata, |
| sd_bus_error *err) |
| { |
| struct kcsbridged_context *context = userdata; |
| int r; |
| |
| MSG_OUT("Sending FORCE_ABORT\n"); |
| |
| r = ioctl(context->fds[KCS_FD].fd, IPMI_BMC_IOCTL_FORCE_ABORT); |
| if (r == -1) { |
| r = errno; |
| MSG_ERR("Couldn't FORCE_ABORT: %s\n", strerror(r)); |
| return sd_bus_reply_method_errno(msg, errno, err); |
| } |
| |
| r = 0; |
| return sd_bus_reply_method_return(msg, "x", r); |
| } |
| |
| static int dispatch_sd_bus(struct kcsbridged_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_timer(struct kcsbridged_context *context) |
| { |
| if (context->fds[TIMER_FD].revents & POLLIN) { |
| struct kcs_msg_req *req; |
| uint8_t rsp[3]; |
| |
| MSG_OUT("Timeout on msg with seq: 0x%02x\n", context->seqnum); |
| |
| context->expired = 1; |
| |
| req = &context->req; |
| rsp[0] = ((req->netfn | 1) << 2) | (req->lun & 0x3); |
| rsp[1] = req->cmd; |
| rsp[2] = 0xce; /* Command response could not be provided */ |
| if (write(context->fds[KCS_FD].fd, rsp, 3) < 0) |
| MSG_ERR("Failed to send the timeout response!\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int dispatch_kcs(struct kcsbridged_context *context) |
| { |
| struct kcs_msg_req *req = &context->req; |
| sd_bus_message *msg; |
| int r = 0, len; |
| uint8_t data[KCS_MESSAGE_SIZE]; |
| |
| if (!(context->fds[KCS_FD].revents & POLLIN)) |
| goto out; |
| |
| len = read(context->fds[KCS_FD].fd, data, sizeof(data)); |
| if (len < 0 || handle_kcs_request(context, data, len)) |
| goto out; |
| |
| 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)); |
| goto out; |
| } |
| |
| r = sd_bus_message_append(msg, "yyyy", context->seqnum, req->netfn, |
| req->lun, req->cmd); |
| if (r < 0) { |
| MSG_ERR("Couldn't append header to signal: %s\n", strerror(-r)); |
| goto bail; |
| } |
| |
| r = sd_bus_message_append_array(msg, 'y', req->data, req->data_len); |
| if (r < 0) { |
| MSG_ERR("Couldn't append array to signal: %s\n", strerror(-r)); |
| goto bail; |
| } |
| |
| r = sd_bus_send(context->bus, msg, NULL); |
| if (r < 0) { |
| MSG_ERR("Couldn't emit dbus signal: %s\n", strerror(-r)); |
| goto bail; |
| } |
| |
| kcs_set_timer(context, KCS_TIMEOUT_IN_SEC); |
| |
| MSG_OUT("Recv req msg -> seq=0x%02x netfn=0x%02x lun=0x%02x cmd=0x%02x\n", |
| context->seqnum, req->netfn, req->lun, req->cmd); |
| |
| if (verbosity == KCS_LOG_DEBUG && req->data_len != 0) |
| kcs_dump_data(req->data, req->data_len); |
| |
| bail: |
| sd_bus_message_unref(msg); |
| out: |
| 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.\n\n", |
| name); |
| } |
| |
| 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_METHOD("setAttention", "", "x", &method_set_sms_atn, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| SD_BUS_METHOD("clearAttention", "", "x", &method_clear_sms_atn, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| SD_BUS_METHOD("forceAbort", "", "x", &method_force_abort, |
| SD_BUS_VTABLE_UNPRIVILEGED), |
| SD_BUS_SIGNAL("ReceivedMessage", "yyyyay", 0), |
| SD_BUS_VTABLE_END}; |
| |
| int main(int argc, char *argv[]) |
| { |
| struct kcsbridged_context *context; |
| const char *kcs_bmc_device = NULL; |
| const char *name = argv[0]; |
| int opt, polled, r; |
| static const struct option long_options[] = { |
| {"device", required_argument, 0, 'd'}, |
| {"v", no_argument, &verbosity, KCS_LOG_VERBOSE}, |
| {"vv", no_argument, &verbosity, KCS_LOG_DEBUG}, |
| {"syslog", no_argument, 0, 's'}, |
| {0, 0, 0, 0}}; |
| |
| context = calloc(1, sizeof(*context)); |
| if (!context) { |
| fprintf(stderr, "OOM!\n"); |
| return -1; |
| } |
| |
| kcs_vlog = &kcs_log_console; |
| while ((opt = getopt_long(argc, argv, "", long_options, NULL)) != -1) { |
| switch (opt) { |
| case 0: |
| break; |
| |
| case 'd': |
| kcs_bmc_device = optarg; |
| break; |
| |
| case 's': |
| if (kcs_vlog != &vsyslog) { |
| openlog(LOG_PREFIX, LOG_ODELAY, LOG_DAEMON); |
| kcs_vlog = &vsyslog; |
| } |
| break; |
| |
| default: |
| usage(name); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| if (!kcs_bmc_device) { |
| usage(name); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (verbosity == KCS_LOG_VERBOSE) |
| MSG_OUT("Verbose logging\n"); |
| else if (verbosity == KCS_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 finish; |
| } |
| |
| 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 finish; |
| } |
| |
| 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 finish; |
| } |
| |
| 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 finish; |
| } |
| |
| MSG_OUT("Opening %s\n", kcs_bmc_device); |
| context->fds[KCS_FD].fd = open(kcs_bmc_device, O_RDWR | O_NONBLOCK); |
| if (context->fds[KCS_FD].fd < 0) { |
| r = -errno; |
| MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", |
| kcs_bmc_device, strerror(errno)); |
| goto finish; |
| } |
| |
| 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 finish; |
| } |
| context->fds[SD_BUS_FD].events = POLLIN; |
| context->fds[KCS_FD].events = POLLIN; |
| context->fds[TIMER_FD].events = POLLIN; |
| |
| MSG_OUT("Entering polling loop\n"); |
| |
| while (1) { |
| polled = poll(context->fds, TOTAL_FDS, 5000); |
| 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_kcs(context); |
| if (r < 0) { |
| MSG_ERR("Error handling KCS 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[KCS_FD].fd); |
| close(context->fds[TIMER_FD].fd); |
| sd_bus_unref(context->bus); |
| free(context); |
| |
| return r; |
| } |