ASPEED SoC driver updates for 6.17

The ASPEED LPC snoop driver was recently the cause of some concern. In addition
 to the initial fixes, the channel configuration paths are refactored to improve
 robustness against errors.
 -----BEGIN PGP SIGNATURE-----
 
 iJIEABYIADoWIQSoUT1x3bOSX/nAa8ajM9GZTrjhpgUCaGyE5RwcYW5kcmV3QGNv
 ZGVjb25zdHJ1Y3QuY29tLmF1AAoJEKMz0ZlOuOGmuHQA/2nA+lhSqWy3hjK9eada
 /l5d0ffV8jhbmMfWDUhcxrELAP40xkUSUdJD31LRrU/FDGjv7CIznZIldiSlD6tE
 JSNyDQ==
 =SNkf
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmh+UmAACgkQmmx57+YA
 GNkWEQ/9G+zYrNpGXmRhvdXmiSEs0/d9V9qfCdr26dwFR0F6lsFPtm/TJ+Fqi2fJ
 dV2RkJx2+31XzapxKrlyT9L7IMZ4OtfhN+MhFCqv0cB1HDSnxVQKue9/0BsomRUd
 8ZpV5GUTHrYPQvqJUN/eK8BO4INqEnp6XZ9ArTzRDUvbOiCSLzXP8nVaHwSbq3WB
 FqwbMKMah26sW+PgokEhG4Xopa27pN4wbD+0/d3NFFXFDE/KUNBGtxEKz1NOr7qx
 nnpMBZ7KWIg7UltiZ3h6by5yiVQYOW8ekS+j1GcrHLN++LPEJhYlk/Ab91jxCln3
 /U+aWlbQuYiqVVZINeks/RyOhwB8eofV0OvqAJu3TmnkZq28w22cTJESvs5KO3d9
 eKXTd8Xz4mimK9ov8xKof0G/CN4m38FdwSr6YsjIZhWYVh64z0AtA3DlyXbRBjbV
 EY1ePVZZy3S56LdPDk/ADxtHHDC0PL/nIupeCwQLr9GYZeJQrRkPNV9CS8sJeW+o
 YPWM/14V4jCxERauUkCimLDOnRgD+9TVEdS5+a5gz2Bdw3YQCOzyJgqHj8STXlYV
 zZt3NGbb3WC9NXy8EW2U8nyRRg1w1KDQU1d+T6VF7m1WVG8+mbXqVNTw7cUKEsCu
 1MO5V8AfFqyA0fKSN8bGX/5j671u/v611gZGetpwRg135HP46j0=
 =CrwR
 -----END PGP SIGNATURE-----

Merge tag 'aspeed-6.17-drivers-1' of https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux into soc/drivers

ASPEED SoC driver updates for 6.17

The ASPEED LPC snoop driver was recently the cause of some concern. In addition
to the initial fixes, the channel configuration paths are refactored to improve
robustness against errors.

* tag 'aspeed-6.17-drivers-1' of https://git.kernel.org/pub/scm/linux/kernel/git/bmc/linux:
  soc: aspeed: lpc-snoop: Lift channel config to const structs
  soc: aspeed: lpc-snoop: Consolidate channel initialisation
  soc: aspeed: lpc-snoop: Use dev_err_probe() where possible
  soc: aspeed: lpc-snoop: Switch to devm_clk_get_enabled()
  soc: aspeed: lpc-snoop: Rearrange channel paths
  soc: aspeed: lpc-snoop: Rename 'channel' to 'index' in channel paths
  soc: aspeed: lpc-snoop: Constrain parameters in channel paths
  soc: aspeed: lpc-snoop: Ensure model_data is valid
  soc: aspeed: lpc-snoop: Don't disable channels that aren't enabled
  soc: aspeed: lpc-snoop: Cleanup resources in stack-order

Link: https://lore.kernel.org/r/9123f151280e52c63dcb645cb07d4eee3462c067.camel@codeconstruct.com.au
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2025-07-21 16:44:48 +02:00
commit 708109c46f

View File

