Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752066AbdHHDxM (ORCPT ); Mon, 7 Aug 2017 23:53:12 -0400 Received: from mail-pg0-f51.google.com ([74.125.83.51]:34883 "EHLO mail-pg0-f51.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751891AbdHHDxJ (ORCPT ); Mon, 7 Aug 2017 23:53:09 -0400 From: Brendan Higgins To: minyard@acm.org, benjaminfair@google.com, clg@kaod.org, joel@jms.id.au, andrew@aj.id.au Cc: openipmi-developer@lists.sourceforge.net, openbmc@lists.ozlabs.org, linux-kernel@vger.kernel.org, Brendan Higgins Subject: [RFC v1 2/4] ipmi_bmc: device interface to IPMI BMC framework Date: Mon, 7 Aug 2017 20:52:59 -0700 Message-Id: <20170808035301.1980-3-brendanhiggins@google.com> X-Mailer: git-send-email 2.14.0.rc1.383.gd1ce394fe2-goog In-Reply-To: <20170808035301.1980-1-brendanhiggins@google.com> References: <20170808035301.1980-1-brendanhiggins@google.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 8455 Lines: 292 From: Benjamin Fair This creates a char device which allows userspace programs to send and receive IPMI messages. Messages are only routed to userspace if no other kernel driver can handle them. Signed-off-by: Benjamin Fair Signed-off-by: Brendan Higgins --- drivers/char/ipmi_bmc/Kconfig | 6 + drivers/char/ipmi_bmc/Makefile | 1 + drivers/char/ipmi_bmc/ipmi_bmc_devintf.c | 241 +++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 drivers/char/ipmi_bmc/ipmi_bmc_devintf.c diff --git a/drivers/char/ipmi_bmc/Kconfig b/drivers/char/ipmi_bmc/Kconfig index b6af38455702..262a17866aa2 100644 --- a/drivers/char/ipmi_bmc/Kconfig +++ b/drivers/char/ipmi_bmc/Kconfig @@ -11,6 +11,12 @@ menuconfig IPMI_BMC if IPMI_BMC +config IPMI_BMC_DEVICE_INTERFACE + tristate 'Device interface for BMC-side IPMI' + help + This provides a file interface to the IPMI BMC core so userland + processes may use IPMI. + config IPMI_BMC_BT_I2C depends on I2C select I2C_SLAVE diff --git a/drivers/char/ipmi_bmc/Makefile b/drivers/char/ipmi_bmc/Makefile index 9c7cd48d899f..ead8abffbd11 100644 --- a/drivers/char/ipmi_bmc/Makefile +++ b/drivers/char/ipmi_bmc/Makefile @@ -3,5 +3,6 @@ # obj-$(CONFIG_IPMI_BMC) += ipmi_bmc.o +obj-$(CONFIG_IPMI_BMC_DEVICE_INTERFACE) += ipmi_bmc_devintf.o obj-$(CONFIG_IPMI_BMC_BT_I2C) += ipmi_bmc_bt_i2c.o obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += ipmi_bmc_bt_aspeed.o diff --git a/drivers/char/ipmi_bmc/ipmi_bmc_devintf.c b/drivers/char/ipmi_bmc/ipmi_bmc_devintf.c new file mode 100644 index 000000000000..2421237ed575 --- /dev/null +++ b/drivers/char/ipmi_bmc/ipmi_bmc_devintf.c @@ -0,0 +1,241 @@ +/* + * Copyright 2017 Google Inc. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PFX "IPMI BMC devintf: " + +#define DEVICE_NAME "ipmi-bt-host" + +/* Must be a power of two */ +#define REQUEST_FIFO_SIZE roundup_pow_of_two(BT_MSG_SEQ_MAX) + +struct bmc_devintf_data { + struct miscdevice miscdev; + struct ipmi_bmc_device bmc_device; + struct ipmi_bmc_ctx *bmc_ctx; + wait_queue_head_t wait_queue; + /* FIFO of waiting messages */ + DECLARE_KFIFO(requests, struct bt_msg, REQUEST_FIFO_SIZE); +}; + +static inline struct bmc_devintf_data *file_to_bmc_devintf_data( + struct file *file) +{ + return container_of(file->private_data, struct bmc_devintf_data, + miscdev); +} + +static ssize_t ipmi_bmc_devintf_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct bmc_devintf_data *devintf_data = file_to_bmc_devintf_data(file); + bool non_blocking = file->f_flags & O_NONBLOCK; + struct bt_msg msg; + + if (non_blocking && kfifo_is_empty(&devintf_data->requests)) { + return -EAGAIN; + } else if (!non_blocking) { + if (wait_event_interruptible(devintf_data->wait_queue, + !kfifo_is_empty(&devintf_data->requests))) + return -ERESTARTSYS; + } + + /* TODO(benjaminfair): eliminate this extra copy */ + if (unlikely(!kfifo_get(&devintf_data->requests, &msg))) { + pr_err(PFX "Unable to read request from fifo\n"); + return -EIO; + } + + /* TODO(benjaminfair): handle partial reads of a message */ + if (count > bt_msg_len(&msg)) + count = bt_msg_len(&msg); + + if (copy_to_user(buf, &msg, count)) + return -EFAULT; + + return count; +} + +static ssize_t ipmi_bmc_devintf_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct bmc_devintf_data *devintf_data = file_to_bmc_devintf_data(file); + bool non_blocking = file->f_flags & O_NONBLOCK; + struct bt_msg msg; + ssize_t ret = 0; + + if (count > sizeof(struct bt_msg)) + return -EINVAL; + + if (copy_from_user(&msg, buf, count)) + return -EFAULT; + + if (count != bt_msg_len(&msg)) + return -EINVAL; + + ret = ipmi_bmc_send_response(devintf_data->bmc_ctx, &msg); + + /* Try again if blocking is allowed */ + while (!non_blocking && ret == -EAGAIN) { + if (wait_event_interruptible(devintf_data->wait_queue, + ipmi_bmc_is_response_open( + devintf_data->bmc_ctx))) + return -ERESTARTSYS; + + ret = ipmi_bmc_send_response(devintf_data->bmc_ctx, &msg); + } + + if (ret < 0) + return ret; + else + return count; +} + +static unsigned int ipmi_bmc_devintf_poll(struct file *file, poll_table *wait) +{ + struct bmc_devintf_data *devintf_data = file_to_bmc_devintf_data(file); + unsigned int mask = 0; + + poll_wait(file, &devintf_data->wait_queue, wait); + + if (!kfifo_is_empty(&devintf_data->requests)) + mask |= POLLIN; + if (ipmi_bmc_is_response_open(devintf_data->bmc_ctx)) + mask |= POLLOUT; + + return mask; +} + +static const struct file_operations ipmi_bmc_fops = { + .owner = THIS_MODULE, + .read = ipmi_bmc_devintf_read, + .write = ipmi_bmc_devintf_write, + .poll = ipmi_bmc_devintf_poll, +}; + +static inline struct bmc_devintf_data *device_to_bmc_devintf_data( + struct ipmi_bmc_device *device) +{ + return container_of(device, struct bmc_devintf_data, bmc_device); +} + +static int ipmi_bmc_devintf_handle_request(struct ipmi_bmc_device *device, + struct bt_msg *bt_request) +{ + struct bmc_devintf_data *devintf_data = + device_to_bmc_devintf_data(device); + + if (!bt_request->len) + return -EINVAL; + + if (!kfifo_put(&devintf_data->requests, *bt_request)) + return -EBUSY; + + wake_up_all(&devintf_data->wait_queue); + + return 0; +} + +static bool ipmi_bmc_devintf_match_request(struct ipmi_bmc_device *device, + struct bt_msg *bt_request) +{ + /* Since this is a default device, match all requests */ + return true; +} + +static void ipmi_bmc_devintf_signal_response_open( + struct ipmi_bmc_device *device) +{ + struct bmc_devintf_data *devintf_data = + device_to_bmc_devintf_data(device); + + wake_up_all(&devintf_data->wait_queue); +} + +/* + * TODO: if we want to support multiple interfaces, initialize this global + * variable elsewhere + */ +static struct bmc_devintf_data *devintf_data; + +static int __init init_ipmi_bmc_devintf(void) +{ + int ret; + + devintf_data = kzalloc(sizeof(*devintf_data), GFP_KERNEL); + if (!devintf_data) + return -ENOMEM; + + init_waitqueue_head(&devintf_data->wait_queue); + INIT_KFIFO(devintf_data->requests); + + devintf_data->bmc_device.handle_request = + ipmi_bmc_devintf_handle_request; + devintf_data->bmc_device.match_request = + ipmi_bmc_devintf_match_request; + devintf_data->bmc_device.signal_response_open = + ipmi_bmc_devintf_signal_response_open; + + devintf_data->miscdev.minor = MISC_DYNAMIC_MINOR; + devintf_data->miscdev.name = DEVICE_NAME; + devintf_data->miscdev.fops = &ipmi_bmc_fops; + + devintf_data->bmc_ctx = ipmi_bmc_get_global_ctx(); + + ret = ipmi_bmc_register_default_device(devintf_data->bmc_ctx, + &devintf_data->bmc_device); + if (ret) { + pr_err(PFX "unable to register IPMI BMC device\n"); + return ret; + } + + ret = misc_register(&devintf_data->miscdev); + if (ret) { + ipmi_bmc_unregister_default_device(devintf_data->bmc_ctx, + &devintf_data->bmc_device); + pr_err(PFX "unable to register misc device\n"); + return ret; + } + + pr_info(PFX "initialized\n"); + return 0; +} +module_init(init_ipmi_bmc_devintf); + +static void __exit exit_ipmi_bmc_devintf(void) +{ + misc_deregister(&devintf_data->miscdev); + WARN_ON(ipmi_bmc_unregister_default_device(devintf_data->bmc_ctx, + &devintf_data->bmc_device)); +} +module_exit(exit_ipmi_bmc_devintf); + +MODULE_AUTHOR("Benjamin Fair "); +MODULE_DESCRIPTION("Device file interface to IPMI Block Transfer core."); +MODULE_LICENSE("GPL v2"); -- 2.14.0.rc1.383.gd1ce394fe2-goog