Skip to content

Commit 9f4f9ae

Browse files
rlippertgregkh
authored andcommitted
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 <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 761d031 commit 9f4f9ae

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

drivers/misc/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,14 @@ config ASPEED_LPC_CTRL
490490
ioctl()s, the driver also provides a read/write interface to a BMC ram
491491
region where the host LPC read/write region can be buffered.
492492

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+
493501
config PCI_ENDPOINT_TEST
494502
depends on PCI
495503
select CRC32

drivers/misc/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ obj-$(CONFIG_ECHO) += echo/
5353
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
5454
obj-$(CONFIG_CXL_BASE) += cxl/
5555
obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
56+
obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o
5657
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
5758

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

drivers/misc/aspeed-lpc-snoop.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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 <[email protected]>");
261+
MODULE_DESCRIPTION("Linux driver to control Aspeed LPC snoop functionality");

0 commit comments

Comments
 (0)