Skip to contentSkip to conversation

drivers/misc: Add Aspeed LPC control driver

PATCH In review Cyril Bur 5 participants 7 messages Feb 17, 2017 – Mar 16, 2017

Subsystems (2)
lore
Share & tools
lore

Cyril Bur

author
#0 lore raw

PATCH v6 1/1 drivers/misc: Add Aspeed LPC control driver

In order to manage server systems, there is typically another processor known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

The Aspeed BMC family which is what is used on OpenPOWER machines and a number of x86 as well is typically connected to the host via an LPC (Low Pin Count) bus (among others).

The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW cycles) that contains the BIOS or other host firmware along with a number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers.

On the BMC chip side, this is all configured via a bunch of registers whose content is related to a given policy of what devices are exposed at a per system level, which is system/vendor specific, so we don't want to bolt that into the BMC kernel. This started with a need to provide something nicer than /dev/mem for user space to configure these things.

One important aspect of the configuration is how the MEM/FW space is exposed to the host (ie, the x86 or POWER). Some registers in that bridge can define a window remapping all or portion of the LPC MEM/FW space to a portion of the BMC internal bus, with no specific limits imposed in HW.

I think it makes sense to ensure that this window is configured by a kernel driver that can apply some serious sanity checks on what it is configured to map.

In practice, user space wants to control this by flipping the mapping between essentially two types of portions of the BMC address space:

- The flash space. This is a region of the BMC MMIO space that
more/less directly maps the system flash (at least for reads, writes are somewhat more complicated).

- One (or more) reserved area(s) of the BMC physical memory.

The latter is needed for a number of things, such as avoiding letting the host manipulate the innards of the BMC flash controller via some evil backdoor, we want to do flash updates by routing the window to a portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data in bulk and then request the BMC to flash it. There are other uses, such as allowing the host to boot from an in-memory flash image rather than the one in flash (very handy for continuous integration and test, the BMC can just download new images).

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

---
v2:
Removed unused functions
Removed use of access_ok()
All input is evil
Reworked the interface as per Benjamin Herrenschmidts vision
v3:
Removed 'default y' from Kconfig
Reordered ioctl() struct fields
Reworeded some comments
v4:
Reorder ioctl() struct fields (again)
v5:
Style cleanups
Use of_address_to_resource()
Document ioctl structure fields
v6:
Check and fail ioctl() if flags field non-zero

 drivers/misc/Kconfig                 |   8 ++
 drivers/misc/Makefile                |   1 +
 drivers/misc/aspeed-lpc-ctrl.c       | 267 +++++++++++++++++++++++++++++++++++
 include/uapi/linux/aspeed-lpc-ctrl.h |  60 ++++++++
 4 files changed, 336 insertions(+)
 create mode 100644 drivers/misc/aspeed-lpc-ctrl.c
 create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h
drivers/misc/Kconfig +8 -0
index 64971baf11fa..7dc4c369012f 100644
@@ -766,6 +766,14 @@ config PANEL_BOOT_MESSAGE
 	  An empty message will only clear the display at driver init time. Any other
 	  printf()-formatted message is valid with newline and escape codes.
 
+config ASPEED_LPC_CTRL
+	depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
+	tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
+	---help---
+	  Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
+	  ioctl()s, the driver also provides a read/write interface to a BMC ram
+	  region where the host LPC read/write region can be buffered.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
drivers/misc/Makefile +1 -0
index 31983366090a..de1925a9c80b 100644
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO)		+= echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL)	+= aspeed-lpc-ctrl.o
 
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_bugs.o
drivers/misc/aspeed-lpc-ctrl.c new file +267 -0

267 lines not on this page · raw message

include/uapi/linux/aspeed-lpc-ctrl.h new file +60 -0

60 lines not on this page · raw message

Signature · 1 line
2.11.1
maintainer Reviewed-by on v6 1/1
#1 lore raw
Quoted 68 lines · Cyril Bur

In order to manage server systems, there is typically another processor known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

The Aspeed BMC family which is what is used on OpenPOWER machines and a number of x86 as well is typically connected to the host via an LPC (Low Pin Count) bus (among others).

The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW cycles) that contains the BIOS or other host firmware along with a number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers.

On the BMC chip side, this is all configured via a bunch of registers whose content is related to a given policy of what devices are exposed at a per system level, which is system/vendor specific, so we don't want to bolt that into the BMC kernel. This started with a need to provide something nicer than /dev/mem for user space to configure these things.

One important aspect of the configuration is how the MEM/FW space is exposed to the host (ie, the x86 or POWER). Some registers in that bridge can define a window remapping all or portion of the LPC MEM/FW space to a portion of the BMC internal bus, with no specific limits imposed in HW.

I think it makes sense to ensure that this window is configured by a kernel driver that can apply some serious sanity checks on what it is configured to map.

In practice, user space wants to control this by flipping the mapping between essentially two types of portions of the BMC address space:

- The flash space. This is a region of the BMC MMIO space that
more/less directly maps the system flash (at least for reads, writes are somewhat more complicated).

- One (or more) reserved area(s) of the BMC physical memory.

The latter is needed for a number of things, such as avoiding letting the host manipulate the innards of the BMC flash controller via some evil backdoor, we want to do flash updates by routing the window to a portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data in bulk and then request the BMC to flash it. There are other uses, such as allowing the host to boot from an in-memory flash image rather than the one in flash (very handy for continuous integration and test, the BMC can just download new images).

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

---
v2:
Removed unused functions
Removed use of access_ok()
All input is evil
Reworked the interface as per Benjamin Herrenschmidts vision
v3:
Removed 'default y' from Kconfig
Reordered ioctl() struct fields
Reworeded some comments

Quoted 8 lines

v4:
Reorder ioctl() struct fields (again)
v5:
Style cleanups
Use of_address_to_resource()
Document ioctl structure fields
v6:
Check and fail ioctl() if flags field non-zero

Woot!

Quoted 382 lines
 drivers/misc/Kconfig                 |   8 ++
 drivers/misc/Makefile                |   1 +
 drivers/misc/aspeed-lpc-ctrl.c       | 267 +++++++++++++++++++++++++++++++++++
 include/uapi/linux/aspeed-lpc-ctrl.h |  60 ++++++++
 4 files changed, 336 insertions(+)
 create mode 100644 drivers/misc/aspeed-lpc-ctrl.c
 create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h
drivers/misc/Kconfig +8 -0
index 64971baf11fa..7dc4c369012f 100644
@@ -766,6 +766,14 @@ config PANEL_BOOT_MESSAGE
          An empty message will only clear the display at driver init time. Any other
          printf()-formatted message is valid with newline and escape codes.
 
+config ASPEED_LPC_CTRL
+       depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
+       tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
+       ---help---
+         Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
+         ioctl()s, the driver also provides a read/write interface to a BMC ram
+         region where the host LPC read/write region can be buffered.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
drivers/misc/Makefile +1 -0
index 31983366090a..de1925a9c80b 100644
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO)            += echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)  += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)         += cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL)  += aspeed-lpc-ctrl.o
 
 lkdtm-$(CONFIG_LKDTM)          += lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM)          += lkdtm_bugs.o
