Hi, On 4/1/21 5:05 AM, David E. Box wrote: > By default the Low Power Mode (LPM or sub-state) status registers will > latch condition status on every entry into Package C10. This is > configurable in the PMC to allow latching on any achievable sub-state. Add > a debugfs file to support this. > > Also add the option to clear the status registers to 0. Clearing the status > registers before testing removes ambiguity around when the current values > were set. > > The new file, latch_lpm_mode, looks like this: > > [c10] S0i2.0 S0i3.0 S0i2.1 S0i3.1 S0i3.2 clear > > Signed-off-by: David E. Box <david.e.box@xxxxxxxxxxxxxxx> > --- > drivers/platform/x86/intel_pmc_core.c | 94 +++++++++++++++++++++++++++ > drivers/platform/x86/intel_pmc_core.h | 20 ++++++ > 2 files changed, 114 insertions(+) > > diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c > index 0b47a1da5f49..458c0056e7a1 100644 > --- a/drivers/platform/x86/intel_pmc_core.c > +++ b/drivers/platform/x86/intel_pmc_core.c > @@ -584,6 +584,8 @@ static const struct pmc_reg_map tgl_reg_map = { > .ltr_ignore_max = TGL_NUM_IP_IGN_ALLOWED, > .lpm_num_maps = TGL_LPM_NUM_MAPS, > .lpm_res_counter_step_x2 = TGL_PMC_LPM_RES_COUNTER_STEP_X2, > + .etr3_offset = TGL_ETR3_OFFSET, > + .lpm_sts_latch_en_offset = TGL_LPM_STS_LATCH_EN_OFFSET, > .lpm_en_offset = TGL_LPM_EN_OFFSET, > .lpm_priority_offset = TGL_LPM_PRI_OFFSET, > .lpm_residency_offset = TGL_LPM_RESIDENCY_OFFSET, > @@ -1202,6 +1204,95 @@ static int pmc_core_substate_req_regs_show(struct seq_file *s, void *unused) > } > DEFINE_SHOW_ATTRIBUTE(pmc_core_substate_req_regs); > > +static int pmc_core_lpm_latch_mode_show(struct seq_file *s, void *unused) > +{ > + struct pmc_dev *pmcdev = s->private; > + bool c10; > + u32 reg; > + int idx, mode; > + > + reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); > + if (reg & BIT(LPM_STS_LATCH_MODE_BIT)) { > + seq_puts(s, "c10"); > + c10 = false; > + } else { > + seq_puts(s, "[c10]"); > + c10 = true; > + } > + > + pmc_for_each_mode(idx, mode, pmcdev) { > + if ((BIT(mode) & reg) && !c10) > + seq_printf(s, " [%s]", pmc_lpm_modes[mode]); > + else > + seq_printf(s, " %s", pmc_lpm_modes[mode]); > + } So this either shows [c10] selected, or it shows which s0ix modes are selected, that is a bit weird. I realize this is a debugfs interface, but can we still get some docs in this, at a minimum some more explanation in the commit message ? > + > + seq_puts(s, " clear\n"); > + > + return 0; > +} > + > +static ssize_t pmc_core_lpm_latch_mode_write(struct file *file, > + const char __user *userbuf, > + size_t count, loff_t *ppos) > +{ > + struct seq_file *s = file->private_data; > + struct pmc_dev *pmcdev = s->private; > + bool clear = false, c10 = false; > + unsigned char buf[10] = {0}; > + size_t ret; > + int mode; > + u32 reg; > + > + ret = simple_write_to_buffer(buf, 10, ppos, userbuf, count); > + if (ret < 0) > + return ret; You are using C-string functions on buf below, so you must guarantee that it is 0 terminated. To guarantee the buffer's size must be 1 larger then the size which you pass to simple_write_to_buffer() > + > + mode = sysfs_match_string(pmc_lpm_modes, buf); > + if (mode < 0) { > + if (strncmp("clear", buf, 5) == 0) > + clear = true; > + else if (strncmp("c10", buf, 3) == 0) > + c10 = true; Ugh, no. Now it will not just accept "clear" and "clear\n" but also "clear foobar everything else I write now does not matter" as "clear" string. This misuse of strncmp for sysfs / debugfs write functions seems to be some sort of anti-pattern in the kernel. Please use sysfs_streq() here so that only "clear" and "clear\n" are accepted (and the same for the "c10" check). > + else > + return mode; > + } > + > + if (clear) { > + mutex_lock(&pmcdev->lock); > + > + reg = pmc_core_reg_read(pmcdev, pmcdev->map->etr3_offset); > + reg |= BIT(ETR3_CLEAR_LPM_EVENTS_BIT); > + pmc_core_reg_write(pmcdev, pmcdev->map->etr3_offset, reg); > + > + mutex_unlock(&pmcdev->lock); > + > + return count; > + } > + > + if (c10) { > + mutex_lock(&pmcdev->lock); > + > + reg = pmc_core_reg_read(pmcdev, pmcdev->map->lpm_sts_latch_en_offset); > + reg &= ~BIT(LPM_STS_LATCH_MODE_BIT); > + pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); > + > + mutex_unlock(&pmcdev->lock); > + > + return count; > + } > + > + /* > + * For LPM mode latching we set the latch enable bit and selected mode > + * and clear everything else. > + */ > + reg = BIT(LPM_STS_LATCH_MODE_BIT) | BIT(mode); > + pmc_core_reg_write(pmcdev, pmcdev->map->lpm_sts_latch_en_offset, reg); > + > + return count; > +} > +DEFINE_PMC_CORE_ATTR_WRITE(pmc_core_lpm_latch_mode); > + > static int pmc_core_pkgc_show(struct seq_file *s, void *unused) > { > struct pmc_dev *pmcdev = s->private; > @@ -1320,6 +1411,9 @@ static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) > debugfs_create_file("substate_live_status_registers", 0444, > pmcdev->dbgfs_dir, pmcdev, > &pmc_core_substate_l_sts_regs_fops); > + debugfs_create_file("lpm_latch_mode", 0644, > + pmcdev->dbgfs_dir, pmcdev, > + &pmc_core_lpm_latch_mode_fops); > } > > if (pmcdev->lpm_req_regs) { > diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h > index 81d797feed33..f41f61aa7008 100644 > --- a/drivers/platform/x86/intel_pmc_core.h > +++ b/drivers/platform/x86/intel_pmc_core.h > @@ -189,6 +189,8 @@ enum ppfear_regs { > > #define LPM_MAX_NUM_MODES 8 > #define GET_X2_COUNTER(v) ((v) >> 1) > +#define ETR3_CLEAR_LPM_EVENTS_BIT 28 > +#define LPM_STS_LATCH_MODE_BIT 31 > > #define TGL_NUM_IP_IGN_ALLOWED 22 > #define TGL_PMC_SLP_S0_RES_COUNTER_STEP 0x7A > @@ -197,6 +199,8 @@ enum ppfear_regs { > /* > * Tigerlake Power Management Controller register offsets > */ > +#define TGL_ETR3_OFFSET 0x1048 > +#define TGL_LPM_STS_LATCH_EN_OFFSET 0x1C34 > #define TGL_LPM_EN_OFFSET 0x1C78 > #define TGL_LPM_RESIDENCY_OFFSET 0x1C80 > > @@ -266,6 +270,8 @@ struct pmc_reg_map { > /* Low Power Mode registers */ > const int lpm_num_maps; > const int lpm_res_counter_step_x2; > + const u32 etr3_offset; > + const u32 lpm_sts_latch_en_offset; > const u32 lpm_en_offset; > const u32 lpm_priority_offset; > const u32 lpm_residency_offset; > @@ -313,4 +319,18 @@ struct pmc_dev { > i < pmcdev->num_modes; \ > i++, mode = pmcdev->lpm_en_modes[i]) > > +#define DEFINE_PMC_CORE_ATTR_WRITE(__name) \ > +static int __name ## _open(struct inode *inode, struct file *file) \ > +{ \ > + return single_open(file, __name ## _show, inode->i_private); \ > +} \ > + \ > +static const struct file_operations __name ## _fops = { \ > + .owner = THIS_MODULE, \ > + .open = __name ## _open, \ > + .read = seq_read, \ > + .write = __name ## _write, \ > + .release = single_release, \ > +} > + > #endif /* PMC_CORE_H */ > Regards, Hans