Skip to content

Commit b473d6a

Browse files
spandruvadarafaeljw
authored andcommitted
thermal: int340x: processor_thermal: Support power floor notifications
When the hardware reduces the power to the minimum possible, the power floor is notified via an interrupt. This can happen when user space requests a power limit via powercap RAPL interface, which forces the system to enter to the lowest power. This power floor indication can be used as a hint to resort to other methods of reducing power than via RAPL power limit. Before power floor status can be read or the firmware can trigger notifications regarding it, it needs to be configured via a mailbox command. The actual power floor status is read via bit 39 of MMIO offset 0x5B18 of the processor thermal PCI device. To show the current power floor status and get notification on a sysfs attribute, add 2 new attributes to /sys/bus/pci/devices/0000\:00\:04.0/power_limits/ power_floor_enable : This attribute is present when power floor notifications are supported. This attribute allows to enable/disable power floor notifications. power_floor_status : This attribute is present when power floor notifications are supported. When enabled via power_floor_enable, this attribute shows the current power floor status. The power floor implementation provides interfaces which are called from the sysfs callbacks to enable/disable and read power floor status. It also provides two additional interfaces to check if the current processor thermal device interrupt is for power floor status and to send notifications to user space. Signed-off-by: Srinivas Pandruvada <[email protected]> [ rjw: Changelog and documentation changes edits ] Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 6ebc25d commit b473d6a

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

Documentation/driver-api/thermal/intel_dptf.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ ABI.
164164
``power_limit_1_tmax_us`` (RO)
165165
Maximum powercap sysfs constraint_1_time_window_us for Intel RAPL
166166

167+
``power_floor_status`` (RO)
168+
When set to 1, the power floor of the system in the current
169+
configuration has been reached. It needs to be reconfigured to allow
170+
power to be reduced any further.
171+
172+
``power_floor_enable`` (RW)
173+
When set to 1, enable reading and notification of the power floor
174+
status. Notifications are triggered for the power_floor_status
175+
attribute value changes.
176+
167177
:file:`/sys/bus/pci/devices/0000\:00\:04.0/`
168178

169179
``tcc_offset_degree_celsius`` (RW)

drivers/thermal/intel/int340x_thermal/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_rfim.o
1212
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_mbox.o
1313
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_req.o
1414
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_wt_hint.o
15+
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_power_floor.o
1516
obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o
1617
obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o

drivers/thermal/intel/int340x_thermal/processor_thermal_device.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,48 @@ static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
2626
(unsigned long)proc_dev->power_limits[index].suffix * 1000); \
2727
}
2828

29+
static ssize_t power_floor_status_show(struct device *dev,
30+
struct device_attribute *attr,
31+
char *buf)
32+
{
33+
struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
34+
int ret;
35+
36+
ret = proc_thermal_read_power_floor_status(proc_dev);
37+
38+
return sysfs_emit(buf, "%d\n", ret);
39+
}
40+
41+
static ssize_t power_floor_enable_show(struct device *dev,
42+
struct device_attribute *attr,
43+
char *buf)
44+
{
45+
struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
46+
bool ret;
47+
48+
ret = proc_thermal_power_floor_get_state(proc_dev);
49+
50+
return sysfs_emit(buf, "%d\n", ret);
51+
}
52+
53+
static ssize_t power_floor_enable_store(struct device *dev,
54+
struct device_attribute *attr,
55+
const char *buf, size_t count)
56+
{
57+
struct proc_thermal_device *proc_dev = dev_get_drvdata(dev);
58+
u8 state;
59+
int ret;
60+
61+
if (kstrtou8(buf, 0, &state))
62+
return -EINVAL;
63+
64+
ret = proc_thermal_power_floor_set_state(proc_dev, !!state);
65+
if (ret)
66+
return ret;
67+
68+
return count;
69+
}
70+
2971
POWER_LIMIT_SHOW(0, min_uw)
3072
POWER_LIMIT_SHOW(0, max_uw)
3173
POWER_LIMIT_SHOW(0, step_uw)
@@ -50,6 +92,9 @@ static DEVICE_ATTR_RO(power_limit_1_step_uw);
5092
static DEVICE_ATTR_RO(power_limit_1_tmin_us);
5193
static DEVICE_ATTR_RO(power_limit_1_tmax_us);
5294