drivers/misc/aspeed-lpc-ctrl.c new file +267 -0
new file mode 100644
index 000000000000..f6acbe1d9378
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME    "aspeed-lpc-ctrl"
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+       struct miscdevice       miscdev;
+       struct regmap           *regmap;
+       phys_addr_t             mem_base;
+       resource_size_t         mem_size;
+       u32             pnor_size;
+       u32             pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file)
+{
+       return container_of(file->private_data, struct aspeed_lpc_ctrl,
+                       miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+       unsigned long vsize = vma->vm_end - vma->vm_start;
+       pgprot_t prot = vma->vm_page_prot;
+
+       if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size)
+               return -EINVAL;
+
+       /* ast2400/2500 AHB accesses are not cache coherent */
+       prot = pgprot_dmacoherent(prot);
+
+       if (remap_pfn_range(vma, vma->vm_start,
+               (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
+               vsize, prot))
+               return -EAGAIN;
+
+       return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd,
+               unsigned long param)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+       void __user *p = (void __user *)param;
+       struct aspeed_lpc_ctrl_mapping map;
+       u32 addr;
+       u32 size;
+       long rc;
+
+       if (copy_from_user(&map, p, sizeof(map)))
+               return -EFAULT;
+
+       if (map.flags != 0)
+               return -EINVAL;
+
+       switch (cmd) {
+       case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+               /* The flash windows don't report their size */
+               if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY)
+                       return -EINVAL;
+
+               /* Support more than one window id in the future */
+               if (map.window_id != 0)
+                       return -EINVAL;
+
+               map.size = lpc_ctrl->mem_size;
+
+               return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0;
+       case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+               /*
+                * The top half of HICR7 is the MSB of the BMC address of the
+                * mapping.
+                * The bottom half of HICR7 is the MSB of the HOST LPC
+                * firmware space address of the mapping.
+                *
+                * The 1 bits in the top of half of HICR8 represent the bits
+                * (in the requested address) that should be ignored and
+                * replaced with those from the top half of HICR7.
+                * The 1 bits in the bottom half of HICR8 represent the bits
+                * (in the requested address) that should be kept and pass
+                * into the BMC address space.
+                */
+
+               /*
+                * It doesn't make sense to talk about a size or offset with
+                * low 16 bits set. Both HICR7 and HICR8 talk about the top 16
+                * bits of addresses and sizes.
+                */
+
+               if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff))
+                       return -EINVAL;
+
+               /*
+                * Because of the way the masks work in HICR8 offset has to
+                * be a multiple of size.
+                */
+               if (map.offset & (map.size - 1))
+                       return -EINVAL;
+
+               if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) {
+                       addr = lpc_ctrl->pnor_base;
+                       size = lpc_ctrl->pnor_size;
+               } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) {
+                       addr = lpc_ctrl->mem_base;
+                       size = lpc_ctrl->mem_size;
+               } else {
+                       return -EINVAL;
+               }
+
+               /* Check overflow first! */
+               if (map.offset + map.size < map.offset ||
+                       map.offset + map.size > size)
+                       return -EINVAL;
+
+               if (map.size == 0 || map.size > size)
+                       return -EINVAL;
+
+               addr += map.offset;
+
+               /*
+                * addr (host lpc address) is safe regardless of values. This
+                * simply changes the address the host has to request on its
+                * side of the LPC bus. This cannot impact the hosts own
+                * memory space by surprise as LPC specific accessors are
+                * required. The only strange thing that could be done is
+                * setting the lower 16 bits but the shift takes care of that.
+                */
+
+               rc = regmap_write(lpc_ctrl->regmap, HICR7,
+                               (addr | (map.addr >> 16)));
+               if (rc)
+                       return rc;
+
+               return regmap_write(lpc_ctrl->regmap, HICR8,
+                       (~(map.size - 1)) | ((map.size >> 16) - 1));
+       }
+
+       return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+       .owner          = THIS_MODULE,
+       .mmap           = aspeed_lpc_ctrl_mmap,
+       .unlocked_ioctl = aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl;
+       struct device_node *node;
+       struct resource resm;
+       struct device *dev;
+       int rc;
+
+       dev = &pdev->dev;
+
+       lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
+       if (!lpc_ctrl)
+               return -ENOMEM;
+
+       node = of_parse_phandle(dev->of_node, "flash", 0);
+       if (!node) {
+               dev_err(dev, "Didn't find host pnor flash node\n");
+               return -ENODEV;
+       }
+
+       rc = of_address_to_resource(node, 1, &resm);
+       of_node_put(node);
+       if (rc) {
+               dev_err(dev, "Couldn't address to resource for flash\n");
+               return rc;
+       }
+
+       lpc_ctrl->pnor_size = resource_size(&resm);
+       lpc_ctrl->pnor_base = resm.start;
+
+       dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+       node = of_parse_phandle(dev->of_node, "memory-region", 0);
+       if (!node) {
+               dev_err(dev, "Didn't find reserved memory\n");
+               return -EINVAL;
+       }
+
+       rc = of_address_to_resource(node, 0, &resm);
+       of_node_put(node);
+       if (rc) {
+               dev_err(dev, "Couldn't address to resource for reserved memory\n");
+               return -ENOMEM;
+       }
+
+       lpc_ctrl->mem_size = resource_size(&resm);
+       lpc_ctrl->mem_base = resm.start;
+
+       lpc_ctrl->regmap = syscon_node_to_regmap(
+                       pdev->dev.parent->of_node);
+       if (IS_ERR(lpc_ctrl->regmap)) {
+               dev_err(dev, "Couldn't get regmap\n");
+               return -ENODEV;
+       }
+
+       lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+       lpc_ctrl->miscdev.name = DEVICE_NAME;
+       lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+       lpc_ctrl->miscdev.parent = dev;
+       rc = misc_register(&lpc_ctrl->miscdev);
+       if (rc)
+               dev_err(dev, "Unable to register device\n");
+       else
+               dev_info(dev, "Loaded at 0x%08x (0x%08x)\n",
+                       lpc_ctrl->mem_base, lpc_ctrl->mem_size);
+
+       return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+       struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev);
+
+       misc_deregister(&lpc_ctrl->miscdev);
+
+       return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+       { .compatible = "aspeed,ast2400-lpc-ctrl" },
+       { .compatible = "aspeed,ast2500-lpc-ctrl" },
+       { },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+       .driver = {
+               .name           = DEVICE_NAME,
+               .of_match_table = aspeed_lpc_ctrl_match,
+       },
+       .probe = aspeed_lpc_ctrl_probe,
+       .remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <[email protected]>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings");
include/uapi/linux/aspeed-lpc-ctrl.h new file +60 -1
new file mode 100644
index 000000000000..f96fa995a3f0
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _UAPI_LINUX_ASPEED_LPC_CTRL_H
+#define _UAPI_LINUX_ASPEED_LPC_CTRL_H
+
+#include <linux/ioctl.h>
+
+/* Window types */
+#define ASPEED_LPC_CTRL_WINDOW_FLASH   1
+#define ASPEED_LPC_CTRL_WINDOW_MEMORY  2
+
+/*
+ * This driver provides a window for the host to access a BMC resource
+ * across the BMC <-> Host LPC bus.
+ *
+ * window_type: The BMC resource that the host will access through the
+ * window. BMC flash and BMC RAM.
+ *
+ * window_id: For each window type there may be multiple windows,
+ * these are referenced by ID.
+ *
+ * flags: Reserved for future use, this field is expected to be
+ * zeroed.
+ *
+ * addr: Address on the host LPC bus that the specified window should
+ * be mapped. This address must be power of two aligned.
+ *
+ * offset: Offset into the BMC window that should be mapped to the
+ * host (at addr). This must be a multiple of size.
+ *
+ * size: The size of the mapping. The smallest possible size is 64K.
+ * This must be power of two aligned.
+ *
+ */
+
+struct aspeed_lpc_ctrl_mapping {
+       __u8    window_type;
+       __u8    window_id;
+       __u16   flags;
+       __u32   addr;
+       __u32   offset;
+       __u32   size;
+};
+
+#define __ASPEED_LPC_CTRL_IOCTL_MAGIC  0xb2
+
+#define ASPEED_LPC_CTRL_IOCTL_GET_SIZE _IOWR(__ASPEED_LPC_CTRL_IOCTL_MAGIC, \
+               0x00, struct aspeed_lpc_ctrl_mapping)
+
+#define ASPEED_LPC_CTRL_IOCTL_MAP      _IOW(__ASPEED_LPC_CTRL_IOCTL_MAGIC, \
+               0x01, struct aspeed_lpc_ctrl_mapping)
+
+#endif /* _UAPI_LINUX_ASPEED_LPC_CTRL_H */
--

2.11.1

Reviewed-by on v6 1/1
#2 lore raw
In reply to Cyril Bur
Quoted 5 lines · Cyril Bur

In order to manage server systems, there is typically another processor
known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

.../...

Quoted 8 lines

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

Greg, quick Q for you:

We will need to also add a mechanism to poke at a few registers in the same LPC controllers from userspace.

Mostly those are "scratch" registers that the BMC is supposed to set to specific values to indicates features it supports etc... (or to enable the host BIOS to run in some kind of verbose debug mode).

Think of it as a communication channel between the BMC and the BIOS running on the host.

The BMC userspace needs to set them to various platform specific values as appropriate for a given vendor/system (userspace policy from the BMC perspective) during boot and/or change them as the result of some user actions (enable debug mode) etc...

The question here is read/write or sysfs files attached to the sysfs node ?

The former means userspace needs to "seek" to the right magic offset to write to one of them which is non-trivial to do from simple shell scripts but is a more natural interface.

It also means the owner/permissions of the /dev entry as set by uboot apply which can be nice.

The latter can expose each register by its name which provides a nice way to echo in/out of them from a shell script.

The drawback is that pretty much restricts access to root.

What do you recommend ?

Cheers,
Ben.

maintainer
#3 lore raw
Quoted 47 lines · Benjamin Herrenschmidt
Quoted 5 lines · Cyril Bur

In order to manage server systems, there is typically another processor
known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

.../...

Quoted 8 lines

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

Greg, quick Q for you:

We will need to also add a mechanism to poke at a few registers in the same LPC controllers from userspace.

