Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

drivers/misc: add Aspeed LPC snoop driver

This driver enables the LPC snoop hardware on the ASPEED BMC
which generates an interrupt upon every write to an I/O port
by the host.

This is typically used to monitor BIOS boot progress by listening
to well-known debug port 80h.

The functionality in this commit just saves all snooped values
to a circular 2K buffer in the kernel, subsequent commits can
act on the values to do things with them.

Signed-off-by: Robert Lippert <rlippert@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Robert Lippert and committed by
Greg Kroah-Hartman
9f4f9ae8 761d031e

+270
+8
drivers/misc/Kconfig
··· 490 490 ioctl()s, the driver also provides a read/write interface to a BMC ram 491 491 region where the host LPC read/write region can be buffered. 492 492 493 + config ASPEED_LPC_SNOOP 494 + tristate "Aspeed ast2500 HOST LPC snoop support" 495 + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON 496 + help 497 + Provides a driver to control the LPC snoop interface which 498 + allows the BMC to listen on and save the data written by 499 + the host to an arbitrary LPC I/O port. 500 + 493 501 config PCI_ENDPOINT_TEST 494 502 depends on PCI 495 503 select CRC32
+1
drivers/misc/Makefile
··· 53 53 obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o 54 54 obj-$(CONFIG_CXL_BASE) += cxl/ 55 55 obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o 56 + obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o 56 57 obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o 57 58 58 59 lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
+261
drivers/misc/aspeed-lpc-snoop.c
··· 1 + /* 2 + * Copyright 2017 Google Inc 3 + * 4 + * This program is free software; you can redistribute it and/or 5 + * modify it under the terms of the GNU General Public License 6 + * as published by the Free Software Foundation; either version 7 + * 2 of the License, or (at your option) any later version. 8 + * 9 + * Provides a simple driver to control the ASPEED LPC snoop interface which 10 + * allows the BMC to listen on and save the data written by 11 + * the host to an arbitrary LPC I/O port. 12 + * 13 + * Typically used by the BMC to "watch" host boot progress via port 14 + * 0x80 writes made by the BIOS during the boot process. 15 + */ 16 + 17 + #include <linux/bitops.h> 18 + #include <linux/interrupt.h> 19 + #include <linux/kfifo.h> 20 + #include <linux/mfd/syscon.h> 21 + #include <linux/module.h> 22 + #include <linux/of.h> 23 + #include <linux/platform_device.h> 24 + #include <linux/regmap.h> 25 + 26 + #define DEVICE_NAME "aspeed-lpc-snoop" 27 + 28 + #define NUM_SNOOP_CHANNELS 2 29 + #define SNOOP_FIFO_SIZE 2048 30 + 31 + #define HICR5 0x0 32 + #define HICR5_EN_SNP0W BIT(0) 33 + #define HICR5_ENINT_SNP0W BIT(1) 34 + #define HICR5_EN_SNP1W BIT(2) 35 + #define HICR5_ENINT_SNP1W BIT(3) 36 + 37 + #define HICR6 0x4 38 + #define HICR6_STR_SNP0W BIT(0) 39 + #define HICR6_STR_SNP1W BIT(1) 40 + #define SNPWADR 0x10 41 + #define SNPWADR_CH0_MASK GENMASK(15, 0) 42 + #define SNPWADR_CH0_SHIFT 0 43 + #define SNPWADR_CH1_MASK GENMASK(31, 16) 44 + #define SNPWADR_CH1_SHIFT 16 45 + #define SNPWDR 0x14 46 + #define SNPWDR_CH0_MASK GENMASK(7, 0) 47 + #define SNPWDR_CH0_SHIFT 0 48 + #define SNPWDR_CH1_MASK GENMASK(15, 8) 49 + #define SNPWDR_CH1_SHIFT 8 50 + #define HICRB 0x80 51 + #define HICRB_ENSNP0D BIT(14) 52 + #define HICRB_ENSNP1D BIT(15) 53 + 54 + struct aspeed_lpc_snoop { 55 + struct regmap *regmap; 56 + int irq; 57 + struct kfifo snoop_fifo[NUM_SNOOP_CHANNELS]; 58 + }; 59 + 60 + /* Save a byte to a FIFO and discard the oldest byte if FIFO is full */ 61 + static void put_fifo_with_discard(struct kfifo *fifo, u8 val) 62 + { 63 + if (!kfifo_initialized(fifo)) 64 + return; 65 + if (kfifo_is_full(fifo)) 66 + kfifo_skip(fifo); 67 + kfifo_put(fifo, val); 68 + } 69 + 70 + static irqreturn_t aspeed_lpc_snoop_irq(int irq, void *arg) 71 + { 72 + struct aspeed_lpc_snoop *lpc_snoop = arg; 73 + u32 reg, data; 74 + 75 + if (regmap_read(lpc_snoop->regmap, HICR6, &reg)) 76 + return IRQ_NONE; 77 + 78 + /* Check if one of the snoop channels is interrupting */ 79 + reg &= (HICR6_STR_SNP0W | HICR6_STR_SNP1W); 80 + if (!reg) 81 + return IRQ_NONE; 82 + 83 + /* Ack pending IRQs */ 84 + regmap_write(lpc_snoop->regmap, HICR6, reg); 85 + 86 + /* Read and save most recent snoop'ed data byte to FIFO */ 87 + regmap_read(lpc_snoop->regmap, SNPWDR, &data); 88 + 89 + if (reg & HICR6_STR_SNP0W) { 90 + u8 val = (data & SNPWDR_CH0_MASK) >> SNPWDR_CH0_SHIFT; 91 + 92 + put_fifo_with_discard(&lpc_snoop->snoop_fifo[0], val); 93 + } 94 + if (reg & HICR6_STR_SNP1W) { 95 + u8 val = (data & SNPWDR_CH1_MASK) >> SNPWDR_CH1_SHIFT; 96 + 97 + put_fifo_with_discard(&lpc_snoop->snoop_fifo[1], val); 98 + } 99 + 100 + return IRQ_HANDLED; 101 + } 102 + 103 + static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop, 104 + struct platform_device *pdev) 105 + { 106 + struct device *dev = &pdev->dev; 107 + int rc; 108 + 109 + lpc_snoop->irq = platform_get_irq(pdev, 0); 110 + if (!lpc_snoop->irq) 111 + return -ENODEV; 112 + 113 + rc = devm_request_irq(dev, lpc_snoop->irq, 114 + aspeed_lpc_snoop_irq, IRQF_SHARED, 115 + DEVICE_NAME, lpc_snoop); 116 + if (rc < 0) { 117 + dev_warn(dev, "Unable to request IRQ %d\n", lpc_snoop->irq); 118 + lpc_snoop->irq = 0; 119 + return rc; 120 + } 121 + 122 + return 0; 123 + } 124 + 125 + static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 126 + int channel, u16 lpc_port) 127 + { 128 + int rc = 0; 129 + u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en; 130 + 131 + /* Create FIFO datastructure */ 132 + rc = kfifo_alloc(&lpc_snoop->snoop_fifo[channel], 133 + SNOOP_FIFO_SIZE, GFP_KERNEL); 134 + if (rc) 135 + return rc; 136 + 137 + /* Enable LPC snoop channel at requested port */ 138 + switch (channel) { 139 + case 0: 140 + hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W; 141 + snpwadr_mask = SNPWADR_CH0_MASK; 142 + snpwadr_shift = SNPWADR_CH0_SHIFT; 143 + hicrb_en = HICRB_ENSNP0D; 144 + break; 145 + case 1: 146 + hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W; 147 + snpwadr_mask = SNPWADR_CH1_MASK; 148 + snpwadr_shift = SNPWADR_CH1_SHIFT; 149 + hicrb_en = HICRB_ENSNP1D; 150 + break; 151 + default: 152 + return -EINVAL; 153 + } 154 + 155 + regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en); 156 + regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask, 157 + lpc_port << snpwadr_shift); 158 + regmap_update_bits(lpc_snoop->regmap, HICRB, hicrb_en, hicrb_en); 159 + 160 + return rc; 161 + } 162 + 163 + static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop, 164 + int channel) 165 + { 166 + switch (channel) { 167 + case 0: 168 + regmap_update_bits(lpc_snoop->regmap, HICR5, 169 + HICR5_EN_SNP0W | HICR5_ENINT_SNP0W, 170 + 0); 171 + break; 172 + case 1: 173 + regmap_update_bits(lpc_snoop->regmap, HICR5, 174 + HICR5_EN_SNP1W | HICR5_ENINT_SNP1W, 175 + 0); 176 + break; 177 + default: 178 + return; 179 + } 180 + 181 + kfifo_free(&lpc_snoop->snoop_fifo[channel]); 182 + } 183 + 184 + static int aspeed_lpc_snoop_probe(struct platform_device *pdev) 185 + { 186 + struct aspeed_lpc_snoop *lpc_snoop; 187 + struct device *dev; 188 + u32 port; 189 + int rc; 190 + 191 + dev = &pdev->dev; 192 + 193 + lpc_snoop = devm_kzalloc(dev, sizeof(*lpc_snoop), GFP_KERNEL); 194 + if (!lpc_snoop) 195 + return -ENOMEM; 196 + 197 + lpc_snoop->regmap = syscon_node_to_regmap( 198 + pdev->dev.parent->of_node); 199 + if (IS_ERR(lpc_snoop->regmap)) { 200 + dev_err(dev, "Couldn't get regmap\n"); 201 + return -ENODEV; 202 + } 203 + 204 + dev_set_drvdata(&pdev->dev, lpc_snoop); 205 + 206 + rc = of_property_read_u32_index(dev->of_node, "snoop-ports", 0, &port); 207 + if (rc) { 208 + dev_err(dev, "no snoop ports configured\n"); 209 + return -ENODEV; 210 + } 211 + 212 + rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev); 213 + if (rc) 214 + return rc; 215 + 216 + rc = aspeed_lpc_enable_snoop(lpc_snoop, 0, port); 217 + if (rc) 218 + return rc; 219 + 220 + /* Configuration of 2nd snoop channel port is optional */ 221 + if (of_property_read_u32_index(dev->of_node, "snoop-ports", 222 + 1, &port) == 0) { 223 + rc = aspeed_lpc_enable_snoop(lpc_snoop, 1, port); 224 + if (rc) 225 + aspeed_lpc_disable_snoop(lpc_snoop, 0); 226 + } 227 + 228 + return rc; 229 + } 230 + 231 + static int aspeed_lpc_snoop_remove(struct platform_device *pdev) 232 + { 233 + struct aspeed_lpc_snoop *lpc_snoop = dev_get_drvdata(&pdev->dev); 234 + 235 + /* Disable both snoop channels */ 236 + aspeed_lpc_disable_snoop(lpc_snoop, 0); 237 + aspeed_lpc_disable_snoop(lpc_snoop, 1); 238 + 239 + return 0; 240 + } 241 + 242 + static const struct of_device_id aspeed_lpc_snoop_match[] = { 243 + { .compatible = "aspeed,ast2500-lpc-snoop" }, 244 + { }, 245 + }; 246 + 247 + static struct platform_driver aspeed_lpc_snoop_driver = { 248 + .driver = { 249 + .name = DEVICE_NAME, 250 + .of_match_table = aspeed_lpc_snoop_match, 251 + }, 252 + .probe = aspeed_lpc_snoop_probe, 253 + .remove = aspeed_lpc_snoop_remove, 254 + }; 255 + 256 + module_platform_driver(aspeed_lpc_snoop_driver); 257 + 258 + MODULE_DEVICE_TABLE(of, aspeed_lpc_snoop_match); 259 + MODULE_LICENSE("GPL"); 260 + MODULE_AUTHOR("Robert Lippert <rlippert@google.com>"); 261 + MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");