95+
static DEVICE_ATTR_RO(power_floor_status);
96+
static DEVICE_ATTR_RW(power_floor_enable);
97+
5398
static struct attribute *power_limit_attrs[] = {
5499
&dev_attr_power_limit_0_min_uw.attr,
55100
&dev_attr_power_limit_1_min_uw.attr,
@@ -61,12 +106,30 @@ static struct attribute *power_limit_attrs[] = {
61106
&dev_attr_power_limit_1_tmin_us.attr,
62107
&dev_attr_power_limit_0_tmax_us.attr,
63108
&dev_attr_power_limit_1_tmax_us.attr,
109+
&dev_attr_power_floor_status.attr,
110+
&dev_attr_power_floor_enable.attr,
64111
NULL
65112
};
66113

114+
static umode_t power_limit_attr_visible(struct kobject *kobj, struct attribute *attr, int unused)
115+
{
116+
struct device *dev = kobj_to_dev(kobj);
117+
struct proc_thermal_device *proc_dev;
118+
119+
if (attr != &dev_attr_power_floor_status.attr && attr != &dev_attr_power_floor_enable.attr)
120+
return attr->mode;
121+
122+
proc_dev = dev_get_drvdata(dev);
123+
if (!proc_dev || !(proc_dev->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR))
124+
return 0;
125+
126+
return attr->mode;
127+
}
128+
67129
static const struct attribute_group power_limit_attribute_group = {
68130
.attrs = power_limit_attrs,
69-
.name = "power_limits"
131+
.name = "power_limits",
132+
.is_visible = power_limit_attr_visible,
70133
};
71134

72135
static ssize_t tcc_offset_degree_celsius_show(struct device *dev,
@@ -380,6 +443,9 @@ void proc_thermal_mmio_remove(struct pci_dev *pdev, struct proc_thermal_device *
380443
proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_DVFS)
381444
proc_thermal_rfim_remove(pdev);
382445

446+
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_POWER_FLOOR)
447+
proc_thermal_power_floor_set_state(proc_priv, false);
448+
383449
if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_REQ)
384450
proc_thermal_wt_req_remove(pdev);
385451
else if (proc_priv->mmio_feature_mask & PROC_THERMAL_FEATURE_WT_HINT)

drivers/thermal/intel/int340x_thermal/processor_thermal_device.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ struct rapl_mmio_regs {
6363
#define PROC_THERMAL_FEATURE_WT_REQ 0x08
6464
#define PROC_THERMAL_FEATURE_DLVR 0x10
6565
#define PROC_THERMAL_FEATURE_WT_HINT 0x20
66+
#define PROC_THERMAL_FEATURE_POWER_FLOOR 0x40
6667

6768
#if IS_ENABLED(CONFIG_PROC_THERMAL_MMIO_RAPL)
6869
int proc_thermal_rapl_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv);
@@ -94,6 +95,13 @@ void proc_thermal_wt_req_remove(struct pci_dev *pdev);
9495
#define SOC_WT_RES_INT_STATUS_OFFSET 0x5B18
9596
#define SOC_WT_RES_INT_STATUS_MASK GENMASK_ULL(3, 2)
9697