Mostly those are "scratch" registers that the BMC is supposed to set to specific values to indicates features it supports etc... (or to enable the host BIOS to run in some kind of verbose debug mode).

Think of it as a communication channel between the BMC and the BIOS running on the host.

The BMC userspace needs to set them to various platform specific values as appropriate for a given vendor/system (userspace policy from the BMC perspective) during boot and/or change them as the result of some user actions (enable debug mode) etc...

The question here is read/write or sysfs files attached to the sysfs node ?

The former means userspace needs to "seek" to the right magic offset to write to one of them which is non-trivial to do from simple shell scripts but is a more natural interface.

It also means the owner/permissions of the /dev entry as set by uboot apply which can be nice.

Quoted 6 lines

The latter can expose each register by its name which provides a nice way to echo in/out of them from a shell script.

The drawback is that pretty much restricts access to root.

What do you recommend ?

That wasn't a quick question :)

I really have no idea, sorry, try some things out and see what seems to work.

greg k-h

#4 lore raw
In reply to Cyril Bur

On Fri, 2017-02-17 at 14:28 +1100, Cyril Bur wrote:

I may be too late, but see below...

Quoted 242 lines

In order to manage server systems, there is typically another processor
known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

The Aspeed BMC family which is what is used on OpenPOWER machines and a
number of x86 as well is typically connected to the host via an LPC (Low Pin Count) bus (among others).

The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW
cycles) that contains the BIOS or other host firmware along with a number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers.

On the BMC chip side, this is all configured via a bunch of registers whose content is related to a given policy of what devices are exposed
at a per system level, which is system/vendor specific, so we don't want
to bolt that into the BMC kernel. This started with a need to provide something nicer than /dev/mem for user space to configure these things.

One important aspect of the configuration is how the MEM/FW space is exposed to the host (ie, the x86 or POWER). Some registers in that bridge can define a window remapping all or portion of the LPC MEM/FW space to a portion of the BMC internal bus, with no specific limits imposed in HW.

I think it makes sense to ensure that this window is configured by a kernel driver that can apply some serious sanity checks on what it is configured to map.

In practice, user space wants to control this by flipping the mapping between essentially two types of portions of the BMC address space:

   - The flash space. This is a region of the BMC MMIO space that more/less directly maps the system flash (at least for reads, writes are somewhat more complicated).

   - One (or more) reserved area(s) of the BMC physical memory.

The latter is needed for a number of things, such as avoiding letting the host manipulate the innards of the BMC flash controller via some evil backdoor, we want to do flash updates by routing the window to a portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data in
bulk and then request the BMC to flash it. There are other uses, such as allowing the host to boot from an in-memory flash image rather than
the one in flash (very handy for continuous integration and test, the BMC can just download new images).

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

---
v2:
   Removed unused functions
   Removed use of access_ok()
   All input is evil
   Reworked the interface as per Benjamin Herrenschmidts vision v3:
   Removed 'default y' from Kconfig
   Reordered ioctl() struct fields
   Reworeded some comments
v4:
   Reorder ioctl() struct fields (again)
v5:
   Style cleanups
   Use of_address_to_resource()
   Document ioctl structure fields
v6:
   Check and fail ioctl() if flags field non-zero

 drivers/misc/Kconfig                 |   8 ++ drivers/misc/Makefile                |   1 + drivers/misc/aspeed-lpc-ctrl.c       | 267
+++++++++++++++++++++++++++++++++++
 include/uapi/linux/aspeed-lpc-ctrl.h |  60 ++++++++

 4 files changed, 336 insertions(+)

 create mode 100644 drivers/misc/aspeed-lpc-ctrl.c create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h

drivers/misc/Kconfig +0 -0
index 64971baf11fa..7dc4c369012f 100644
@@ -766,6 +766,14 @@ config PANEL_BOOT_MESSAGE

    An empty message will only clear the display at driver init time. Any other
    printf()-formatted message is valid with newline and escape codes.

drivers/misc/Kconfig +2 -0
+config ASPEED_LPC_CTRL
+	depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP &&

MFD_SYSCON

drivers/misc/Kconfig +1 -0
+	tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge

control"

drivers/misc/Kconfig +2 -0
+	---help---
+	  Control Aspeed ast2400/2500 HOST LPC to BMC mappings

through

drivers/misc/Kconfig +1 -0
+	  ioctl()s, the driver also provides a read/write interface

to a BMC ram

drivers/misc/Kconfig +1 -0
+	  region where the host LPC read/write region can be

buffered.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"

drivers/misc/Makefile +0 -0
index 31983366090a..de1925a9c80b 100644
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO)		+= echo/

 obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE) += cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o

 lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o

drivers/misc/aspeed- renamed from drivers/misc/aspeed-lpc-ctrl.c +0 -0

lpc-ctrl.c
new file mode 100644
index 000000000000..f6acbe1d9378

drivers/misc/aspeed-lpc-ctrl.c new file +35 -0
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME	"aspeed-lpc-ctrl"
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+	struct miscdevice	miscdev;
+	struct regmap		*regmap;
+	phys_addr_t		mem_base;
+	resource_size_t		mem_size;
+	u32		pnor_size;
+	u32		pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file

*file)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	return container_of(file->private_data, struct

aspeed_lpc_ctrl,

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+			miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct

vm_area_struct *vma)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	unsigned long vsize = vma->vm_end - vma->vm_start;
+	pgprot_t prot = vma->vm_page_prot;
+
+	if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl-
Quoted 1 line

mem_size)

drivers/misc/aspeed-lpc-ctrl.c new file +14 -0
+		return -EINVAL;
+
+	/* ast2400/2500 AHB accesses are not cache coherent */
+	prot = pgprot_dmacoherent(prot);
+
+	if (remap_pfn_range(vma, vma->vm_start,
+		(lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
+		vsize, prot))
+		return -EAGAIN;
+
+	return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int

cmd,

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		unsigned long param)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +16 -0
+	void __user *p = (void __user *)param;
+	struct aspeed_lpc_ctrl_mapping map;
+	u32 addr;
+	u32 size;
+	long rc;
+
+	if (copy_from_user(&map, p, sizeof(map)))
+		return -EFAULT;
+
+	if (map.flags != 0)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+		/* The flash windows don't report their size */
+		if (map.window_type !=

ASPEED_LPC_CTRL_WINDOW_MEMORY)

drivers/misc/aspeed-lpc-ctrl.c new file +9 -0
+			return -EINVAL;
+
+		/* Support more than one window id in the future */
+		if (map.window_id != 0)
+			return -EINVAL;
+
+		map.size = lpc_ctrl->mem_size;
+
+		return copy_to_user(p, &map, sizeof(map)) ? -EFAULT

: 0;

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+		/*
+		 * The top half of HICR7 is the MSB of the BMC

address of the

drivers/misc/aspeed-lpc-ctrl.c line 94

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * mapping.

How about: HICR7[31:16] is the [offset into BMC memory/most significant two bytes fo the BMC address] of the mapping.

Quoted 2 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The bottom half of HICR7 is

the MSB of the HOST LPC

HICR7[15:0] is the [offset on the HOST LPC firmware address space/most fisnificant two bytes of the HOST LPC firmware address space] of the mapping

drivers/misc/aspeed-lpc-ctrl.c line 100

Quoted 7 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		 * firmware space address of the mapping.
+		 *
+		 * The 1 bits in the top of half of HICR8 represent

the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be ignored

and

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * replaced with those from the top half of HICR7.

HICR8[31:16] represents a mask of the bits which should be taken from HICR7[31:16] and placed in the BMC address.

drivers/misc/aspeed-lpc-ctrl.c line 103

Quoted 5 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The 1 bits in the bottom half of HICR8 represent

the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be kept

and pass

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * into the BMC address space.

HICR8[15:0] represents a mask of the bits which should be taken from LPC_ADDR[31:16] and placed in the BMC address.

If I understand correctly, we basically end up with:

BMC_ADDR[31:0] = (BMC_OFFSET + LPC_OFFSET + INDEX) << 16 + OFFSET where:
BMC_OFFSET = HICR7[31:16] & HICR8[31:16] - The offset into bmc memory of where the window is.
LPC_OFFSET = LPC_ADDR[31:16] - HICR7[15:0] - The offset on the lpc bus of where we created the window.
INDEX = LPC_ADDR[31:16] & HICR8[15:0] - High bits of the lpc address which we use in the bmc address.
OFFSET = LPC_ADDR[15:0] - Low bits of the lpc address which we use in the bmc address.

drivers/misc/aspeed-lpc-ctrl.c

Quoted 13 lines
drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+		 */
+
+		/*
+		 * It doesn't make sense to talk about a size or

offset with

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * low 16 bits set. Both HICR7 and HICR8 talk about

the top 16

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+		 * bits of addresses and sizes.
+		 */
+
+		if ((map.size & 0x0000ffff) || (map.offset &

0x0000ffff))

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+			return -EINVAL;

