From: Benjamin Fair <benjaminf...@google.com>

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 <benjaminf...@google.com>
Signed-off-by: Brendan Higgins <brendanhigg...@google.com>
---
 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 <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/ipmi_bmc.h>
+#include <linux/kfifo.h>
+#include <linux/log2.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+
+#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 <benjaminf...@google.com>");
+MODULE_DESCRIPTION("Device file interface to IPMI Block Transfer core.");
+MODULE_LICENSE("GPL v2");
-- 
2.14.0.rc1.383.gd1ce394fe2-goog


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openipmi-developer mailing list
Openipmi-developer@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openipmi-developer

Reply via email to