diff options
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/misc/apds990x.c | 16 | ||||
-rw-r--r-- | drivers/misc/aspeed-lpc-snoop.c | 261 | ||||
-rw-r--r-- | drivers/misc/bh1770glc.c | 2 | ||||
-rw-r--r-- | drivers/misc/mei/bus.c | 2 | ||||
-rw-r--r-- | drivers/misc/mei/hw.h | 2 | ||||
-rw-r--r-- | drivers/misc/mei/init.c | 6 | ||||
-rw-r--r-- | drivers/misc/mei/interrupt.c | 26 | ||||
-rw-r--r-- | drivers/misc/mei/mei_dev.h | 1 | ||||
-rw-r--r-- | drivers/misc/sram-exec.c | 27 |
11 files changed, 320 insertions, 32 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 07bbd4cc1852..8136dc7e863d 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -490,6 +490,14 @@ config ASPEED_LPC_CTRL 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. +config ASPEED_LPC_SNOOP + tristate "Aspeed ast2500 HOST LPC snoop support" + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON + help + Provides a driver to control the LPC snoop interface which + allows the BMC to listen on and save the data written by + the host to an arbitrary LPC I/O port. + config PCI_ENDPOINT_TEST depends on PCI select CRC32 diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 81ef3e67acc9..b0b766416306 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o +obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o diff --git a/drivers/misc/apds990x.c b/drivers/misc/apds990x.c index dfb72ecfa604..84e5b949399e 100644 --- a/drivers/misc/apds990x.c +++ b/drivers/misc/apds990x.c @@ -32,7 +32,7 @@ #include <linux/delay.h> #include <linux/wait.h> #include <linux/slab.h> -#include <linux/i2c/apds990x.h> +#include <linux/platform_data/apds990x.h> /* Register map */ #define APDS990X_ENABLE 0x00 /* Enable of states and interrupts */ @@ -841,7 +841,7 @@ static ssize_t apds990x_prox_enable_store(struct device *dev, static DEVICE_ATTR(prox0_raw_en, S_IRUGO | S_IWUSR, apds990x_prox_enable_show, apds990x_prox_enable_store); -static const char reporting_modes[][9] = {"trigger", "periodic"}; +static const char *reporting_modes[] = {"trigger", "periodic"}; static ssize_t apds990x_prox_reporting_mode_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -856,13 +856,13 @@ static ssize_t apds990x_prox_reporting_mode_store(struct device *dev, const char *buf, size_t len) { struct apds990x_chip *chip = dev_get_drvdata(dev); + int ret; - if (sysfs_streq(buf, reporting_modes[0])) - chip->prox_continuous_mode = 0; - else if (sysfs_streq(buf, reporting_modes[1])) - chip->prox_continuous_mode = 1; - else - return -EINVAL; + ret = sysfs_match_string(reporting_modes, buf); + if (ret < 0) + return ret; + + chip->prox_continuous_mode = ret; return len; } diff --git a/drivers/misc/aspeed-lpc-snoop.c b/drivers/misc/aspeed-lpc-snoop.c new file mode 100644 index 000000000000..593905565b74 --- /dev/null +++ b/drivers/misc/aspeed-lpc-snoop.c @@ -0,0 +1,261 @@ +/* + * 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Provides a simple driver to control the ASPEED LPC snoop interface which + * allows the BMC to listen on and save the data written by + * the host to an arbitrary LPC I/O port. + * + * Typically used by the BMC to "watch" host boot progress via port + * 0x80 writes made by the BIOS during the boot process. + */ + +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define DEVICE_NAME "aspeed-lpc-snoop" + +#define NUM_SNOOP_CHANNELS 2 +#define SNOOP_FIFO_SIZE 2048 + +#define HICR5 0x0 +#define HICR5_EN_SNP0W BIT(0) +#define HICR5_ENINT_SNP0W BIT(1) +#define HICR5_EN_SNP1W BIT(2) +#define HICR5_ENINT_SNP1W BIT(3) + +#define HICR6 0x4 +#define HICR6_STR_SNP0W BIT(0) +#define HICR6_STR_SNP1W BIT(1) +#define SNPWADR 0x10 +#define SNPWADR_CH0_MASK GENMASK(15, 0) +#define SNPWADR_CH0_SHIFT 0 +#define SNPWADR_CH1_MASK GENMASK(31, 16) +#define SNPWADR_CH1_SHIFT 16 +#define SNPWDR 0x14 +#define SNPWDR_CH0_MASK GENMASK(7, 0) +#define SNPWDR_CH0_SHIFT 0 +#define SNPWDR_CH1_MASK GENMASK(15, 8) +#define SNPWDR_CH1_SHIFT 8 +#define HICRB 0x80 +#define HICRB_ENSNP0D BIT(14) +#define HICRB_ENSNP1D BIT(15) + +struct aspeed_lpc_snoop { + struct regmap *regmap; + int irq; + struct kfifo snoop_fifo[NUM_SNOOP_CHANNELS]; +}; + +/* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ +static void put_fifo_with_discard(struct kfifo *fifo, u8 val) +{ + if (!kfifo_initialized(fifo)) + return; + if (kfifo_is_full(fifo)) + kfifo_skip(fifo); + kfifo_put(fifo, val); +} + +static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) +{ + struct aspeed_lpc_snoop *lpc_snoop = arg; + u32 reg, data; + + if (regmap_read(lpc_snoop->regmap, HICR6, ®)) + return IRQ_NONE; + + /* Check if one of the snoop channels is interrupting */ + reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); + if (!reg) + return IRQ_NONE; + + /* Ack pending IRQs */ + regmap_write(lpc_snoop->regmap, HICR6, reg); + + /* Read and save most recent snoop'ed data byte to FIFO */ + regmap_read(lpc_snoop->regmap, SNPWDR, &data); + + if (reg & HICR6_STR_SNP0W) { + u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; + + put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val); + } + if (reg & HICR6_STR_SNP1W) { + u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; + + put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val); + } + + return IRQ_HANDLED; +} + +static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc; + + lpc_snoop->irq = platform_get_irq(pdev, 0); + if (!lpc_snoop->irq) + return -ENODEV; + + rc = devm_request_irq(dev, lpc_snoop->irq, + aspeed_lpc_snoop_irq, IRQF_SHARED, + DEVICE_NAME, lpc_snoop); + if (rc < 0) { + dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); + lpc_snoop->irq = 0; + return rc; + } + + return 0; +} + +static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, + int channel, u16 lpc_port) +{ + int rc = 0; + u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; + + /* Create FIFO datastructure */ + rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel], + SNOOP_FIFO_SIZE, GFP_KERNEL); + if (rc) + return rc; + + /* Enable LPC snoop channel at requested port */ + switch (channel) { + case 0: + hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; + snpwadr_mask = SNPWADR_CH0_MASK; + snpwadr_shift = SNPWADR_CH0_SHIFT; + hicrb_en = HICRB_ENSNP0D; + break; + case 1: + hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; + snpwadr_mask = SNPWADR_CH1_MASK; + snpwadr_shift = SNPWADR_CH1_SHIFT; + hicrb_en = HICRB_ENSNP1D; + break; + default: + return -EINVAL; + } + + regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); + regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, + lpc_port << snpwadr_shift); + regmap_update_bits(lpc_snoop->regmap, HICRB, hicrb_en, hicrb_en); + + return rc; +} + +static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, + int channel) +{ + switch (channel) { + case 0: + regmap_update_bits(lpc_snoop->regmap, HICR5, + HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, + 0); + break; + case 1: + regmap_update_bits(lpc_snoop->regmap, HICR5, + HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, + 0); + break; + default: + return; + } + + kfifo_free(&lpc_snoop->snoop_fifo[channel]); +} + +static int aspeed_lpc_snoop_probe(struct platform_device *pdev) +{ + struct aspeed_lpc_snoop *lpc_snoop; + struct device *dev; + u32 port; + int rc; + + dev = &pdev->dev; + + lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); + if (!lpc_snoop) + return -ENOMEM; + + lpc_snoop->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(lpc_snoop->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + dev_set_drvdata(&pdev->dev, lpc_snoop); + + rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); + if (rc) { + dev_err(dev, "no snoop ports configured\n"); + return -ENODEV; + } + + rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); + if (rc) + return rc; + + rc = aspeed_lpc_enable_snoop(lpc_snoop, 0, port); + if (rc) + return rc; + + /* Configuration of 2nd snoop channel port is optional */ + if (of_property_read_u32_index(dev->of_node, "snoop-ports", + 1, &port) == 0) { + rc = aspeed_lpc_enable_snoop(lpc_snoop, 1, port); + if (rc) + aspeed_lpc_disable_snoop(lpc_snoop, 0); + } + + return rc; +} + +static int aspeed_lpc_snoop_remove(struct platform_device *pdev) +{ + struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); + + /* Disable both snoop channels */ + aspeed_lpc_disable_snoop(lpc_snoop, 0); + aspeed_lpc_disable_snoop(lpc_snoop, 1); + + return 0; +} + +static const struct of_device_id aspeed_lpc_snoop_match[] = { + { .compatible = "aspeed,ast2500-lpc-snoop" }, + { }, +}; + +static struct platform_driver aspeed_lpc_snoop_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_lpc_snoop_match, + }, + .probe = aspeed_lpc_snoop_probe, + .remove = aspeed_lpc_snoop_remove, +}; + +module_platform_driver(aspeed_lpc_snoop_driver); + +MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); +MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality"); diff --git a/drivers/misc/bh1770glc.c b/drivers/misc/bh1770glc.c index 845466e45b95..38fcfe219d1c 100644 --- a/drivers/misc/bh1770glc.c +++ b/drivers/misc/bh1770glc.c @@ -27,7 +27,7 @@ #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/mutex.h> -#include <linux/i2c/bh1770glc.h> +#include <linux/platform_data/bh1770glc.h> #include <linux/regulator/consumer.h> #include <linux/pm_runtime.h> #include <linux/workqueue.h> diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 07aad8576334..40c79089e548 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -1040,7 +1040,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus, * * @bus: mei device */ -void mei_cl_bus_rescan(struct mei_device *bus) +static void mei_cl_bus_rescan(struct mei_device *bus) { struct mei_cl_device *cldev, *n; struct mei_me_client *me_cl; diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index e1e4d47d4d7d..5c8286b40b62 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -65,7 +65,7 @@ #define HBM_MAJOR_VERSION_DOT 2 /* - * MEI version with notifcation support + * MEI version with notification support */ #define HBM_MINOR_VERSION_EV 0 #define HBM_MAJOR_VERSION_EV 2 diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index c8ad9ee7cb80..d2f691424dd1 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -215,12 +215,6 @@ int mei_start(struct mei_device *dev) } } while (ret); - /* we cannot start the device w/o hbm start message completed */ - if (dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "reset failed"); - goto err; - } - if (mei_hbm_start_wait(dev)) { dev_err(dev->dev, "HBM haven't started"); goto err; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index c14e35201721..b0b8f18a85e3 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -235,6 +235,17 @@ static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr) return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0; } +static inline int hdr_is_valid(u32 msg_hdr) +{ + struct mei_msg_hdr *mei_hdr; + + mei_hdr = (struct mei_msg_hdr *)&msg_hdr; + if (!msg_hdr || mei_hdr->reserved) + return -EBADMSG; + + return 0; +} + /** * mei_irq_read_handler - bottom half read routine after ISR to * handle the read processing. @@ -256,17 +267,18 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr = mei_read_hdr(dev); (*slots)--; dev_dbg(dev->dev, "slots =%08x.\n", *slots); - } - mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); - if (mei_hdr->reserved || !dev->rd_msg_hdr) { - dev_err(dev->dev, "corrupted message header 0x%08X\n", + ret = hdr_is_valid(dev->rd_msg_hdr); + if (ret) { + dev_err(dev->dev, "corrupted message header 0x%08X\n", dev->rd_msg_hdr); - ret = -EBADMSG; - goto end; + goto end; + } } + mei_hdr = (struct mei_msg_hdr *)&dev->rd_msg_hdr; + dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + if (mei_slots2data(*slots) < mei_hdr->length) { dev_err(dev->dev, "less data available than length=%08x.\n", *slots); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 63a67c99fc78..ebcd5132e447 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -306,7 +306,6 @@ struct mei_hw_ops { }; /* MEI bus API*/ -void mei_cl_bus_rescan(struct mei_device *bus); void mei_cl_bus_rescan_work(struct work_struct *work); void mei_cl_bus_dev_fixup(struct mei_cl_device *dev); ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, diff --git a/drivers/misc/sram-exec.c b/drivers/misc/sram-exec.c index 3d528a13b8fc..426ad912b441 100644 --- a/drivers/misc/sram-exec.c +++ b/drivers/misc/sram-exec.c @@ -19,6 +19,7 @@ #include <linux/mm.h> #include <linux/sram.h> +#include <asm/fncpy.h> #include <asm/set_memory.h> #include "sram.h" @@ -58,20 +59,32 @@ int sram_add_protect_exec(struct sram_partition *part) * @src: Source address for the data to copy * @size: Size of copy to perform, which starting from dst, must reside in pool * + * Return: Address for copied data that can safely be called through function + * pointer, or NULL if problem. + * * This helper function allows sram driver to act as central control location * of 'protect-exec' pools which are normal sram pools but are always set * read-only and executable except when copying data to them, at which point * they are set to read-write non-executable, to make sure no memory is * writeable and executable at the same time. This region must be page-aligned * and is checked during probe, otherwise page attribute manipulation would - * not be possible. + * not be possible. Care must be taken to only call the returned address as + * dst address is not guaranteed to be safely callable. + * + * NOTE: This function uses the fncpy macro to move code to the executable + * region. Some architectures have strict requirements for relocating + * executable code, so fncpy is a macro that must be defined by any arch + * making use of this functionality that guarantees a safe copy of exec + * data and returns a safe address that can be called as a C function + * pointer. */ -int sram_exec_copy(struct gen_pool *pool, void *dst, void *src, - size_t size) +void *sram_exec_copy(struct gen_pool *pool, void *dst, void *src, + size_t size) { struct sram_partition *part = NULL, *p; unsigned long base; int pages; + void *dst_cpy; mutex_lock(&exec_pool_list_mutex); list_for_each_entry(p, &exec_pool_list, list) { @@ -81,10 +94,10 @@ int sram_exec_copy(struct gen_pool *pool, void *dst, void *src, mutex_unlock(&exec_pool_list_mutex); if (!part) - return -EINVAL; + return NULL; if (!addr_in_gen_pool(pool, (unsigned long)dst, size)) - return -EINVAL; + return NULL; base = (unsigned long)part->base; pages = PAGE_ALIGN(size) / PAGE_SIZE; @@ -94,13 +107,13 @@ int sram_exec_copy(struct gen_pool *pool, void *dst, void *src, set_memory_nx((unsigned long)base, pages); set_memory_rw((unsigned long)base, pages); - memcpy(dst, src, size); + dst_cpy = fncpy(dst, src, size); set_memory_ro((unsigned long)base, pages); set_memory_x((unsigned long)base, pages); mutex_unlock(&part->lock); - return 0; + return dst_cpy; } EXPORT_SYMBOL_GPL(sram_exec_copy); |