Am I missing something? Are you checking anywhere that map.size is power-of-2 aligned?

Quoted 44 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+
+		/*
+		 * Because of the way the masks work in HICR8 offset

has to

drivers/misc/aspeed-lpc-ctrl.c new file +6 -0
+		 * be a multiple of size.
+		 */
+		if (map.offset & (map.size - 1))
+			return -EINVAL;
+
+		if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH)

{

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+			addr = lpc_ctrl->pnor_base;
+			size = lpc_ctrl->pnor_size;
+		} else if (map.window_type ==

ASPEED_LPC_CTRL_WINDOW_MEMORY) {

drivers/misc/aspeed-lpc-ctrl.c new file +18 -0
+			addr = lpc_ctrl->mem_base;
+			size = lpc_ctrl->mem_size;
+		} else {
+			return -EINVAL;
+		}
+
+		/* Check overflow first! */
+		if (map.offset + map.size < map.offset ||
+			map.offset + map.size > size)
+			return -EINVAL;
+
+		if (map.size == 0 || map.size > size)
+			return -EINVAL;
+
+		addr += map.offset;
+
+		/*
+		 * addr (host lpc address) is safe regardless of

values. This

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * simply changes the address the host has to

request on its

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * side of the LPC bus. This cannot impact the hosts

own

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * memory space by surprise as LPC specific

accessors are

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * required. The only strange thing that could be

done is

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * setting the lower 16 bits but the shift takes

care of that.

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 */

Don't you have to explicitly check that the bits 15:0 of map.addr aren't set? Otherwise something could ask to map LPC address 0x1234 (map.addr = 0x1234), you will set HICR7[15:0] = 0 (map.addr >> 16). So when address 0x1234 is accessed on the lpc bus, this will map to bmc address 0x1234 instead of the expected 0x0 (since this is the base lpc address)?

Or do we preclude the low 16 bits of map.addr from being set somewhere else?

Quoted 157 lines
drivers/misc/aspeed-lpc-ctrl.c new file +43 -0
+
+		rc = regmap_write(lpc_ctrl->regmap, HICR7,
+				(addr | (map.addr >> 16)));
+		if (rc)
+			return rc;
+
+		return regmap_write(lpc_ctrl->regmap, HICR8,
+			(~(map.size - 1)) | ((map.size >> 16) - 1));
+	}
+
+	return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+	.owner		= THIS_MODULE,
+	.mmap		= aspeed_lpc_ctrl_mmap,
+	.unlocked_ioctl	= aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl;
+	struct device_node *node;
+	struct resource resm;
+	struct device *dev;
+	int rc;
+
+	dev = &pdev->dev;
+
+	lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
+	if (!lpc_ctrl)
+		return -ENOMEM;
+
+	node = of_parse_phandle(dev->of_node, "flash", 0);
+	if (!node) {
+		dev_err(dev, "Didn't find host pnor flash node\n");
+		return -ENODEV;
+	}
+
+	rc = of_address_to_resource(node, 1, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

flash\n");

drivers/misc/aspeed-lpc-ctrl.c new file +18 -0
+		return rc;
+	}
+
+	lpc_ctrl->pnor_size = resource_size(&resm);
+	lpc_ctrl->pnor_base = resm.start;
+
+	dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+	node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (!node) {
+		dev_err(dev, "Didn't find reserved memory\n");
+		return -EINVAL;
+	}
+
+	rc = of_address_to_resource(node, 0, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

reserved memory\n");

drivers/misc/aspeed-lpc-ctrl.c new file +30 -0
+		return -ENOMEM;
+	}
+
+	lpc_ctrl->mem_size = resource_size(&resm);
+	lpc_ctrl->mem_base = resm.start;
+
+	lpc_ctrl->regmap = syscon_node_to_regmap(
+			pdev->dev.parent->of_node);
+	if (IS_ERR(lpc_ctrl->regmap)) {
+		dev_err(dev, "Couldn't get regmap\n");
+		return -ENODEV;
+	}
+
+	lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+	lpc_ctrl->miscdev.name = DEVICE_NAME;
+	lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+	lpc_ctrl->miscdev.parent = dev;
+	rc = misc_register(&lpc_ctrl->miscdev);
+	if (rc)
+		dev_err(dev, "Unable to register device\n");
+	else
+		dev_info(dev, "Loaded at 0x%08x (0x%08x)\n",
+			lpc_ctrl->mem_base, lpc_ctrl->mem_size);
+
+	return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev-
Quoted 1 line

dev);

drivers/misc/aspeed-lpc-ctrl.c new file +27 -0
+
+	misc_deregister(&lpc_ctrl->miscdev);
+
+	return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+	{ .compatible = "aspeed,ast2400-lpc-ctrl" },
+	{ .compatible = "aspeed,ast2500-lpc-ctrl" },
+	{ },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+	.driver = {
+		.name		= DEVICE_NAME,
+		.of_match_table = aspeed_lpc_ctrl_match,
+	},
+	.probe = aspeed_lpc_ctrl_probe,
+	.remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <[email protected]>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC

mappings");

include/uapi/linux/aspeed-lpc-ctrl.h new file +20 -0
new file mode 100644
index 000000000000..f96fa995a3f0
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _UAPI_LINUX_ASPEED_LPC_CTRL_H
+#define _UAPI_LINUX_ASPEED_LPC_CTRL_H
+
+#include <linux/ioctl.h>
+
+/* Window types */
+#define ASPEED_LPC_CTRL_WINDOW_FLASH	1
+#define ASPEED_LPC_CTRL_WINDOW_MEMORY	2
+
+/*
+ * This driver provides a window for the host to access a BMC

resource
+ * across the BMC <-> Host LPC bus.
+ *
+ * window_type: The BMC resource that the host will access through the
+ * window. BMC flash and BMC RAM.
+ *
+ * window_id: For each window type there may be multiple windows,

Quoted 8 lines

+ * these are referenced by ID.
+ *
+ * flags: Reserved for future use, this field is expected to be
+ * zeroed.
+ *
+ * addr: Address on the host LPC bus that the specified window should
+ * be mapped. This address must be power of two aligned.

If I understand correctly -> AND a multiple of size?

Quoted 29 lines

+ *
+ * offset: Offset into the BMC window that should be mapped to the
+ * host (at addr). This must be a multiple of size.
+ *
+ * size: The size of the mapping. The smallest possible size is 64K.
+ * This must be power of two aligned.
+ *
+ */

include/uapi/linux/aspeed-lpc-ctrl.h new file +13 -0
+
+struct aspeed_lpc_ctrl_mapping {
+	__u8	window_type;
+	__u8	window_id;
+	__u16	flags;
+	__u32	addr;
+	__u32	offset;
+	__u32	size;
+};
+
+#define __ASPEED_LPC_CTRL_IOCTL_MAGIC	0xb2
+
+#define ASPEED_LPC_CTRL_IOCTL_GET_SIZE	_IOWR(__ASPEED_LPC_CTR

L_IOCTL_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x00, struct aspeed_lpc_ctrl_mapping)
+
+#define ASPEED_LPC_CTRL_IOCTL_MAP	_IOW(__ASPEED_LPC_CTRL_IOCT

L_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x01, struct aspeed_lpc_ctrl_mapping)
+
+#endif /* _UAPI_LINUX_ASPEED_LPC_CTRL_H */
author
#5 lore raw
Quoted 239 lines · Suraj Jitindar Singh

On Fri, 2017-02-17 at 14:28 +1100, Cyril Bur wrote:

I may be too late, but see below...

Quoted 236 lines

In order to manage server systems, there is typically another processor
known as a BMC (Baseboard Management Controller) which is responsible for powering the server and other various elements, sometimes fans, often the system flash.

The Aspeed BMC family which is what is used on OpenPOWER machines and a
number of x86 as well is typically connected to the host via an LPC (Low Pin Count) bus (among others).

The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW
cycles) that contains the BIOS or other host firmware along with a number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers.

On the BMC chip side, this is all configured via a bunch of registers whose content is related to a given policy of what devices are exposed
at a per system level, which is system/vendor specific, so we don't want
to bolt that into the BMC kernel. This started with a need to provide something nicer than /dev/mem for user space to configure these things.

One important aspect of the configuration is how the MEM/FW space is exposed to the host (ie, the x86 or POWER). Some registers in that bridge can define a window remapping all or portion of the LPC MEM/FW space to a portion of the BMC internal bus, with no specific limits imposed in HW.

