blob: 2234501f83f6bf52bb0cdf80cf6921003e01e711 [file] [log] [blame]
/* 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;
}