98+
int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv);
99+
int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable);
100+
bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv);
101+
void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev,
102+
struct proc_thermal_device *proc_priv);
103+
bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv);
104+
97105
int processor_thermal_send_mbox_read_cmd(struct pci_dev *pdev, u16 id, u64 *resp);
98106
int processor_thermal_send_mbox_write_cmd(struct pci_dev *pdev, u16 id, u32 data);
99107
int processor_thermal_mbox_interrupt_config(struct pci_dev *pdev, bool enable, int enable_bit,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Processor thermal device module for registering and processing
4+
* power floor. When the hardware reduces the power to the minimum
5+
* possible, the power floor is notified via an interrupt.
6+
*
7+
* Operation:
8+
* When user space enables power floor reporting:
9+
* - Use mailbox to:
10+
* Enable processor thermal device interrupt
11+
*
12+
* - Current status of power floor is read from offset 0x5B18
13+
* bit 39.
14+
*
15+
* Two interface functions are provided to call when there is a
16+
* thermal device interrupt:
17+
* - proc_thermal_power_floor_intr():
18+
* Check if the interrupt is for change in power floor.
19+
* Called from interrupt context.
20+
*
21+
* - proc_thermal_power_floor_intr_callback():
22+
* Callback for interrupt processing in thread context. This involves
23+
* sending notification to user space that there is a change in the
24+
* power floor status.
25+
*
26+
* Copyright (c) 2023, Intel Corporation.
27+
*/
28+
29+
#include <linux/pci.h>
30+
#include "processor_thermal_device.h"
31+
32+
#define SOC_POWER_FLOOR_STATUS BIT(39)
33+
#define SOC_POWER_FLOOR_SHIFT 39
34+
35+
#define SOC_POWER_FLOOR_INT_ENABLE_BIT 31
36+
#define SOC_POWER_FLOOR_INT_ACTIVE BIT(3)
37+
38+
int proc_thermal_read_power_floor_status(struct proc_thermal_device *proc_priv)
39+
{
40+
u64 status = 0;
41+
42+
status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
43+
return (status & SOC_POWER_FLOOR_STATUS) >> SOC_POWER_FLOOR_SHIFT;
44+
}
45+
EXPORT_SYMBOL_NS_GPL(proc_thermal_read_power_floor_status, INT340X_THERMAL);
46+
47+
static bool enable_state;
48+
static DEFINE_MUTEX(pf_lock);
49+
50+
int proc_thermal_power_floor_set_state(struct proc_thermal_device *proc_priv, bool enable)
51+
{
52+
int ret = 0;
53+
54+
mutex_lock(&pf_lock);
55+
if (enable_state == enable)
56+
goto pf_unlock;
57+
58+
/*
59+
* Time window parameter is not applicable to power floor interrupt configuration.
60+
* Hence use -1 for time window.
61+
*/
62+
ret = processor_thermal_mbox_interrupt_config(to_pci_dev(proc_priv->dev), enable,
63+
SOC_POWER_FLOOR_INT_ENABLE_BIT, -1);
64+
if (!ret)
65+
enable_state = enable;
66+
67+
pf_unlock:
68+
mutex_unlock(&pf_lock);
69+
70+
return ret;
71+
}
72+
EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_set_state, INT340X_THERMAL);
73+
74+
bool proc_thermal_power_floor_get_state(struct proc_thermal_device *proc_priv)
75+
{
76+
return enable_state;
77+
}
78+
EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_get_state, INT340X_THERMAL);
79+
80+
/**
81+
* proc_thermal_check_power_floor_intr() - Check power floor interrupt.
82+
* @proc_priv: Processor thermal device instance.
83+
*
84+
* Callback to check if the interrupt for power floor is active.
85+
*
86+
* Context: Called from interrupt context.
87+
*
88+
* Return: true if power floor is active, false when not active.
89+
*/
90+
bool proc_thermal_check_power_floor_intr(struct proc_thermal_device *proc_priv)
91+
{
92+
u64 int_status;
93+
94+
int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
95+
return !!(int_status & SOC_POWER_FLOOR_INT_ACTIVE);
96+
}
97+
EXPORT_SYMBOL_NS_GPL(proc_thermal_check_power_floor_intr, INT340X_THERMAL);
98+
99+
/**
100+
* proc_thermal_power_floor_intr_callback() - Process power floor notification
101+
* @pdev: PCI device instance
102+
* @proc_priv: Processor thermal device instance.
103+
*
104+
* Check if the power floor interrupt is active, if active send notification to
105+
* user space for the attribute "power_limits", so that user can read the attribute
106+
* and take action.
107+
*
108+
* Context: Called from interrupt thread context.
109+
*
110+
* Return: None.
111+
*/
112+
void proc_thermal_power_floor_intr_callback(struct pci_dev *pdev,
113+
struct proc_thermal_device *proc_priv)
114+
{
115+
u64 status;
116+
117+
status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
118+
if (!(status & SOC_POWER_FLOOR_INT_ACTIVE))
119+
return;
120+
121+
sysfs_notify(&pdev->dev.kobj, "power_limits", "power_floor_status");
122+
}
123+
EXPORT_SYMBOL_NS_GPL(proc_thermal_power_floor_intr_callback, INT340X_THERMAL);
124+
125+
MODULE_IMPORT_NS(INT340X_THERMAL);
126+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)