I think it makes sense to ensure that this window is configured by a kernel driver that can apply some serious sanity checks on what it is configured to map.

In practice, user space wants to control this by flipping the mapping between essentially two types of portions of the BMC address space:

   - The flash space. This is a region of the BMC MMIO space that more/less directly maps the system flash (at least for reads, writes are somewhat more complicated).

   - One (or more) reserved area(s) of the BMC physical memory.

The latter is needed for a number of things, such as avoiding letting the host manipulate the innards of the BMC flash controller via some evil backdoor, we want to do flash updates by routing the window to a portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data in
bulk and then request the BMC to flash it. There are other uses, such as allowing the host to boot from an in-memory flash image rather than
the one in flash (very handy for continuous integration and test, the BMC can just download new images).

It is important to note that due to the way the Aspeed chip lets the kernel configure the mapping between host LPC addresses and BMC ram addresses the offset within the window must be a multiple of size. Not doing so will fragment the accessible space rather than simply moving 'zero' upwards. This is caused by the nature of HICR8 being a mask and the way host LPC addresses are translated.

---
v2:
   Removed unused functions
   Removed use of access_ok()
   All input is evil
   Reworked the interface as per Benjamin Herrenschmidts vision v3:
   Removed 'default y' from Kconfig
   Reordered ioctl() struct fields
   Reworeded some comments
v4:
   Reorder ioctl() struct fields (again)
v5:
   Style cleanups
   Use of_address_to_resource()
   Document ioctl structure fields
v6:
   Check and fail ioctl() if flags field non-zero

 drivers/misc/Kconfig                 |   8 ++ drivers/misc/Makefile                |   1 + drivers/misc/aspeed-lpc-ctrl.c       | 267
+++++++++++++++++++++++++++++++++++
 include/uapi/linux/aspeed-lpc-ctrl.h |  60 ++++++++

 4 files changed, 336 insertions(+)

 create mode 100644 drivers/misc/aspeed-lpc-ctrl.c create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h

drivers/misc/Kconfig +0 -0
index 64971baf11fa..7dc4c369012f 100644
@@ -766,6 +766,14 @@ config PANEL_BOOT_MESSAGE

    An empty message will only clear the display at driver init time. Any other
    printf()-formatted message is valid with newline and escape codes.

drivers/misc/Kconfig +2 -0
+config ASPEED_LPC_CTRL
+	depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP &&

MFD_SYSCON

drivers/misc/Kconfig +1 -0
+	tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge

control"

drivers/misc/Kconfig +2 -0
+	---help---
+	  Control Aspeed ast2400/2500 HOST LPC to BMC mappings

through

drivers/misc/Kconfig +1 -0
+	  ioctl()s, the driver also provides a read/write interface

to a BMC ram

drivers/misc/Kconfig +1 -0
+	  region where the host LPC read/write region can be

buffered.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"

drivers/misc/Makefile +0 -0
index 31983366090a..de1925a9c80b 100644
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO)		+= echo/

 obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE) += cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o

 lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o

drivers/misc/aspeed- renamed from drivers/misc/aspeed-lpc-ctrl.c +0 -0

lpc-ctrl.c
new file mode 100644
index 000000000000..f6acbe1d9378

drivers/misc/aspeed-lpc-ctrl.c new file +35 -0
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME	"aspeed-lpc-ctrl"
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+	struct miscdevice	miscdev;
+	struct regmap		*regmap;
+	phys_addr_t		mem_base;
+	resource_size_t		mem_size;
+	u32		pnor_size;
+	u32		pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file

*file)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	return container_of(file->private_data, struct

aspeed_lpc_ctrl,

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+			miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct

vm_area_struct *vma)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	unsigned long vsize = vma->vm_end - vma->vm_start;
+	pgprot_t prot = vma->vm_page_prot;
+
+	if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl-
Quoted 1 line

mem_size)

drivers/misc/aspeed-lpc-ctrl.c new file +14 -0
+		return -EINVAL;
+
+	/* ast2400/2500 AHB accesses are not cache coherent */
+	prot = pgprot_dmacoherent(prot);
+
+	if (remap_pfn_range(vma, vma->vm_start,
+		(lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
+		vsize, prot))
+		return -EAGAIN;
+
+	return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int

cmd,

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		unsigned long param)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +16 -0
+	void __user *p = (void __user *)param;
+	struct aspeed_lpc_ctrl_mapping map;
+	u32 addr;
+	u32 size;
+	long rc;
+
+	if (copy_from_user(&map, p, sizeof(map)))
+		return -EFAULT;
+
+	if (map.flags != 0)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+		/* The flash windows don't report their size */
+		if (map.window_type !=

ASPEED_LPC_CTRL_WINDOW_MEMORY)

drivers/misc/aspeed-lpc-ctrl.c new file +8 -0
+			return -EINVAL;
+
+		/* Support more than one window id in the future */
+		if (map.window_id != 0)
+			return -EINVAL;
+
+		map.size = lpc_ctrl->mem_size;
+
Quoted 9 lines

drivers/misc/aspeed-lpc-ctrl.c line 94

Quoted 8 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		return copy_to_user(p, &map, sizeof(map)) ? -EFAULT

: 0;

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+		/*
+		 * The top half of HICR7 is the MSB of the BMC

address of the

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * mapping.

Hi Suraj,

I'm all for writing this out as many times as possible to be sure everyone understands it. I think a visual diagram would be fantastic, I'd love to do one in my infinite spare time. It may be useful to link to Benhs comments in skiboot for another wording of how these two registers work skiboot/hw/ast-bmc/ast-io.c.

Quoted 8 lines

How about: HICR7[31:16] is the [offset into BMC memory/most significant two bytes fo the BMC address] of the mapping.

Quoted 2 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The bottom half of HICR7 is

the MSB of the HOST LPC

HICR7[15:0] is the [offset on the HOST LPC firmware address space/most fisnificant two bytes of the HOST LPC firmware address space] of the mapping

Yes, this appears correct.

Quoted 10 lines

drivers/misc/aspeed-lpc-ctrl.c line 100

Quoted 7 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		 * firmware space address of the mapping.
+		 *
+		 * The 1 bits in the top of half of HICR8 represent

the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be ignored

and

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * replaced with those from the top half of HICR7.

HICR8[31:16] represents a mask of the bits which should be taken from HICR7[31:16] and placed in the BMC address.

Yes.

Quoted 21 lines

drivers/misc/aspeed-lpc-ctrl.c line 103

Quoted 5 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The 1 bits in the bottom half of HICR8 represent

the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be kept

and pass

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * into the BMC address space.

HICR8[15:0] represents a mask of the bits which should be taken from LPC_ADDR[31:16] and placed in the BMC address.

If I understand correctly, we basically end up with:

BMC_ADDR[31:0] = (BMC_OFFSET + LPC_OFFSET + INDEX) << 16 + OFFSET where:
BMC_OFFSET = HICR7[31:16] & HICR8[31:16] - The offset into bmc memory of where the window is.
LPC_OFFSET = LPC_ADDR[31:16] - HICR7[15:0] - The offset on the lpc bus of where we created the window.
INDEX = LPC_ADDR[31:16] & HICR8[15:0] - High bits of the lpc address which we use in the bmc address.
OFFSET = LPC_ADDR[15:0] - Low bits of the lpc address which we use in the bmc address.

Quoted 1 line

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 */

This sounds right to me.

Quoted 7 lines
Quoted 7 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+
+		/*
+		 * It doesn't make sense to talk about a size or

offset with

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * low 16 bits set. Both HICR7 and HICR8 talk about

the top 16

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * bits of addresses and sizes.
Quoted 9 lines

drivers/misc/aspeed-lpc-ctrl.c

Quoted 5 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		 */
+
+		if ((map.size & 0x0000ffff) || (map.offset &

0x0000ffff))

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+			return -EINVAL;

Am I missing something? Are you checking anywhere that map.size is power-of-2 aligned?

Quoted 1 line

+

Not explicitly - I didn't want to be too restrictive. The conditions only protect against exploits. Any other values should *work* but produce interesting results. For example, it would be possible to map the same 64K BMC region to multiple 64K regions on the host - why one would do that I have no idea but you can.

Quoted 43 lines
Quoted 43 lines
drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+		/*
+		 * Because of the way the masks work in HICR8 offset

has to

drivers/misc/aspeed-lpc-ctrl.c new file +6 -0
+		 * be a multiple of size.
+		 */
+		if (map.offset & (map.size - 1))
+			return -EINVAL;
+
+		if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH)