@ -12,6 +12,7 @@
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/dev_printk.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/kfifo.h>
@ -25,7 +26,6 @@
#define DEVICE_NAME "aspeed-lpc-snoop"
#define NUM_SNOOP_CHANNELS 2
#define SNOOP_FIFO_SIZE 2048
#define HICR5 0x80
@ -57,7 +57,23 @@ struct aspeed_lpc_snoop_model_data {
unsigned int has_hicrb_ensnp;
};
enum aspeed_lpc_snoop_index {
ASPEED_LPC_SNOOP_INDEX_0 = 0,
ASPEED_LPC_SNOOP_INDEX_1 = 1,
ASPEED_LPC_SNOOP_INDEX_MAX = ASPEED_LPC_SNOOP_INDEX_1,
};
struct aspeed_lpc_snoop_channel_cfg {
enum aspeed_lpc_snoop_index index;
u32 hicr5_en;
u32 snpwadr_mask;
u32 snpwadr_shift;
u32 hicrb_en;
};
struct aspeed_lpc_snoop_channel {
const struct aspeed_lpc_snoop_channel_cfg *cfg;
bool enabled;
struct kfifo fifo;
wait_queue_head_t wq;
struct miscdevice miscdev;
@ -67,7 +83,24 @@ struct aspeed_lpc_snoop {
struct regmap *regmap;
int irq;
struct clk *clk;
struct aspeed_lpc_snoop_channel chan[NUM_SNOOP_CHANNELS];
struct aspeed_lpc_snoop_channel chan[ASPEED_LPC_SNOOP_INDEX_MAX + 1];
};
static const struct aspeed_lpc_snoop_channel_cfg channel_cfgs[ASPEED_LPC_SNOOP_INDEX_MAX + 1] = {
{
.index = ASPEED_LPC_SNOOP_INDEX_0,
.hicr5_en = HICR5_EN_SNP0W | HICR5_ENINT_SNP0W,
.snpwadr_mask = SNPWADR_CH0_MASK,
.snpwadr_shift = SNPWADR_CH0_SHIFT,
.hicrb_en = HICRB_ENSNP0D,
},
{
.index = ASPEED_LPC_SNOOP_INDEX_1,
.hicr5_en = HICR5_EN_SNP1W | HICR5_ENINT_SNP1W,
.snpwadr_mask = SNPWADR_CH1_MASK,
.snpwadr_shift = SNPWADR_CH1_SHIFT,
.hicrb_en = HICRB_ENSNP1D,
},
};
static struct aspeed_lpc_snoop_channel *snoop_file_to_chan(struct file *file)
@ -181,98 +214,88 @@ static int aspeed_lpc_snoop_config_irq(struct aspeed_lpc_snoop *lpc_snoop,
return 0;
}
static int aspeed_lpc_enable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
struct device *dev,
int channel, u16 lpc_port)
__attribute__((nonnull))
static int aspeed_lpc_enable_snoop(struct device *dev,
struct aspeed_lpc_snoop *lpc_snoop,
struct aspeed_lpc_snoop_channel *channel,
const struct aspeed_lpc_snoop_channel_cfg *cfg,
u16 lpc_port)
{
const struct aspeed_lpc_snoop_model_data *model_data;
int rc = 0;
u32 hicr5_en, snpwadr_mask, snpwadr_shift, hicrb_en;
const struct aspeed_lpc_snoop_model_data *model_data =
of_device_get_match_data(dev);
init_waitqueue_head(&lpc_snoop->chan[channel].wq);
/* Create FIFO datastructure */
rc = kfifo_alloc(&lpc_snoop->chan[channel].fifo,
SNOOP_FIFO_SIZE, GFP_KERNEL);
if (WARN_ON(channel->enabled))
return -EBUSY;
init_waitqueue_head(&channel->wq);
channel->cfg = cfg;
channel->miscdev.minor = MISC_DYNAMIC_MINOR;
channel->miscdev.fops = &snoop_fops;
channel->miscdev.parent = dev;
channel->miscdev.name =
devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, cfg->index);
if (!channel->miscdev.name)
return -ENOMEM;
rc = kfifo_alloc(&channel->fifo, SNOOP_FIFO_SIZE, GFP_KERNEL);
if (rc)
return rc;
lpc_snoop->chan[channel].miscdev.minor = MISC_DYNAMIC_MINOR;
lpc_snoop->chan[channel].miscdev.name =
devm_kasprintf(dev, GFP_KERNEL, "%s%d", DEVICE_NAME, channel);
if (!lpc_snoop->chan[channel].miscdev.name) {
rc = -ENOMEM;
goto err_free_fifo;
}
lpc_snoop->chan[channel].miscdev.fops = &snoop_fops;
lpc_snoop->chan[channel].miscdev.parent = dev;
rc = misc_register(&lpc_snoop->chan[channel].miscdev);
rc = misc_register(&channel->miscdev);
if (rc)
goto err_free_fifo;
/* 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:
rc = -EINVAL;
goto err_misc_deregister;
}
regmap_set_bits(lpc_snoop->regmap, HICR5, cfg->hicr5_en);
regmap_update_bits(lpc_snoop->regmap, SNPWADR, cfg->snpwadr_mask,
lpc_port << cfg->snpwadr_shift);
regmap_update_bits(lpc_snoop->regmap, HICR5, hicr5_en, hicr5_en);
regmap_update_bits(lpc_snoop->regmap, SNPWADR, snpwadr_mask,
lpc_port << snpwadr_shift);
if (model_data->has_hicrb_ensnp)
regmap_update_bits(lpc_snoop->regmap, HICRB,
hicrb_en, hicrb_en);
model_data = of_device_get_match_data(dev);
if (model_data && model_data->has_hicrb_ensnp)
regmap_set_bits(lpc_snoop->regmap, HICRB, cfg->hicrb_en);
channel->enabled = true;
return 0;
err_misc_deregister:
misc_deregister(&lpc_snoop->chan[channel].miscdev);
err_free_fifo:
kfifo_free(&lpc_snoop->chan[channel].fifo);
kfifo_free(&channel->fifo);
return rc;
}
__attribute__((nonnull))
static void aspeed_lpc_disable_snoop(struct aspeed_lpc_snoop *lpc_snoop,
int channel)
struct aspeed_lpc_snoop_channel *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:
if (!channel->enabled)
return;
}
kfifo_free(&lpc_snoop->chan[channel].fifo);
misc_deregister(&lpc_snoop->chan[channel].miscdev);
/* Disable interrupts along with the device */
regmap_clear_bits(lpc_snoop->regmap, HICR5, channel->cfg->hicr5_en);
channel->enabled = false;
/* Consider improving safety wrt concurrent reader(s) */
misc_deregister(&channel->miscdev);
kfifo_free(&channel->fifo);
}
static void 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, &lpc_snoop->chan[0]);
aspeed_lpc_disable_snoop(lpc_snoop, &lpc_snoop->chan[1]);
}
static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
{
struct aspeed_lpc_snoop *lpc_snoop;
struct device *dev;
struct device_node *np;
u32 port;
struct device *dev;
int idx;
int rc;
dev = &pdev->dev;
@ -290,69 +313,42 @@ static int aspeed_lpc_snoop_probe(struct platform_device *pdev)
}
lpc_snoop->regmap = syscon_node_to_regmap(np);
if (IS_ERR(lpc_snoop->regmap)) {
dev_err(dev, "Couldn't get regmap\n");
return -ENODEV;
}
if (IS_ERR(lpc_snoop->regmap))
return dev_err_probe(dev, PTR_ERR(lpc_snoop->regmap), "Couldn't get regmap\n");
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;
}
lpc_snoop->clk = devm_clk_get(dev, NULL);
if (IS_ERR(lpc_snoop->clk)) {
rc = PTR_ERR(lpc_snoop->clk);
if (rc != -EPROBE_DEFER)
dev_err(dev, "couldn't get clock\n");
return rc;
}
rc = clk_prepare_enable(lpc_snoop->clk);
if (rc) {
dev_err(dev, "couldn't enable clock\n");
return rc;
}
lpc_snoop->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(lpc_snoop->clk))
return dev_err_probe(dev, PTR_ERR(lpc_snoop->clk), "couldn't get clock");
rc = aspeed_lpc_snoop_config_irq(lpc_snoop, pdev);
if (rc)
goto err;
return rc;
rc = aspeed_lpc_enable_snoop(lpc_snoop, dev, 0, port);
if (rc)
goto err;
static_assert(ARRAY_SIZE(channel_cfgs) == ARRAY_SIZE(lpc_snoop->chan),
"Broken implementation assumption regarding cfg count");
for (idx = ASPEED_LPC_SNOOP_INDEX_0; idx <= ASPEED_LPC_SNOOP_INDEX_MAX; idx++) {
u32 port;
/* 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, dev, 1, port);
if (rc) {
aspeed_lpc_disable_snoop(lpc_snoop, 0);
goto err;
}
rc = of_property_read_u32_index(dev->of_node, "snoop-ports", idx, &port);
if (rc)
break;
rc = aspeed_lpc_enable_snoop(dev, lpc_snoop, &lpc_snoop->chan[idx],
&channel_cfgs[idx], port);
if (rc)
goto cleanup_channels;
}
return 0;
return idx == ASPEED_LPC_SNOOP_INDEX_0 ? -ENODEV : 0;
err:
clk_disable_unprepare(lpc_snoop->clk);
cleanup_channels:
aspeed_lpc_snoop_remove(pdev);
return rc;
}
static void 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);
clk_disable_unprepare(lpc_snoop->clk);
}
static const struct aspeed_lpc_snoop_model_data ast2400_model_data = {
.has_hicrb_ensnp = 0,
};