[PATCH AUTOSEL 6.18-5.15] drm/amd/pm: Validate Tonga PPTable subtable bounds
From: Sasha Levin
Date: Mon Oct 19 2026 - 10:36:21 EST
From: Yang Wang <kevinyang.wang@xxxxxxx>
[ Upstream commit 53ef33c084c5778cc2dcd1efff25e31b6e231141 ]
v1:
Tonga PPTable parsing also relies on VBIOS offsets, revision fields
and entry counts for several subtables. Malformed data can cause
out-of-bounds reads, while voltage lookup tables can overrun their
fixed-size destination arrays.
Add common bounds helpers and validate fixed subtables, dynamic entry
arrays and revision-specific layouts before consuming voltage lookup,
dependency, PCIE, power-tune, hard-limit, thermal, fan, GPIO, PPM and
VCE state data.
v2:
correct to handle get_tonga_ppm_table() return value.
Signed-off-by: Yang Wang <kevinyang.wang@xxxxxxx>
Reviewed-by: Asad Kamal <asad.kamal@xxxxxxx>
Reviewed-by: Kenneth Feng <kenneth.feng@xxxxxxx>
Signed-off-by: Alex Deucher <alexander.deucher@xxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---
LLM Generated explanations, may be completely bogus:
## Phase 1: Commit message forensics
**Step 1.1 — Subject**
- Record: `[drm/amd/pm] [Validate] Tonga PPTable subtable bounds`
**Step 1.2 — Tags**
- Record: `Reviewed-by: Asad Kamal <asad.kamal@xxxxxxx>`; `Reviewed-by:
Kenneth Feng <kenneth.feng@xxxxxxx>`; `Signed-off-by: Yang Wang
<kevinyang.wang@xxxxxxx>`; `Signed-off-by: Alex Deucher
<alexander.deucher@xxxxxxx>`.
- No `Fixes:`, `Reported-by`, `Tested-by`, `Link:`, or `Cc: stable`
(absence is expected).
- Pipeline markers (`[Upstream commit …]`, `Signed-off-by: Sasha Levin`)
ignored.
**Step 1.3 — Body**
- Record: Tonga PPTable parsing trusts VBIOS offsets, revision IDs, and
entry counts. Malformed data can cause out-of-bounds reads; voltage
lookup tables can overrun fixed-size destination arrays.
- Symptom: kernel OOB read/write while parsing PowerPlay tables at init
or after a `pp_table` upload.
- Root cause: pointer = `powerplay_table + le16_to_cpu(offset)` with no
check against `soft_pp_table_size`, plus `ucNumEntries` used past
allocated destination size.
- v2 only corrects `get_tonga_ppm_table()` return handling.
**Step 1.4 — Hidden bug fix?**
- Record: Not hidden. Explicit bounds-check fix for OOB reads and
destination-array overrun.
## Phase 2: Diff analysis
**Step 2.1 — Inventory**
- Record: One file,
`drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c`.
Upstream: **+521 / −85**.
- New helpers: `tonga_pp_table_has_space`, `get_tonga_subtable`,
`validate_tonga_table_entries`, plus getters for voltage lookup,
MCLK/MM/SCLK/PCIE/hard-limit/thermal/fan/power-tune/PPM/GPIO/VCE.
- Callers updated: `init_dpm_2_parameters`,
`init_clock_voltage_dependency`, `init_thermal_controller`,
`ppt_get_num_of_vce_state_table_entries_v1_0`,
`ppt_get_vce_state_table_entry_v1_0`.
- Scope: single-file parser hardening, not a cross-subsystem refactor.
**Step 2.2 — Flow change**
- Record: Before, every subtable pointer was `powerplay_table + offset`
with no size check. After, each access goes through helpers that
require a non-zero offset, that the header fits, and that
`entries_offset + num_entries * entry_size` fits in
`soft_pp_table_size`.
- Voltage lookup: `ucNumEntries` must be in `(0, max_levels]` (16)
before `get_vddc_lookup_table()`.
- GPIO: previously `gpio_table` was always a non-NULL computed pointer,
so `get_gpio_table()` ran even when `usGPIOTableOffset == 0`. After,
that path runs only if the offset is non-zero.
- VCE: SCLK records are indexed as Tonga vs Polaris by `ucRevId`;
SCLK/MM/MCLK indices are range-checked.
**Step 2.3 — Bug mechanism**
- Record: **Memory safety (OOB read + heap OOB write)**, plus a Polaris
VCE layout correctness bug.
- Verified on this tree’s pre-fix `get_vddc_lookup_table()` (v6.18.53):
```
table = kzalloc(struct_size(table, entries, max_levels), GFP_KERNEL);
table->count = vddc_lookup_pp_tables->ucNumEntries;
for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++)
```
Allocation uses `max_levels` (16); the loop uses raw `ucNumEntries`
(u8, up to 255). That is a real heap overflow.
- Subtable offsets/counts can also walk past `soft_pp_table_size` (VBIOS
blob or uploaded table).
**Step 2.4 — Fix quality**
- Record: Helpers are overflow-safe (`offset <= table_size && size <=
table_size - offset`). `PP_ASSERT_WITH_CODE` always runs the failure
`return` (not debug-only). Leaf parsers already rejected `ucNumEntries
== 0`, so that check is not a new behavior cliff.
- Regression risk: low–medium. Over-strict revision-based fan/power-tune
sizes could reject a corrupt-but-previously-parsed table. No public
API change. Patch is large because every subtable is wrapped, not
because control flow is redesigned.
## Phase 3: Git history
**Step 3.1 — Blame / introduction**
- Record: Parser has been here since the Tonga PowerPlay path
(`processpptable_v1_0`, later moved in `e098bc9612c2b`, 2020).
Unbounded offset arithmetic is old, not a recent regression.
**Step 3.2 — Fixes: tag**
- Record: None. Not applicable.
**Step 3.3 — Related file history**
- Record: Same-day AMD series:
- `1ac24df78c566` — Tonga state-array bounds
- `3a8d8e0b7f61c` — Vega hwmgr bounds
- `0ceb6bc43e62f` — legacy hwmgr offsets
- `9914149f99fd2` — Vega10 subtable bounds
- `53ef33c084c57` — this commit
- Standalone for this file. State-array helpers
(`get_tonga_state_array`) are a sibling, not a call-graph dependency
of these new getters.
**Step 3.4 — Author**
- Record: Yang Wang (AMD). Same author as the rest of this PPTable
validation series. Merged by Alex Deucher.
**Step 3.5 — Prerequisites**
- Record: No new APIs. Upstream context uses `kzalloc_obj(*ptr)` in an
unchanged hunk; v6.18.53 still has `kzalloc(sizeof(*ptr), GFP_KERNEL)`
in that function. That is a trivial context tweak, not a functional
dependency. Vega10/legacy patches in the same series touch other files
and are not required for this one.
## Phase 4: Mailing list / external
**Step 4.1 — Original submission**
- Record: `b4 dig -c 53ef33c084c5778cc2dcd1efff25e31b6e231141` → `[PATCH
v2 2/3] drm/amd/pm: Validate Tonga PPTable subtable bounds`
https://patch.msgid.link/20260623134917.443011-2-
kevinyang.wang@xxxxxxx
- v1 → v2; v2 is what landed. v2 changelog: handle
`get_tonga_ppm_table()` return value.
- No NAKs in the saved thread.
**Step 4.2 — Reviewers**
- Record: To/Cc: amd-gfx, Alex Deucher, Hawking Zhang, Kenneth Feng,
Asad Kamal.
- Asad: “Series is Reviewed-by”. Kenneth Feng: same. Maintainer SOB from
Alex Deucher.
**Step 4.3 — Bug report**
- Record: No syzbot/bugzilla/CVE in the message or thread. Bug is
described from code inspection of the parser.
**Step 4.4 — Series**
- Record: v2 1/3 Vega10 subtables; **2/3 this patch**; 3/3 remaining
legacy PPLIB. This patch is self-contained.
**Step 4.5 — stable@**
- Record: lore.kernel.org HTTP 403 from WebFetch/curl. `b4 dig` mbox has
no `Cc: stable` and no stable-list discussion. UNVERIFIED vs
lore.kernel.org/stable search.
## Phase 5: Semantic / call graph
**Step 5.1 — Functions**
- Record: New `tonga_pp_table_has_space` / `get_tonga_*` /
`validate_tonga_table_entries`; callers listed in 2.1.
**Step 5.2 — Callers**
- Record: `pptable_v1_0_funcs.pptable_init` →
`pp_tables_v1_0_initialize` → `init_thermal_controller` /
`init_clock_voltage_dependency` / `init_dpm_2_parameters`. Wired from
`smu7_init_function_pointers()` when `pp_table_version ==
PP_TABLE_V1`.
- VCE getters run when userspace/KFD queries VCE states.
**Step 5.3 — Callees**
- Record: Existing leaf parsers (`get_vddc_lookup_table`,
`get_mclk_voltage_dependency_table`, `get_cac_tdp_table`, …). No new
allocations in the helpers.
**Step 5.4 — Reachability**
- Record: Yes. Driver load on Tonga/Fiji/Polaris/VegaM. Also
`pp_dpm_set_pp_table()` after a sysfs write to `pp_table` (`S_IRUGO |
S_IWUSR`, owner-writable; typically root).
**Step 5.5 — Similar patterns**
- Record: Same pattern as Vega10/legacy PPLIB validation in the same
series; same class as earlier Tonga state-array bounds.
## Phase 6: This tree (6.18.y)
**Step 6.1 — Does the bug exist here?**
- Record: Local tree is **v6.18.53** (`6.18.53`, branch `autosel`).
Vanilla v6.18.53 still has unbounded offset arithmetic and the
voltage-lookup heap overflow. Bug was not introduced after this
branch.
**Step 6.2 — Backport difficulty**
- Record: **Minor conflicts**. `git apply --check` of upstream onto
v6.18.53 fails on `kzalloc_obj` vs `kzalloc` context in
`get_platform_power_management_table()`. Logic applies with that
mechanical adjustment. No missing types/helpers required by the new
code (`soft_pp_table_size` is already set in `get_powerplay_table()`).
**Step 6.3 — Related fixes already here?**
- Record: v6.18.53 does **not** have this validation, `pp_entries_max`
clamping in this file, or the Tonga state-array bounds commit. No
alternate fix for the voltage-lookup overflow is present on vanilla
6.18.53.
## Phase 7: Subsystem
**Step 7.1**
- Record: `drm/amd/pm` PowerPlay v1 parser. **IMPORTANT** (not core
mm/VFS; Polar/Tonga/Fiji/VegaM still common, especially RX 400/500).
**Step 7.2**
- Record: Mature parser; recent work is bounds hardening, not feature
churn.
## Phase 8: Impact and risk
**Step 8.1 — Who is affected**
- Record: AMDGPU SMU7 with PP table v1: Tonga, Fiji, Polaris10/11/12,
VegaM (`hwmgr.c` keeps default `PP_TABLE_V1` for those chips).
**Step 8.2 — Trigger**
- Record: GPU init (every boot with those ASICs) if VBIOS is
truncated/corrupt. Also root write to `pp_table`. Unprivileged users
cannot write that sysfs node under normal permissions.
**Step 8.3 — Failure mode**
- Record: Heap overflow in voltage lookup (**CRITICAL** if triggered).
OOB reads of BIOS/uploaded buffer (**HIGH**). Wrong Polaris VCE SCLK
stride (**MEDIUM** functional). Malformed table now fails init instead
of walking off the end.
**Step 8.4 — Risk/benefit**
- Record: Benefit high for Polar/Tonga users and for `pp_table` upload.
Risk: large-ish one-file change; conservative rejects of garbage
tables. Ratio favors backport: the overflow is real and the helpers
are local.
## Phase 9: Synthesis
**For**
- Confirmed heap overflow: `kzalloc(..., max_levels)` vs loop on
`ucNumEntries`.
- Confirmed OOB reads from unchecked VBIOS/upload offsets.
- Reachable on driver init and via `pp_table`.
- Polaris VCE record-size bug also fixed.
- Reviewed by AMD PM; merged by the DRM AMD maintainer.
- One file; no new UAPI; v2 already fixed PPM error handling.
- Buggy code is in this 6.18.y tree.
**Against**
- +521 lines is large for stable.
- No public crash/CVE/syzbot report.
- Needs a small `kzalloc` context tweak vs upstream.
- Sibling series patches are separate (not blockers).
**Unresolved**
- Could not query lore.kernel.org/stable (HTTP 403). No stable
discussion in the `b4` mbox.
**Stable rules**
1. Obviously correct and tested? **PASS** — overflow-safe helpers; AMD
review; v2 PPM fix.
2. Real user-visible bug? **PASS** — OOB on Tonga/Polaris PPTable parse
/ `pp_table` upload.
3. Important (crash/security/corruption)? **PASS** — heap overflow + OOB
read.
4. Small and contained? **PASS** (borderline on size, **contained**: one
parser file, no architecture change).
5. No new features/APIs? **PASS**.
6. Can apply here? **PASS** with minor `kzalloc_obj` context adjustment.
**Exception category:** none (not a device-ID/quirk/DT/docs/build-only
change). It is a memory-safety fix.
This is a real bounds-check for a long-lived AMD PowerPlay parser that
this 6.18.y tree still ships. Size is the only serious stable concern;
the overflow and OOB are concrete enough that omitting it would be the
worse mistake.
### Verification
- [Phase 1] Parsed tags: Reviewed-by Asad Kamal and Kenneth Feng; Alex
Deucher SOB; no Fixes/syzbot/Cc stable.
- [Phase 2] Diff: +521/−85 in `process_pptables_v1_0.c`; helpers +
caller validation.
- [Phase 2] v6.18.53 `get_vddc_lookup_table`: allocates `max_levels`,
iterates `ucNumEntries` — heap overflow confirmed.
- [Phase 2] `PP_ASSERT_WITH_CODE` in `pp_debug.h` always executes the
failure `code`.
- [Phase 3] No Fixes tag. Parser present since Tonga PowerPlay v1; file
moved in `e098bc9612c2b`.
- [Phase 3] Series: 1/3 Vega10, 2/3 this, 3/3 legacy PPLIB; this file
does not need 1/3 or 3/3.
- [Phase 4] `b4 dig -c 53ef33c084c5778`: lore msgid
`20260623134917.443011-2`; v2 of 2/3.
- [Phase 4] mbox: Asad and Kenneth “Series is Reviewed-by”; no NAK; v2
PPM return handling.
- [Phase 4] WebFetch/curl to lore.kernel.org: 403. UNVERIFIED: stable@
archives.
- [Phase 5] `pptable_v1_0_funcs` used from `smu7_init_function_pointers`
for `PP_TABLE_V1`.
- [Phase 5] `pp_table` sysfs: `AMDGPU_DEVICE_ATTR_RW(..., S_IRUGO |
S_IWUSR)`; `pp_dpm_set_pp_table()` replaces `soft_pp_table` and re-
inits.
- [Phase 6] `git describe HEAD`: `v6.18.53-5049-g0f0bdc2b0dffb`; `make
kernelversion`: 6.18.53.
- [Phase 6] `git merge-base --is-ancestor 53ef33c084c5778 HEAD` → not
ancestor (upstream SHA not in this history).
- [Phase 6] Apply onto v6.18.53: fails on `kzalloc_obj` context;
functional backport is still straightforward.
- [Phase 6] `pp_entries_max` not in v6.18.53; this commit is the
voltage-lookup cap for this tree.
- [Phase 7/8] `hwmgr.c`: Tonga/Fiji/Polaris10/11/12/VegaM keep
`PP_TABLE_V1` and call `smu7_init_function_pointers()`.
- [Phase 8] Failure mode: heap overflow / OOB read on PPTable parse —
HIGH/CRITICAL if triggered.
**YES**
.../powerplay/hwmgr/process_pptables_v1_0.c | 606 +++++++++++++++---
1 file changed, 521 insertions(+), 85 deletions(-)
diff --git a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c
index 94c1e2c103703..78037edb4752c 100644
--- a/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c
+++ b/drivers/gpu/drm/amd/pm/powerplay/hwmgr/process_pptables_v1_0.c
@@ -150,6 +150,368 @@ static const void *get_powerplay_table(struct pp_hwmgr *hwmgr)
return table_address;
}
+static bool tonga_pp_table_has_space(struct pp_hwmgr *hwmgr, size_t offset,
+ size_t size)
+{
+ size_t table_size = hwmgr->soft_pp_table_size;
+
+ return offset <= table_size && size <= table_size - offset;
+}
+
+static int get_tonga_subtable(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ u16 table_offset, size_t table_size, const void **table)
+{
+ PP_ASSERT_WITH_CODE((table_offset != 0),
+ "Invalid PowerPlay Table!", return -1);
+ PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset,
+ table_size)),
+ "Invalid PowerPlay Table!", return -1);
+
+ *table = (const void *)(((unsigned long)powerplay_table) + table_offset);
+
+ return 0;
+}
+
+static int validate_tonga_table_entries(struct pp_hwmgr *hwmgr,
+ u16 table_offset, size_t entries_offset,
+ u8 num_entries, size_t entry_size)
+{
+ size_t table_size;
+
+ PP_ASSERT_WITH_CODE((num_entries != 0),
+ "Invalid PowerPlay Table!", return -1);
+
+ table_size = entries_offset + num_entries * entry_size;
+ PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset,
+ table_size)),
+ "Invalid PowerPlay Table!", return -1);
+
+ return 0;
+}
+
+static int get_tonga_voltage_lookup_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ u16 table_offset, uint32_t max_levels,
+ const ATOM_Tonga_Voltage_Lookup_Table **lookup_table)
+{
+ const ATOM_Tonga_Voltage_Lookup_Table *table;
+ size_t table_size;
+ int ret;
+
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*table), (const void **)&table);
+ if (ret)
+ return ret;
+
+ PP_ASSERT_WITH_CODE((table->ucNumEntries != 0 &&
+ table->ucNumEntries <= max_levels),
+ "Invalid PowerPlay Table!", return -1);
+
+ table_size = offsetof(ATOM_Tonga_Voltage_Lookup_Table, entries) +
+ table->ucNumEntries * sizeof(ATOM_Tonga_Voltage_Lookup_Record);
+ PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset,
+ table_size)),
+ "Invalid PowerPlay Table!", return -1);
+
+ *lookup_table = table;
+
+ return 0;
+}
+
+static int get_tonga_mclk_dependency_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_MCLK_Dependency_Table **mclk_dep_table)
+{
+ const ATOM_Tonga_MCLK_Dependency_Table *table;
+ u16 table_offset;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usMclkDependencyTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*table), (const void **)&table);
+ if (ret)
+ return ret;
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset,
+ offsetof(ATOM_Tonga_MCLK_Dependency_Table,
+ entries),
+ table->ucNumEntries,
+ sizeof(ATOM_Tonga_MCLK_Dependency_Record));
+ if (ret)
+ return ret;
+
+ *mclk_dep_table = table;
+
+ return 0;
+}
+
+static int get_tonga_mm_dependency_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_MM_Dependency_Table **mm_dep_table)
+{
+ const ATOM_Tonga_MM_Dependency_Table *table;
+ u16 table_offset;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usMMDependencyTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*table), (const void **)&table);
+ if (ret)
+ return ret;
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset,
+ offsetof(ATOM_Tonga_MM_Dependency_Table,
+ entries),
+ table->ucNumEntries,
+ sizeof(ATOM_Tonga_MM_Dependency_Record));
+ if (ret)
+ return ret;
+
+ *mm_dep_table = table;
+
+ return 0;
+}
+
+static int get_tonga_sclk_dependency_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const PPTable_Generic_SubTable_Header **sclk_dep_table)
+{
+ const PPTable_Generic_SubTable_Header *header;
+ u16 table_offset;
+ size_t entries_offset;
+ size_t entry_size;
+ u8 num_entries;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usSclkDependencyTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*header), (const void **)&header);
+ if (ret)
+ return ret;
+
+ if (header->ucRevId < 1) {
+ const ATOM_Tonga_SCLK_Dependency_Table *table =
+ (const ATOM_Tonga_SCLK_Dependency_Table *)header;
+
+ entries_offset = offsetof(ATOM_Tonga_SCLK_Dependency_Table, entries);
+ entry_size = sizeof(ATOM_Tonga_SCLK_Dependency_Record);
+ num_entries = table->ucNumEntries;
+ } else {
+ const ATOM_Polaris_SCLK_Dependency_Table *table =
+ (const ATOM_Polaris_SCLK_Dependency_Table *)header;
+
+ entries_offset = offsetof(ATOM_Polaris_SCLK_Dependency_Table, entries);
+ entry_size = sizeof(ATOM_Polaris_SCLK_Dependency_Record);
+ num_entries = table->ucNumEntries;
+ }
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset, entries_offset,
+ num_entries, entry_size);
+ if (ret)
+ return ret;
+
+ *sclk_dep_table = header;
+
+ return 0;
+}
+
+static int get_tonga_pcie_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const PPTable_Generic_SubTable_Header **pcie_table)
+{
+ const PPTable_Generic_SubTable_Header *header;
+ u16 table_offset;
+ size_t entries_offset;
+ size_t entry_size;
+ u8 num_entries;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usPCIETableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*header), (const void **)&header);
+ if (ret)
+ return ret;
+
+ if (header->ucRevId < 1) {
+ const ATOM_Tonga_PCIE_Table *table =
+ (const ATOM_Tonga_PCIE_Table *)header;
+
+ entries_offset = offsetof(ATOM_Tonga_PCIE_Table, entries);
+ entry_size = sizeof(ATOM_Tonga_PCIE_Record);
+ num_entries = table->ucNumEntries;
+ } else {
+ const ATOM_Polaris10_PCIE_Table *table =
+ (const ATOM_Polaris10_PCIE_Table *)header;
+
+ entries_offset = offsetof(ATOM_Polaris10_PCIE_Table, entries);
+ entry_size = sizeof(ATOM_Polaris10_PCIE_Record);
+ num_entries = table->ucNumEntries;
+ }
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset, entries_offset,
+ num_entries, entry_size);
+ if (ret)
+ return ret;
+
+ *pcie_table = header;
+
+ return 0;
+}
+
+static int get_tonga_hard_limit_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_Hard_Limit_Table **hard_limit_table)
+{
+ const ATOM_Tonga_Hard_Limit_Table *table;
+ u16 table_offset;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usHardLimitTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*table), (const void **)&table);
+ if (ret)
+ return ret;
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset,
+ offsetof(ATOM_Tonga_Hard_Limit_Table,
+ entries),
+ table->ucNumEntries,
+ sizeof(ATOM_Tonga_Hard_Limit_Record));
+ if (ret)
+ return ret;
+
+ *hard_limit_table = table;
+
+ return 0;
+}
+
+static int get_tonga_thermal_controller_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_Thermal_Controller **thermal_controller)
+{
+ u16 table_offset;
+
+ table_offset = le16_to_cpu(powerplay_table->usThermalControllerOffset);
+
+ return get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(**thermal_controller),
+ (const void **)thermal_controller);
+}
+
+static int get_tonga_fan_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const PPTable_Generic_SubTable_Header **fan_table)
+{
+ const PPTable_Generic_SubTable_Header *header;
+ u16 table_offset;
+ size_t table_size;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usFanTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*header), (const void **)&header);
+ if (ret)
+ return ret;
+
+ if (header->ucRevId < 8)
+ table_size = sizeof(ATOM_Tonga_Fan_Table);
+ else if (header->ucRevId == 8)
+ table_size = sizeof(ATOM_Fiji_Fan_Table);
+ else
+ table_size = sizeof(ATOM_Polaris_Fan_Table);
+
+ PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset,
+ table_size)),
+ "Invalid PowerPlay Table!", return -1);
+
+ *fan_table = header;
+
+ return 0;
+}
+
+static int get_tonga_power_tune_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const PPTable_Generic_SubTable_Header **power_tune_table)
+{
+ const PPTable_Generic_SubTable_Header *header;
+ u16 table_offset;
+ size_t table_size;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usPowerTuneTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*header), (const void **)&header);
+ if (ret)
+ return ret;
+
+ if (header->ucRevId < 3)
+ table_size = sizeof(ATOM_Tonga_PowerTune_Table);
+ else if (header->ucRevId < 4)
+ table_size = sizeof(ATOM_Fiji_PowerTune_Table);
+ else
+ table_size = sizeof(ATOM_Polaris_PowerTune_Table);
+
+ PP_ASSERT_WITH_CODE((tonga_pp_table_has_space(hwmgr, table_offset,
+ table_size)),
+ "Invalid PowerPlay Table!", return -1);
+
+ *power_tune_table = header;
+
+ return 0;
+}
+
+static int get_tonga_ppm_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_PPM_Table **ppm_table)
+{
+ u16 table_offset;
+
+ table_offset = le16_to_cpu(powerplay_table->usPPMTableOffset);
+
+ return get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(**ppm_table), (const void **)ppm_table);
+}
+
+static int get_tonga_gpio_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_GPIO_Table **gpio_table)
+{
+ u16 table_offset;
+
+ table_offset = le16_to_cpu(powerplay_table->usGPIOTableOffset);
+
+ return get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(**gpio_table), (const void **)gpio_table);
+}
+
+static int get_tonga_vce_state_table(struct pp_hwmgr *hwmgr,
+ const ATOM_Tonga_POWERPLAYTABLE *powerplay_table,
+ const ATOM_Tonga_VCE_State_Table **vce_state_table)
+{
+ const ATOM_Tonga_VCE_State_Table *table;
+ u16 table_offset;
+ int ret;
+
+ table_offset = le16_to_cpu(powerplay_table->usVCEStateTableOffset);
+ ret = get_tonga_subtable(hwmgr, powerplay_table, table_offset,
+ sizeof(*table), (const void **)&table);
+ if (ret)
+ return ret;
+
+ ret = validate_tonga_table_entries(hwmgr, table_offset,
+ offsetof(ATOM_Tonga_VCE_State_Table,
+ entries),
+ table->ucNumEntries,
+ sizeof(ATOM_Tonga_VCE_State_Record));
+ if (ret)
+ return ret;
+
+ *vce_state_table = table;
+
+ return 0;
+}
+
static int get_vddc_lookup_table(
struct pp_hwmgr *hwmgr,
phm_ppt_v1_voltage_lookup_table **lookup_table,
@@ -198,7 +560,7 @@ static int get_vddc_lookup_table(
*/
static int get_platform_power_management_table(
struct pp_hwmgr *hwmgr,
- ATOM_Tonga_PPM_Table *atom_ppm_table)
+ const ATOM_Tonga_PPM_Table *atom_ppm_table)
{
struct phm_ppm_table *ptr = kzalloc(sizeof(*ptr), GFP_KERNEL);
struct phm_ppt_v1_information *pp_table_information =
@@ -246,7 +608,7 @@ static int init_dpm_2_parameters(
{
int result = 0;
struct phm_ppt_v1_information *pp_table_information = (struct phm_ppt_v1_information *)(hwmgr->pptable);
- ATOM_Tonga_PPM_Table *atom_ppm_table;
+ const ATOM_Tonga_PPM_Table *atom_ppm_table;
uint32_t disable_ppm = 0;
uint32_t disable_power_control = 0;
@@ -275,30 +637,39 @@ static int init_dpm_2_parameters(
}
if (0 != powerplay_table->usVddcLookupTableOffset) {
- const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable =
- (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) +
- le16_to_cpu(powerplay_table->usVddcLookupTableOffset));
-
- result = get_vddc_lookup_table(hwmgr,
- &pp_table_information->vddc_lookup_table, pVddcCACTable, 16);
+ const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable;
+
+ result = get_tonga_voltage_lookup_table(hwmgr, powerplay_table,
+ le16_to_cpu(powerplay_table->usVddcLookupTableOffset),
+ 16, &pVddcCACTable);
+ if (!result)
+ result = get_vddc_lookup_table(hwmgr,
+ &pp_table_information->vddc_lookup_table,
+ pVddcCACTable, 16);
}
- if (0 != powerplay_table->usVddgfxLookupTableOffset) {
- const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable =
- (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) +
- le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset));
+ if (!result && 0 != powerplay_table->usVddgfxLookupTableOffset) {
+ const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable;
- result = get_vddc_lookup_table(hwmgr,
- &pp_table_information->vddgfx_lookup_table, pVddgfxCACTable, 16);
+ result = get_tonga_voltage_lookup_table(hwmgr, powerplay_table,
+ le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset),
+ 16, &pVddgfxCACTable);
+ if (!result)
+ result = get_vddc_lookup_table(hwmgr,
+ &pp_table_information->vddgfx_lookup_table,
+ pVddgfxCACTable, 16);
}
disable_ppm = 0;
if (0 == disable_ppm) {
- atom_ppm_table = (ATOM_Tonga_PPM_Table *)
- (((unsigned long)powerplay_table) + le16_to_cpu(powerplay_table->usPPMTableOffset));
-
if (0 != powerplay_table->usPPMTableOffset) {
- if (get_platform_power_management_table(hwmgr, atom_ppm_table) == 0) {
+ int ret;
+
+ ret = get_tonga_ppm_table(hwmgr, powerplay_table,
+ &atom_ppm_table);
+ if (!ret &&
+ get_platform_power_management_table(hwmgr,
+ atom_ppm_table) == 0) {
phm_cap_set(hwmgr->platform_descriptor.platformCaps,
PHM_PlatformCaps_EnablePlatformPowerManagement);
}
@@ -792,28 +1163,13 @@ static int init_clock_voltage_dependency(
int result = 0;
struct phm_ppt_v1_information *pp_table_information =
(struct phm_ppt_v1_information *)(hwmgr->pptable);
-
- const ATOM_Tonga_MM_Dependency_Table *mm_dependency_table =
- (const ATOM_Tonga_MM_Dependency_Table *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usMMDependencyTableOffset));
- const PPTable_Generic_SubTable_Header *pPowerTuneTable =
- (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usPowerTuneTableOffset));
- const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table =
- (const ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usMclkDependencyTableOffset));
- const PPTable_Generic_SubTable_Header *sclk_dep_table =
- (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usSclkDependencyTableOffset));
- const ATOM_Tonga_Hard_Limit_Table *pHardLimits =
- (const ATOM_Tonga_Hard_Limit_Table *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usHardLimitTableOffset));
- const PPTable_Generic_SubTable_Header *pcie_table =
- (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usPCIETableOffset));
- const ATOM_Tonga_GPIO_Table *gpio_table =
- (const ATOM_Tonga_GPIO_Table *)(((unsigned long) powerplay_table) +
- le16_to_cpu(powerplay_table->usGPIOTableOffset));
+ const ATOM_Tonga_MM_Dependency_Table *mm_dependency_table;
+ const PPTable_Generic_SubTable_Header *pPowerTuneTable;
+ const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table;
+ const PPTable_Generic_SubTable_Header *sclk_dep_table;
+ const ATOM_Tonga_Hard_Limit_Table *pHardLimits;
+ const PPTable_Generic_SubTable_Header *pcie_table;
+ const ATOM_Tonga_GPIO_Table *gpio_table;
pp_table_information->vdd_dep_on_sclk = NULL;
pp_table_information->vdd_dep_on_mclk = NULL;
@@ -821,29 +1177,58 @@ static int init_clock_voltage_dependency(
pp_table_information->pcie_table = NULL;
pp_table_information->gpio_table = NULL;
- if (powerplay_table->usMMDependencyTableOffset != 0)
- result = get_mm_clock_voltage_table(hwmgr,
- &pp_table_information->mm_dep_table, mm_dependency_table);
+ if (powerplay_table->usMMDependencyTableOffset != 0) {
+ result = get_tonga_mm_dependency_table(hwmgr, powerplay_table,
+ &mm_dependency_table);
+ if (!result)
+ result = get_mm_clock_voltage_table(hwmgr,
+ &pp_table_information->mm_dep_table,
+ mm_dependency_table);
+ }
- if (result == 0 && powerplay_table->usPowerTuneTableOffset != 0)
- result = get_cac_tdp_table(hwmgr,
- &pp_table_information->cac_dtp_table, pPowerTuneTable);
+ if (result == 0 && powerplay_table->usPowerTuneTableOffset != 0) {
+ result = get_tonga_power_tune_table(hwmgr, powerplay_table,
+ &pPowerTuneTable);
+ if (!result)
+ result = get_cac_tdp_table(hwmgr,
+ &pp_table_information->cac_dtp_table,
+ pPowerTuneTable);
+ }
- if (result == 0 && powerplay_table->usSclkDependencyTableOffset != 0)
- result = get_sclk_voltage_dependency_table(hwmgr,
- &pp_table_information->vdd_dep_on_sclk, sclk_dep_table);
+ if (result == 0 && powerplay_table->usSclkDependencyTableOffset != 0) {
+ result = get_tonga_sclk_dependency_table(hwmgr, powerplay_table,
+ &sclk_dep_table);
+ if (!result)
+ result = get_sclk_voltage_dependency_table(hwmgr,
+ &pp_table_information->vdd_dep_on_sclk,
+ sclk_dep_table);
+ }
- if (result == 0 && powerplay_table->usMclkDependencyTableOffset != 0)
- result = get_mclk_voltage_dependency_table(hwmgr,
- &pp_table_information->vdd_dep_on_mclk, mclk_dep_table);
+ if (result == 0 && powerplay_table->usMclkDependencyTableOffset != 0) {
+ result = get_tonga_mclk_dependency_table(hwmgr, powerplay_table,
+ &mclk_dep_table);
+ if (!result)
+ result = get_mclk_voltage_dependency_table(hwmgr,
+ &pp_table_information->vdd_dep_on_mclk,
+ mclk_dep_table);
+ }
- if (result == 0 && powerplay_table->usPCIETableOffset != 0)
- result = get_pcie_table(hwmgr,
- &pp_table_information->pcie_table, pcie_table);
+ if (result == 0 && powerplay_table->usPCIETableOffset != 0) {
+ result = get_tonga_pcie_table(hwmgr, powerplay_table,
+ &pcie_table);
+ if (!result)
+ result = get_pcie_table(hwmgr,
+ &pp_table_information->pcie_table, pcie_table);
+ }
- if (result == 0 && powerplay_table->usHardLimitTableOffset != 0)
- result = get_hard_limits(hwmgr,
- &pp_table_information->max_clock_voltage_on_dc, pHardLimits);
+ if (result == 0 && powerplay_table->usHardLimitTableOffset != 0) {
+ result = get_tonga_hard_limit_table(hwmgr, powerplay_table,
+ &pHardLimits);
+ if (!result)
+ result = get_hard_limits(hwmgr,
+ &pp_table_information->max_clock_voltage_on_dc,
+ pHardLimits);
+ }
hwmgr->dyn_state.max_clock_voltage_on_dc.sclk =
pp_table_information->max_clock_voltage_on_dc.sclk;
@@ -864,9 +1249,13 @@ static int init_clock_voltage_dependency(
result = get_valid_clk(hwmgr, &pp_table_information->valid_sclk_values,
pp_table_information->vdd_dep_on_sclk);
- if (!result && gpio_table)
- result = get_gpio_table(hwmgr, &pp_table_information->gpio_table,
- gpio_table);
+ if (!result && powerplay_table->usGPIOTableOffset) {
+ result = get_tonga_gpio_table(hwmgr, powerplay_table,
+ &gpio_table);
+ if (!result)
+ result = get_gpio_table(hwmgr,
+ &pp_table_information->gpio_table, gpio_table);
+ }
return result;
}
@@ -911,14 +1300,17 @@ static int init_thermal_controller(
)
{
const PPTable_Generic_SubTable_Header *fan_table;
- ATOM_Tonga_Thermal_Controller *thermal_controller;
+ const ATOM_Tonga_Thermal_Controller *thermal_controller;
+ int ret;
- thermal_controller = (ATOM_Tonga_Thermal_Controller *)
- (((unsigned long)powerplay_table) +
- le16_to_cpu(powerplay_table->usThermalControllerOffset));
PP_ASSERT_WITH_CODE((0 != powerplay_table->usThermalControllerOffset),
"Thermal controller table not set!", return -1);
+ ret = get_tonga_thermal_controller_table(hwmgr, powerplay_table,
+ &thermal_controller);
+ if (ret)
+ return ret;
+
hwmgr->thermal_controller.ucType = thermal_controller->ucType;
hwmgr->thermal_controller.ucI2cLine = thermal_controller->ucI2cLine;
hwmgr->thermal_controller.ucI2cAddress = thermal_controller->ucI2cAddress;
@@ -946,12 +1338,13 @@ static int init_thermal_controller(
return 0;
}
- fan_table = (const PPTable_Generic_SubTable_Header *)
- (((unsigned long)powerplay_table) +
- le16_to_cpu(powerplay_table->usFanTableOffset));
-
PP_ASSERT_WITH_CODE((0 != powerplay_table->usFanTableOffset),
"Fan table not set!", return -1);
+
+ ret = get_tonga_fan_table(hwmgr, powerplay_table, &fan_table);
+ if (ret)
+ return ret;
+
PP_ASSERT_WITH_CODE((0 < fan_table->ucRevId),
"Unsupported fan table format!", return -1);
@@ -1313,13 +1706,15 @@ static int ppt_get_num_of_vce_state_table_entries_v1_0(struct pp_hwmgr *hwmgr)
{
const ATOM_Tonga_POWERPLAYTABLE *pp_table = get_powerplay_table(hwmgr);
const ATOM_Tonga_VCE_State_Table *vce_state_table;
+ int ret;
if (pp_table == NULL)
return 0;
- vce_state_table = (void *)pp_table +
- le16_to_cpu(pp_table->usVCEStateTableOffset);
+ ret = get_tonga_vce_state_table(hwmgr, pp_table, &vce_state_table);
+ if (ret)
+ return 0;
return vce_state_table->ucNumEntries;
}
@@ -1328,18 +1723,39 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i
struct amd_vce_state *vce_state, void **clock_info, uint32_t *flag)
{
const ATOM_Tonga_VCE_State_Record *vce_state_record;
- ATOM_Tonga_SCLK_Dependency_Record *sclk_dep_record;
+ ATOM_Tonga_SCLK_Dependency_Record *sclk_dep_record = NULL;
+ ATOM_Polaris_SCLK_Dependency_Record *polaris_sclk_dep_record = NULL;
ATOM_Tonga_MCLK_Dependency_Record *mclk_dep_record;
ATOM_Tonga_MM_Dependency_Record *mm_dep_record;
const ATOM_Tonga_POWERPLAYTABLE *pptable = get_powerplay_table(hwmgr);
- const ATOM_Tonga_VCE_State_Table *vce_state_table = (ATOM_Tonga_VCE_State_Table *)(((unsigned long)pptable)
- + le16_to_cpu(pptable->usVCEStateTableOffset));
- const ATOM_Tonga_SCLK_Dependency_Table *sclk_dep_table = (ATOM_Tonga_SCLK_Dependency_Table *)(((unsigned long)pptable)
- + le16_to_cpu(pptable->usSclkDependencyTableOffset));
- const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table = (ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long)pptable)
- + le16_to_cpu(pptable->usMclkDependencyTableOffset));
- const ATOM_Tonga_MM_Dependency_Table *mm_dep_table = (ATOM_Tonga_MM_Dependency_Table *)(((unsigned long)pptable)
- + le16_to_cpu(pptable->usMMDependencyTableOffset));
+ const ATOM_Tonga_VCE_State_Table *vce_state_table;
+ const PPTable_Generic_SubTable_Header *sclk_dep_table_header;
+ const ATOM_Tonga_SCLK_Dependency_Table *sclk_dep_table;
+ const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table;
+ const ATOM_Tonga_MM_Dependency_Table *mm_dep_table;
+ int ret;
+
+ if (!pptable)
+ return -EINVAL;
+
+ ret = get_tonga_vce_state_table(hwmgr, pptable, &vce_state_table);
+ if (ret)
+ return ret;
+
+ ret = get_tonga_sclk_dependency_table(hwmgr, pptable,
+ &sclk_dep_table_header);
+ if (ret)
+ return ret;
+ sclk_dep_table = (const ATOM_Tonga_SCLK_Dependency_Table *)
+ sclk_dep_table_header;
+
+ ret = get_tonga_mclk_dependency_table(hwmgr, pptable, &mclk_dep_table);
+ if (ret)
+ return ret;
+
+ ret = get_tonga_mm_dependency_table(hwmgr, pptable, &mm_dep_table);
+ if (ret)
+ return ret;
PP_ASSERT_WITH_CODE((i < vce_state_table->ucNumEntries),
"Requested state entry ID is out of range!",
@@ -1348,10 +1764,27 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i
vce_state_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
ATOM_Tonga_VCE_State_Record,
entries, vce_state_table, i);
- sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
- ATOM_Tonga_SCLK_Dependency_Record,
- entries, sclk_dep_table,
- vce_state_record->ucSCLKIndex);
+ PP_ASSERT_WITH_CODE((vce_state_record->ucSCLKIndex <
+ sclk_dep_table->ucNumEntries),
+ "Invalid PowerPlay Table!", return -EINVAL);
+ PP_ASSERT_WITH_CODE((vce_state_record->ucVCEClockIndex <
+ mm_dep_table->ucNumEntries),
+ "Invalid PowerPlay Table!", return -EINVAL);
+ PP_ASSERT_WITH_CODE((mclk_dep_table->ucNumEntries != 0),
+ "Invalid PowerPlay Table!", return -EINVAL);
+
+ if (sclk_dep_table_header->ucRevId < 1)
+ sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
+ ATOM_Tonga_SCLK_Dependency_Record,
+ entries, sclk_dep_table,
+ vce_state_record->ucSCLKIndex);
+ else
+ polaris_sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
+ ATOM_Polaris_SCLK_Dependency_Record,
+ entries,
+ (ATOM_Polaris_SCLK_Dependency_Table *)
+ sclk_dep_table_header,
+ vce_state_record->ucSCLKIndex);
mm_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
ATOM_Tonga_MM_Dependency_Record,
entries, mm_dep_table,
@@ -1360,7 +1793,10 @@ static int ppt_get_vce_state_table_entry_v1_0(struct pp_hwmgr *hwmgr, uint32_t i
vce_state->evclk = le32_to_cpu(mm_dep_record->ulEClk);
vce_state->ecclk = le32_to_cpu(mm_dep_record->ulEClk);
- vce_state->sclk = le32_to_cpu(sclk_dep_record->ulSclk);
+ if (sclk_dep_record)
+ vce_state->sclk = le32_to_cpu(sclk_dep_record->ulSclk);
+ else
+ vce_state->sclk = le32_to_cpu(polaris_sclk_dep_record->ulSclk);
if (vce_state_record->ucMCLKIndex >= mclk_dep_table->ucNumEntries)
mclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR(
--
2.53.0