{

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+			addr = lpc_ctrl->pnor_base;
+			size = lpc_ctrl->pnor_size;
+		} else if (map.window_type ==

ASPEED_LPC_CTRL_WINDOW_MEMORY) {

drivers/misc/aspeed-lpc-ctrl.c new file +18 -0
+			addr = lpc_ctrl->mem_base;
+			size = lpc_ctrl->mem_size;
+		} else {
+			return -EINVAL;
+		}
+
+		/* Check overflow first! */
+		if (map.offset + map.size < map.offset ||
+			map.offset + map.size > size)
+			return -EINVAL;
+
+		if (map.size == 0 || map.size > size)
+			return -EINVAL;
+
+		addr += map.offset;
+
+		/*
+		 * addr (host lpc address) is safe regardless of

values. This

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * simply changes the address the host has to

request on its

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * side of the LPC bus. This cannot impact the hosts

own

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * memory space by surprise as LPC specific

accessors are

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * required. The only strange thing that could be

done is

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * setting the lower 16 bits but the shift takes

care of that.

Quoted 9 lines

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 */

Don't you have to explicitly check that the bits 15:0 of map.addr aren't set? Otherwise something could ask to map LPC address 0x1234 (map.addr = 0x1234), you will set HICR7[15:0] = 0 (map.addr >> 16). So when address 0x1234 is accessed on the lpc bus, this will map to bmc address 0x1234 instead of the expected 0x0 (since this is the base lpc address)?

Yeah I didn't check, as you say the 0x1234 get thrown away by the shift. It appears I forgot to explicitly say that that the smallest valid value is 64K, I did say that about the size. I'd be surprised if a user didn't know this already but if you feel strongly about it I can resend with an error condition for it.

Thanks,

Cyril

Quoted 199 lines

Or do we preclude the low 16 bits of map.addr from being set somewhere else?

Quoted 158 lines
drivers/misc/aspeed-lpc-ctrl.c new file +43 -0
+
+		rc = regmap_write(lpc_ctrl->regmap, HICR7,
+				(addr | (map.addr >> 16)));
+		if (rc)
+			return rc;
+
+		return regmap_write(lpc_ctrl->regmap, HICR8,
+			(~(map.size - 1)) | ((map.size >> 16) - 1));
+	}
+
+	return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+	.owner		= THIS_MODULE,
+	.mmap		= aspeed_lpc_ctrl_mmap,
+	.unlocked_ioctl	= aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl;
+	struct device_node *node;
+	struct resource resm;
+	struct device *dev;
+	int rc;
+
+	dev = &pdev->dev;
+
+	lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
+	if (!lpc_ctrl)
+		return -ENOMEM;
+
+	node = of_parse_phandle(dev->of_node, "flash", 0);
+	if (!node) {
+		dev_err(dev, "Didn't find host pnor flash node\n");
+		return -ENODEV;
+	}
+
+	rc = of_address_to_resource(node, 1, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

flash\n");

drivers/misc/aspeed-lpc-ctrl.c new file +18 -0
+		return rc;
+	}
+
+	lpc_ctrl->pnor_size = resource_size(&resm);
+	lpc_ctrl->pnor_base = resm.start;
+
+	dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+	node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (!node) {
+		dev_err(dev, "Didn't find reserved memory\n");
+		return -EINVAL;
+	}
+
+	rc = of_address_to_resource(node, 0, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

reserved memory\n");

drivers/misc/aspeed-lpc-ctrl.c new file +30 -0
+		return -ENOMEM;
+	}
+
+	lpc_ctrl->mem_size = resource_size(&resm);
+	lpc_ctrl->mem_base = resm.start;
+
+	lpc_ctrl->regmap = syscon_node_to_regmap(
+			pdev->dev.parent->of_node);
+	if (IS_ERR(lpc_ctrl->regmap)) {
+		dev_err(dev, "Couldn't get regmap\n");
+		return -ENODEV;
+	}
+
+	lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+	lpc_ctrl->miscdev.name = DEVICE_NAME;
+	lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+	lpc_ctrl->miscdev.parent = dev;
+	rc = misc_register(&lpc_ctrl->miscdev);
+	if (rc)
+		dev_err(dev, "Unable to register device\n");
+	else
+		dev_info(dev, "Loaded at 0x%08x (0x%08x)\n",
+			lpc_ctrl->mem_base, lpc_ctrl->mem_size);
+
+	return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev-
Quoted 1 line

dev);

drivers/misc/aspeed-lpc-ctrl.c new file +27 -0
+
+	misc_deregister(&lpc_ctrl->miscdev);
+
+	return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+	{ .compatible = "aspeed,ast2400-lpc-ctrl" },
+	{ .compatible = "aspeed,ast2500-lpc-ctrl" },
+	{ },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+	.driver = {
+		.name		= DEVICE_NAME,
+		.of_match_table = aspeed_lpc_ctrl_match,
+	},
+	.probe = aspeed_lpc_ctrl_probe,
+	.remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <[email protected]>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC

mappings");

include/uapi/linux/aspeed-lpc-ctrl.h new file +20 -0
new file mode 100644
index 000000000000..f96fa995a3f0
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _UAPI_LINUX_ASPEED_LPC_CTRL_H
+#define _UAPI_LINUX_ASPEED_LPC_CTRL_H
+
+#include <linux/ioctl.h>
+
+/* Window types */
+#define ASPEED_LPC_CTRL_WINDOW_FLASH	1
+#define ASPEED_LPC_CTRL_WINDOW_MEMORY	2
+
+/*
+ * This driver provides a window for the host to access a BMC

resource
+ * across the BMC <-> Host LPC bus.
+ *
+ * window_type: The BMC resource that the host will access through the
+ * window. BMC flash and BMC RAM.
+ *
+ * window_id: For each window type there may be multiple windows,

Quoted 8 lines

+ * these are referenced by ID.
+ *
+ * flags: Reserved for future use, this field is expected to be
+ * zeroed.
+ *
+ * addr: Address on the host LPC bus that the specified window should
+ * be mapped. This address must be power of two aligned.

If I understand correctly -> AND a multiple of size?

Quoted 29 lines

+ *
+ * offset: Offset into the BMC window that should be mapped to the
+ * host (at addr). This must be a multiple of size.
+ *
+ * size: The size of the mapping. The smallest possible size is 64K.
+ * This must be power of two aligned.
+ *
+ */

include/uapi/linux/aspeed-lpc-ctrl.h new file +13 -0
+
+struct aspeed_lpc_ctrl_mapping {
+	__u8	window_type;
+	__u8	window_id;
+	__u16	flags;
+	__u32	addr;
+	__u32	offset;
+	__u32	size;
+};
+
+#define __ASPEED_LPC_CTRL_IOCTL_MAGIC	0xb2
+
+#define ASPEED_LPC_CTRL_IOCTL_GET_SIZE	_IOWR(__ASPEED_LPC_CTR

L_IOCTL_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x00, struct aspeed_lpc_ctrl_mapping)
+
+#define ASPEED_LPC_CTRL_IOCTL_MAP	_IOW(__ASPEED_LPC_CTRL_IOCT

L_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x01, struct aspeed_lpc_ctrl_mapping)
+
+#endif /* _UAPI_LINUX_ASPEED_LPC_CTRL_H */
#6 lore raw
Quoted 443 lines · Cyril Bur
Quoted 276 lines · Suraj Jitindar Singh

On Fri, 2017-02-17 at 14:28 +1100, Cyril Bur wrote:

I may be too late, but see below...

Quoted 272 lines

In order to manage server systems, there is typically another processor
known as a BMC (Baseboard Management Controller) which is responsible
for powering the server and other various elements, sometimes fans,
often the system flash.

The Aspeed BMC family which is what is used on OpenPOWER machines and
a
number of x86 as well is typically connected to the host via an LPC
(Low Pin Count) bus (among others).

The LPC bus is an ISA bus on steroids. It's generally used by the BMC chip to provide the host with access to the system flash (via MEM/FW
cycles) that contains the BIOS or other host firmware along with a
number of SuperIO-style IOs (via IO space) such as UARTs, IPMI controllers.

On the BMC chip side, this is all configured via a bunch of registers
whose content is related to a given policy of what devices are exposed
at a per system level, which is system/vendor specific, so we don't
want
to bolt that into the BMC kernel. This started with a need to provide
something nicer than /dev/mem for user space to configure these things.

One important aspect of the configuration is how the MEM/FW space is
exposed to the host (ie, the x86 or POWER). Some registers in that
bridge can define a window remapping all or portion of the LPC MEM/FW
space to a portion of the BMC internal bus, with no specific limits
imposed in HW.

I think it makes sense to ensure that this window is configured by a
kernel driver that can apply some serious sanity checks on what it is
configured to map.

In practice, user space wants to control this by flipping the mapping
between essentially two types of portions of the BMC address space:

   - The flash space. This is a region of the BMC MMIO space that more/less directly maps the system flash (at least for reads, writes
are somewhat more complicated).

   - One (or more) reserved area(s) of the BMC physical memory.

The latter is needed for a number of things, such as avoiding letting
the host manipulate the innards of the BMC flash controller via some
evil backdoor, we want to do flash updates by routing the window to a
portion of memory (under control of a mailbox protocol via some separate set of registers) which the host can use to write new data
in
bulk and then request the BMC to flash it. There are other uses, such
as allowing the host to boot from an in-memory flash image rather than
the one in flash (very handy for continuous integration and test, the
BMC can just download new images).

It is important to note that due to the way the Aspeed chip lets the
kernel configure the mapping between host LPC addresses and BMC ram
addresses the offset within the window must be a multiple of size.
Not doing so will fragment the accessible space rather than simply
moving 'zero' upwards. This is caused by the nature of HICR8 being a
mask and the way host LPC addresses are translated.

---
v2:
   Removed unused functions
   Removed use of access_ok()
   All input is evil
   Reworked the interface as per Benjamin Herrenschmidts vision v3:
   Removed 'default y' from Kconfig
   Reordered ioctl() struct fields
   Reworeded some comments
v4:
   Reorder ioctl() struct fields (again)
v5:
   Style cleanups
   Use of_address_to_resource()
   Document ioctl structure fields
v6:
   Check and fail ioctl() if flags field non-zero

 drivers/misc/Kconfig                 |   8 ++ drivers/misc/Makefile                |   1 + drivers/misc/aspeed-lpc-ctrl.c       | 267
+++++++++++++++++++++++++++++++++++
 include/uapi/linux/aspeed-lpc-ctrl.h |  60 ++++++++

 4 files changed, 336 insertions(+)

 create mode 100644 drivers/misc/aspeed-lpc-ctrl.c create mode 100644 include/uapi/linux/aspeed-lpc-ctrl.h

drivers/misc/Kconfig +0 -0
index 64971baf11fa..7dc4c369012f 100644
@@ -766,6 +766,14 @@ config PANEL_BOOT_MESSAGE

    An empty message will only clear the display at driver init time. Any other
    printf()-formatted message is valid with newline and escape codes.

drivers/misc/Kconfig +2 -0
+config ASPEED_LPC_CTRL
+	depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP &&

MFD_SYSCON

drivers/misc/Kconfig +1 -0
+	tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge

control"

drivers/misc/Kconfig +2 -0
+	---help---
+	  Control Aspeed ast2400/2500 HOST LPC to BMC mappings

through

drivers/misc/Kconfig +1 -0
+	  ioctl()s, the driver also provides a read/write

interface
to a BMC ram

drivers/misc/Kconfig +1 -0
+	  region where the host LPC read/write region can be

buffered.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"

drivers/misc/Makefile +0 -0
index 31983366090a..de1925a9c80b 100644
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO)		+= echo/

 obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE) += cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o

 lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o

drivers/misc/aspeed- renamed from drivers/misc/aspeed-lpc-ctrl.c +0 -0

lpc-ctrl.c
new file mode 100644
index 000000000000..f6acbe1d9378

drivers/misc/aspeed-lpc-ctrl.c new file +35 -0
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME	"aspeed-lpc-ctrl"
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+	struct miscdevice	miscdev;
+	struct regmap		*regmap;
+	phys_addr_t		mem_base;
+	resource_size_t		mem_size;
+	u32		pnor_size;
+	u32		pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file

*file)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	return container_of(file->private_data, struct

aspeed_lpc_ctrl,

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+			miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct

vm_area_struct *vma)

drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	unsigned long vsize = vma->vm_end - vma->vm_start;
+	pgprot_t prot = vma->vm_page_prot;
+
+	if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base +

lpc_ctrl-

Quoted 2 lines

mem_size)

drivers/misc/aspeed-lpc-ctrl.c new file +7 -0
+		return -EINVAL;
+
+	/* ast2400/2500 AHB accesses are not cache coherent */
+	prot = pgprot_dmacoherent(prot);
+
+	if (remap_pfn_range(vma, vma->vm_start,
+		(lpc_ctrl->mem_base >> PAGE_SHIFT) + vma-
Quoted 1 line

vm_pgoff,

drivers/misc/aspeed-lpc-ctrl.c new file +7 -0
+		vsize, prot))
+		return -EAGAIN;
+
+	return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned

int
cmd,

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		unsigned long param)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

file_aspeed_lpc_ctrl(file);

drivers/misc/aspeed-lpc-ctrl.c new file +16 -0
+	void __user *p = (void __user *)param;
+	struct aspeed_lpc_ctrl_mapping map;
+	u32 addr;
+	u32 size;
+	long rc;
+
+	if (copy_from_user(&map, p, sizeof(map)))
+		return -EFAULT;
+
+	if (map.flags != 0)
+		return -EINVAL;
+
+	switch (cmd) {
+	case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+		/* The flash windows don't report their size */
+		if (map.window_type !=

ASPEED_LPC_CTRL_WINDOW_MEMORY)

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+			return -EINVAL;
+
+		/* Support more than one window id in the future

*/

drivers/misc/aspeed-lpc-ctrl.c new file +6 -0
+		if (map.window_id != 0)
+			return -EINVAL;
+
+		map.size = lpc_ctrl->mem_size;
+
+		return copy_to_user(p, &map, sizeof(map)) ?
Quoted 8 lines

drivers/misc/aspeed-lpc-ctrl.c line 94

Quoted 8 lines

-EFAULT
: 0;

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+	case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+		/*
+		 * The top half of HICR7 is the MSB of the BMC

address of the

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * mapping.

Hi Suraj,

I'm all for writing this out as many times as possible to be sure everyone understands it. I think a visual diagram would be fantastic, I'd love to do one in my infinite spare time. It may be useful to link
to Benhs comments in skiboot for another wording of how these two registers work skiboot/hw/ast-bmc/ast-io.c.

Quoted 5 lines

How about: HICR7[31:16] is the [offset into BMC memory/most significant
two bytes fo the BMC address] of the mapping.

Quoted 1 line
Quoted 7 lines
Quoted 2 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The bottom half of HICR7 is

the MSB of the HOST LPC

HICR7[15:0] is the [offset on the HOST LPC firmware address space/most
fisnificant two bytes of the HOST LPC firmware address space] of the
mapping

Yes, this appears correct.

Quoted 15 lines
Quoted 11 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+		 * firmware space address of the mapping.
+		 *
+		 * The 1 bits in the top of half of HICR8

represent
the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be

ignored
and

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * replaced with those from the top half of
Quoted 1 line

HICR7.

HICR8[31:16] represents a mask of the bits which should be taken from
HICR7[31:16] and placed in the BMC address.

Yes.

Quoted 30 lines

drivers/misc/aspeed-lpc-ctrl.c line 103

Quoted 9 lines
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * The 1 bits in the bottom half of HICR8

represent
the bits

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * (in the requested address) that should be

kept
and pass

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * into the BMC address space.

HICR8[15:0] represents a mask of the bits which should be taken from
LPC_ADDR[31:16] and placed in the BMC address.

If I understand correctly, we basically end up with:

BMC_ADDR[31:0] = (BMC_OFFSET + LPC_OFFSET + INDEX) << 16 + OFFSET where:
BMC_OFFSET = HICR7[31:16] & HICR8[31:16] - The offset into bmc memory
of where the window is.
LPC_OFFSET = LPC_ADDR[31:16] - HICR7[15:0] - The offset on the lpc bus
of where we created the window.
INDEX = LPC_ADDR[31:16] & HICR8[15:0] - High bits of the lpc address
which we use in the bmc address.
OFFSET = LPC_ADDR[15:0] - Low bits of the lpc address which we use in
the bmc address.

Quoted 1 line
Quoted 1 line

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 */

This sounds right to me.

Quoted 19 lines
Quoted 14 lines
drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+
+		/*
+		 * It doesn't make sense to talk about a size or

offset with

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * low 16 bits set. Both HICR7 and HICR8 talk

about
the top 16

drivers/misc/aspeed-lpc-ctrl.c new file +4 -0
+		 * bits of addresses and sizes.
+		 */
+
+		if ((map.size & 0x0000ffff) || (map.offset &

0x0000ffff))

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+			return -EINVAL;

Am I missing something? Are you checking anywhere that map.size is power-of-2 aligned?

Quoted 2 lines

+

Not explicitly - I didn't want to be too restrictive. The conditions only protect against exploits. Any other values should *work* but produce interesting results. For example, it would be possible to map the same 64K BMC region to multiple 64K regions on the host - why one would do that I have no idea but you can.

Quoted 51 lines
Quoted 49 lines
drivers/misc/aspeed-lpc-ctrl.c new file +2 -0
+		/*
+		 * Because of the way the masks work in HICR8

offset
has to

drivers/misc/aspeed-lpc-ctrl.c new file +6 -0
+		 * be a multiple of size.
+		 */
+		if (map.offset & (map.size - 1))
+			return -EINVAL;
+
+		if (map.window_type ==

ASPEED_LPC_CTRL_WINDOW_FLASH)
{

drivers/misc/aspeed-lpc-ctrl.c new file +3 -0
+			addr = lpc_ctrl->pnor_base;
+			size = lpc_ctrl->pnor_size;
+		} else if (map.window_type ==

ASPEED_LPC_CTRL_WINDOW_MEMORY) {

drivers/misc/aspeed-lpc-ctrl.c new file +18 -0
+			addr = lpc_ctrl->mem_base;
+			size = lpc_ctrl->mem_size;
+		} else {
+			return -EINVAL;
+		}
+
+		/* Check overflow first! */
+		if (map.offset + map.size < map.offset ||
+			map.offset + map.size > size)
+			return -EINVAL;
+
+		if (map.size == 0 || map.size > size)
+			return -EINVAL;
+
+		addr += map.offset;
+
+		/*
+		 * addr (host lpc address) is safe regardless of

values. This

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * simply changes the address the host has to

request on its

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * side of the LPC bus. This cannot impact the

hosts
own

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * memory space by surprise as LPC specific

accessors are

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * required. The only strange thing that could

be
done is

drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 * setting the lower 16 bits but the shift takes

care of that.

drivers/misc/aspeed-lpc-ctrl.c

Quoted 1 line
drivers/misc/aspeed-lpc-ctrl.c new file +1 -0
+		 */

Don't you have to explicitly check that the bits 15:0 of map.addr

Quoted 9 lines

aren't set? Otherwise something could ask to map LPC address 0x1234 (map.addr = 0x1234), you will set HICR7[15:0] = 0 (map.addr >> 16). So
when address 0x1234 is accessed on the lpc bus, this will map to bmc
address 0x1234 instead of the expected 0x0 (since this is the base lpc
address)?

Quoted 5 lines

Yeah I didn't check, as you say the 0x1234 get thrown away by the shift. It appears I forgot to explicitly say that that the smallest valid value is 64K, I did say that about the size. I'd be surprised if
a user didn't know this already but if you feel strongly about it I

I don't feel that strongly... :)

Quoted 220 lines

can
resend with an error condition for it.

Thanks,

Cyril

Quoted 213 lines

Or do we preclude the low 16 bits of map.addr from being set somewhere
else?

Quoted 166 lines
drivers/misc/aspeed-lpc-ctrl.c new file +8 -0
+
+		rc = regmap_write(lpc_ctrl->regmap, HICR7,
+				(addr | (map.addr >> 16)));
+		if (rc)
+			return rc;
+
+		return regmap_write(lpc_ctrl->regmap, HICR8,
+			(~(map.size - 1)) | ((map.size >> 16) -

1));

drivers/misc/aspeed-lpc-ctrl.c new file +22 -0
+	}
+
+	return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+	.owner		= THIS_MODULE,
+	.mmap		= aspeed_lpc_ctrl_mmap,
+	.unlocked_ioctl	= aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl;
+	struct device_node *node;
+	struct resource resm;
+	struct device *dev;
+	int rc;
+
+	dev = &pdev->dev;
+
+	lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl),

GFP_KERNEL);

drivers/misc/aspeed-lpc-ctrl.c new file +6 -0
+	if (!lpc_ctrl)
+		return -ENOMEM;
+
+	node = of_parse_phandle(dev->of_node, "flash", 0);
+	if (!node) {
+		dev_err(dev, "Didn't find host pnor flash

node\n");

drivers/misc/aspeed-lpc-ctrl.c new file +7 -0
+		return -ENODEV;
+	}
+
+	rc = of_address_to_resource(node, 1, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

flash\n");

drivers/misc/aspeed-lpc-ctrl.c new file +9 -0
+		return rc;
+	}
+
+	lpc_ctrl->pnor_size = resource_size(&resm);
+	lpc_ctrl->pnor_base = resm.start;
+
+	dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+	node = of_parse_phandle(dev->of_node, "memory-region",

0);

drivers/misc/aspeed-lpc-ctrl.c new file +9 -0
+	if (!node) {
+		dev_err(dev, "Didn't find reserved memory\n");
+		return -EINVAL;
+	}
+
+	rc = of_address_to_resource(node, 0, &resm);
+	of_node_put(node);
+	if (rc) {
+		dev_err(dev, "Couldn't address to resource for

reserved memory\n");

drivers/misc/aspeed-lpc-ctrl.c new file +30 -0
+		return -ENOMEM;
+	}
+
+	lpc_ctrl->mem_size = resource_size(&resm);
+	lpc_ctrl->mem_base = resm.start;
+
+	lpc_ctrl->regmap = syscon_node_to_regmap(
+			pdev->dev.parent->of_node);
+	if (IS_ERR(lpc_ctrl->regmap)) {
+		dev_err(dev, "Couldn't get regmap\n");
+		return -ENODEV;
+	}
+
+	lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+	lpc_ctrl->miscdev.name = DEVICE_NAME;
+	lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+	lpc_ctrl->miscdev.parent = dev;
+	rc = misc_register(&lpc_ctrl->miscdev);
+	if (rc)
+		dev_err(dev, "Unable to register device\n");
+	else
+		dev_info(dev, "Loaded at 0x%08x (0x%08x)\n",
+			lpc_ctrl->mem_base, lpc_ctrl->mem_size);
+
+	return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+	struct aspeed_lpc_ctrl *lpc_ctrl =

dev_get_drvdata(&pdev-

Quoted 2 lines

dev);

drivers/misc/aspeed-lpc-ctrl.c new file +27 -0
+
+	misc_deregister(&lpc_ctrl->miscdev);
+
+	return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+	{ .compatible = "aspeed,ast2400-lpc-ctrl" },
+	{ .compatible = "aspeed,ast2500-lpc-ctrl" },
+	{ },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+	.driver = {
+		.name		= DEVICE_NAME,
+		.of_match_table = aspeed_lpc_ctrl_match,
+	},
+	.probe = aspeed_lpc_ctrl_probe,
+	.remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <[email protected]>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC

mappings");

include/uapi/linux/aspeed-lpc-ctrl.h new file +20 -0
new file mode 100644
index 000000000000..f96fa995a3f0
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 IBM Corp.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _UAPI_LINUX_ASPEED_LPC_CTRL_H
+#define _UAPI_LINUX_ASPEED_LPC_CTRL_H
+
+#include <linux/ioctl.h>
+
+/* Window types */
+#define ASPEED_LPC_CTRL_WINDOW_FLASH	1
+#define ASPEED_LPC_CTRL_WINDOW_MEMORY	2
+
+/*
+ * This driver provides a window for the host to access a BMC

resource
+ * across the BMC <-> Host LPC bus.
+ *
+ * window_type: The BMC resource that the host will access through
the
+ * window. BMC flash and BMC RAM.
+ *
+ * window_id: For each window type there may be multiple windows,

Quoted 8 lines

+ * these are referenced by ID.
+ *
+ * flags: Reserved for future use, this field is expected to be
+ * zeroed.
+ *
+ * addr: Address on the host LPC bus that the specified window should
+ * be mapped. This address must be power of two aligned.

If I understand correctly -> AND a multiple of size?

Quoted 34 lines

+ *
+ * offset: Offset into the BMC window that should be mapped to the
+ * host (at addr). This must be a multiple of size.
+ *
+ * size: The size of the mapping. The smallest possible size is 64K.
+ * This must be power of two aligned.
+ *
+ */

include/uapi/linux/aspeed-lpc-ctrl.h new file +13 -0
+
+struct aspeed_lpc_ctrl_mapping {
+	__u8	window_type;
+	__u8	window_id;
+	__u16	flags;
+	__u32	addr;
+	__u32	offset;
+	__u32	size;
+};
+
+#define __ASPEED_LPC_CTRL_IOCTL_MAGIC	0xb2
+
+#define ASPEED_LPC_CTRL_IOCTL_GET_SIZE	_IOWR(__ASPEED_LPC

_CTR
L_IOCTL_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x00, struct aspeed_lpc_ctrl_mapping)
+
+#define ASPEED_LPC_CTRL_IOCTL_MAP	_IOW(__ASPEED_LPC_CTRL_

IOCT
L_MAGIC, \

include/uapi/linux/aspeed-lpc-ctrl.h new file +3 -0
+		0x01, struct aspeed_lpc_ctrl_mapping)
+
+#endif /* _UAPI_LINUX_ASPEED_LPC_CTRL_H */