--- a/sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c +++ b/sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c @@ -31,6 +31,74 @@ #include "cgs_common.h" #include "pptable_v1_0.h" +/* + * Central bounds-validation layer for V1 (Tonga-format) PowerPlay tables. + * + * Mirrors the V0 treatment in processpptables.c: every firmware-supplied + * offset, index and entry count is proven against the actual size of the + * PowerPlay table image (hwmgr->soft_pp_table / soft_pp_table_size, cached + * by get_powerplay_table()) before it is dereferenced, and entry counts are + * clamped both to what the image can hold and to the capacities of the + * destination arrays that ultimately consume them. + */ + +/* + * Destination-array capacities of the SMU7-family SMC tables that consume + * these parsed counts (smu71.h / smu72.h): GraphicsLevel[8], + * MemoryLevel[4], UvdLevel/VceLevel/AcpLevel[8]; the voltage lookup tables + * are allocated with max_levels=16 (SMU7x_MAX_LEVELS_VDDC) and the VCE + * state array is hwmgr->vce_states[AMD_MAX_VCE_LEVELS=6]. Clamping here + * removes the firmware-controlled count provenance at its source; the + * per-backend clamps (smu7_hwmgr, fiji/polaris10/iceland smumgr) remain as + * defense in depth. + */ +#define PP_V1_MAX_SCLK_DEP_ENTRIES 8 +#define PP_V1_MAX_MCLK_DEP_ENTRIES 4 +#define PP_V1_MAX_MM_DEP_ENTRIES 8 + +static bool pp_v1_table_range_ok(const struct pp_hwmgr *hwmgr, + uint32_t off, uint32_t len) +{ + if (hwmgr->soft_pp_table == NULL || hwmgr->soft_pp_table_size == 0) + return false; + if (off >= hwmgr->soft_pp_table_size) + return false; + return len <= ((uint32_t)hwmgr->soft_pp_table_size - off); +} + +static uint32_t pp_v1_table_offset_of(const struct pp_hwmgr *hwmgr, + const void *p) +{ + return (uint32_t)((const uint8_t *)p - + (const uint8_t *)hwmgr->soft_pp_table); +} + +/* + * Clamp a firmware entry count to the number of entries that actually fit + * in the table image. off is the offset of the sub-table header, + * header_size covers ucRevId + ucNumEntries, entry_size is one entry. + */ +static uint8_t pp_v1_clamp_count(const struct pp_hwmgr *hwmgr, + uint32_t off, uint32_t header_size, + uint8_t num, uint32_t entry_size) +{ + uint64_t avail; + + if (!pp_v1_table_range_ok(hwmgr, off, header_size + entry_size)) + return 0; + avail = ((uint64_t)hwmgr->soft_pp_table_size - off - header_size) / + entry_size; + if (num > avail) + num = (avail > 255) ? 255 : (uint8_t)avail; + return num; +} + +/* Validate that a sub-table pointer derived as powerplay_table + USHORT + * offset actually holds hdr_len bytes of the image. */ +#define PP_V1_SUBTABLE_OK(hwmgr, ptr, hdr_len) \ + pp_v1_table_range_ok((hwmgr), \ + pp_v1_table_offset_of((hwmgr), (ptr)), (hdr_len)) + /** * Private Function used during initialization. * @param hwmgr Pointer to the hardware manager. @@ -158,6 +226,7 @@ ) { uint32_t table_size, i; + uint8_t num_entries; phm_ppt_v1_voltage_lookup_table *table; phm_ppt_v1_voltage_lookup_record *record; ATOM_Tonga_Voltage_Lookup_Record *atom_record; @@ -165,6 +234,21 @@ PP_ASSERT_WITH_CODE((0 != vddc_lookup_pp_tables->ucNumEntries), "Invalid CAC Leakage PowerPlay Table!", return 1); + /* + * ucNumEntries is firmware-controlled (0..255) while the destination + * allocation holds max_levels (16) records: clamp to both the image + * and the destination capacity. + */ + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, vddc_lookup_pp_tables), + 2 /* ucRevId + ucNumEntries */, + vddc_lookup_pp_tables->ucNumEntries, + sizeof(ATOM_Tonga_Voltage_Lookup_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid CAC Leakage PowerPlay Table!", return 1); + if (num_entries > max_levels) + num_entries = max_levels; + table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_voltage_lookup_record) * max_levels; @@ -173,9 +257,9 @@ if (NULL == table) return -ENOMEM; - table->count = vddc_lookup_pp_tables->ucNumEntries; + table->count = num_entries; - for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++) { + for (i = 0; i < table->count; i++) { record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( phm_ppt_v1_voltage_lookup_record, entries, table, i); @@ -279,7 +363,14 @@ } if (0 != powerplay_table->usVddcLookupTableOffset) { - const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable = + const ATOM_Tonga_Voltage_Lookup_Table *pVddcCACTable; + + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usVddcLookupTableOffset), + sizeof(ATOM_Tonga_Voltage_Lookup_Table))) + return -EINVAL; + + pVddcCACTable = (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) + le16_to_cpu(powerplay_table->usVddcLookupTableOffset)); @@ -288,7 +379,14 @@ } if (0 != powerplay_table->usVddgfxLookupTableOffset) { - const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable = + const ATOM_Tonga_Voltage_Lookup_Table *pVddgfxCACTable; + + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset), + sizeof(ATOM_Tonga_Voltage_Lookup_Table))) + return -EINVAL; + + pVddgfxCACTable = (ATOM_Tonga_Voltage_Lookup_Table *)(((unsigned long)powerplay_table) + le16_to_cpu(powerplay_table->usVddgfxLookupTableOffset)); @@ -298,10 +396,15 @@ 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 (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usPPMTableOffset), + sizeof(ATOM_Tonga_PPM_Table))) + return -EINVAL; + + atom_ppm_table = (ATOM_Tonga_PPM_Table *) + (((unsigned long)powerplay_table) + le16_to_cpu(powerplay_table->usPPMTableOffset)); + if (get_platform_power_management_table(hwmgr, atom_ppm_table) == 0) { phm_cap_set(hwmgr->platform_descriptor.platformCaps, PHM_PlatformCaps_EnablePlatformPowerManagement); @@ -371,6 +474,7 @@ ) { uint32_t table_size, i; + uint8_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *mclk_table; phm_ppt_v1_clock_voltage_dependency_record *mclk_table_record; ATOM_Tonga_MCLK_Dependency_Record *mclk_dep_record; @@ -378,17 +482,27 @@ PP_ASSERT_WITH_CODE((0 != mclk_dep_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, mclk_dep_table), 2, + mclk_dep_table->ucNumEntries, + sizeof(ATOM_Tonga_MCLK_Dependency_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + /* Destination: SMU7x MemoryLevel[SMU__NUM_MCLK_DPM_LEVELS=4]. */ + if (num_entries > PP_V1_MAX_MCLK_DEP_ENTRIES) + num_entries = PP_V1_MAX_MCLK_DEP_ENTRIES; + table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_clock_voltage_dependency_record) - * mclk_dep_table->ucNumEntries; + * num_entries; mclk_table = kzalloc(table_size, GFP_KERNEL); if (NULL == mclk_table) return -ENOMEM; - mclk_table->count = (uint32_t)mclk_dep_table->ucNumEntries; + mclk_table->count = (uint32_t)num_entries; - for (i = 0; i < mclk_dep_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mclk_table_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( phm_ppt_v1_clock_voltage_dependency_record, entries, mclk_table, i); @@ -414,6 +528,7 @@ ) { uint32_t table_size, i; + uint8_t num_entries; phm_ppt_v1_clock_voltage_dependency_table *sclk_table; phm_ppt_v1_clock_voltage_dependency_record *sclk_table_record; @@ -425,17 +540,27 @@ PP_ASSERT_WITH_CODE((0 != tonga_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, tonga_table), 2, + tonga_table->ucNumEntries, + sizeof(ATOM_Tonga_SCLK_Dependency_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + /* Destination: SMU7x GraphicsLevel[SMU__NUM_SCLK_DPM_STATE=8]. */ + if (num_entries > PP_V1_MAX_SCLK_DEP_ENTRIES) + num_entries = PP_V1_MAX_SCLK_DEP_ENTRIES; + table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_clock_voltage_dependency_record) - * tonga_table->ucNumEntries; + * num_entries; sclk_table = kzalloc(table_size, GFP_KERNEL); if (NULL == sclk_table) return -ENOMEM; - sclk_table->count = (uint32_t)tonga_table->ucNumEntries; + sclk_table->count = (uint32_t)num_entries; - for (i = 0; i < tonga_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_SCLK_Dependency_Record, entries, tonga_table, i); @@ -457,17 +582,27 @@ PP_ASSERT_WITH_CODE((0 != polaris_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, polaris_table), 2, + polaris_table->ucNumEntries, + sizeof(ATOM_Polaris_SCLK_Dependency_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + /* Destination: SMU7x GraphicsLevel[SMU__NUM_SCLK_DPM_STATE=8]. */ + if (num_entries > PP_V1_MAX_SCLK_DEP_ENTRIES) + num_entries = PP_V1_MAX_SCLK_DEP_ENTRIES; + table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_clock_voltage_dependency_record) - * polaris_table->ucNumEntries; + * num_entries; sclk_table = kzalloc(table_size, GFP_KERNEL); if (NULL == sclk_table) return -ENOMEM; - sclk_table->count = (uint32_t)polaris_table->ucNumEntries; + sclk_table->count = (uint32_t)num_entries; - for (i = 0; i < polaris_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Polaris_SCLK_Dependency_Record, entries, polaris_table, i); @@ -499,6 +634,14 @@ struct phm_ppt_v1_information *pp_table_information = (struct phm_ppt_v1_information *)(hwmgr->pptable); phm_ppt_v1_pcie_record *pcie_record; + uint8_t num_entries; + + /* + * vdd_dep_on_sclk is NULL when usSclkDependencyTableOffset == 0 or + * its parse failed; both PCIE branches dereference it below. + */ + PP_ASSERT_WITH_CODE((NULL != pp_table_information->vdd_dep_on_sclk), + "Missing SCLK dependency table for PCIE table!", return -EINVAL); if (ptable->ucRevId < 1) { const ATOM_Tonga_PCIE_Table *atom_pcie_table = (ATOM_Tonga_PCIE_Table *)ptable; @@ -507,8 +650,15 @@ PP_ASSERT_WITH_CODE((atom_pcie_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, atom_pcie_table), 2, + atom_pcie_table->ucNumEntries, + sizeof(ATOM_Tonga_PCIE_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + table_size = sizeof(uint32_t) + - sizeof(phm_ppt_v1_pcie_record) * atom_pcie_table->ucNumEntries; + sizeof(phm_ppt_v1_pcie_record) * num_entries; pcie_table = kzalloc(table_size, GFP_KERNEL); @@ -520,8 +670,8 @@ * Since first PCIE entry is for ULV, #pcie has to be <= SclkLevel + 1. */ pcie_count = (pp_table_information->vdd_dep_on_sclk->count) + 1; - if ((uint32_t)atom_pcie_table->ucNumEntries <= pcie_count) - pcie_count = (uint32_t)atom_pcie_table->ucNumEntries; + if ((uint32_t)num_entries <= pcie_count) + pcie_count = (uint32_t)num_entries; else pr_err("Number of Pcie Entries exceed the number of SCLK Dpm Levels! Disregarding the excess entries...\n"); @@ -546,8 +696,15 @@ PP_ASSERT_WITH_CODE((atom_pcie_table->ucNumEntries != 0), "Invalid PowerPlay Table!", return -1); + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, atom_pcie_table), 2, + atom_pcie_table->ucNumEntries, + sizeof(ATOM_Polaris10_PCIE_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + table_size = sizeof(uint32_t) + - sizeof(phm_ppt_v1_pcie_record) * atom_pcie_table->ucNumEntries; + sizeof(phm_ppt_v1_pcie_record) * num_entries; pcie_table = kzalloc(table_size, GFP_KERNEL); @@ -559,8 +716,8 @@ * Since first PCIE entry is for ULV, #pcie has to be <= SclkLevel + 1. */ pcie_count = (pp_table_information->vdd_dep_on_sclk->count) + 1; - if ((uint32_t)atom_pcie_table->ucNumEntries <= pcie_count) - pcie_count = (uint32_t)atom_pcie_table->ucNumEntries; + if ((uint32_t)num_entries <= pcie_count) + pcie_count = (uint32_t)num_entries; else pr_err("Number of Pcie Entries exceed the number of SCLK Dpm Levels! Disregarding the excess entries...\n"); @@ -593,6 +750,13 @@ uint32_t table_size; struct phm_cac_tdp_table *tdp_table; + /* The ucRevId dispatch below selects between two table layouts; make + * sure the dispatched struct actually fits the image. */ + if (!PP_V1_SUBTABLE_OK(hwmgr, table, + (table->ucRevId < 3) ? sizeof(ATOM_Tonga_PowerTune_Table) + : sizeof(ATOM_Fiji_PowerTune_Table))) + return -EINVAL; + table_size = sizeof(uint32_t) + sizeof(struct phm_cac_tdp_table); tdp_table = kzalloc(table_size, GFP_KERNEL); @@ -691,23 +855,35 @@ ) { uint32_t table_size, i; + uint8_t num_entries; const ATOM_Tonga_MM_Dependency_Record *mm_dependency_record; phm_ppt_v1_mm_clock_voltage_dependency_table *mm_table; phm_ppt_v1_mm_clock_voltage_dependency_record *mm_table_record; PP_ASSERT_WITH_CODE((0 != mm_dependency_table->ucNumEntries), "Invalid PowerPlay Table!", return -1); + + num_entries = pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, mm_dependency_table), 2, + mm_dependency_table->ucNumEntries, + sizeof(ATOM_Tonga_MM_Dependency_Record)); + PP_ASSERT_WITH_CODE((0 != num_entries), + "Invalid PowerPlay Table!", return -1); + /* Destination: SMU7x UvdLevel[8]/VceLevel[8]/AcpLevel[8]. */ + if (num_entries > PP_V1_MAX_MM_DEP_ENTRIES) + num_entries = PP_V1_MAX_MM_DEP_ENTRIES; + table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_mm_clock_voltage_dependency_record) - * mm_dependency_table->ucNumEntries; + * num_entries; mm_table = kzalloc(table_size, GFP_KERNEL); if (NULL == mm_table) return -ENOMEM; - mm_table->count = mm_dependency_table->ucNumEntries; + mm_table->count = num_entries; - for (i = 0; i < mm_dependency_table->ucNumEntries; i++) { + for (i = 0; i < num_entries; i++) { mm_dependency_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( ATOM_Tonga_MM_Dependency_Record, entries, mm_dependency_table, i); @@ -742,6 +918,11 @@ if (!pp_gpio_table) return -ENOMEM; + /* vdd_dep_on_sclk is NULL when usSclkDependencyTableOffset == 0. */ + PP_ASSERT_WITH_CODE((NULL != pp_table_information->vdd_dep_on_sclk), + "Missing SCLK dependency table for GPIO table!", + kfree(pp_gpio_table); return -EINVAL); + if (pp_table_information->vdd_dep_on_sclk->count < atom_gpio_table->ucVRHotTriggeredSclkDpmIndex) PP_ASSERT_WITH_CODE(false, @@ -769,27 +950,13 @@ 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 = NULL; + const PPTable_Generic_SubTable_Header *pPowerTuneTable = NULL; + const ATOM_Tonga_MCLK_Dependency_Table *mclk_dep_table = NULL; + const PPTable_Generic_SubTable_Header *sclk_dep_table = NULL; + const ATOM_Tonga_Hard_Limit_Table *pHardLimits = NULL; + const PPTable_Generic_SubTable_Header *pcie_table = NULL; + const ATOM_Tonga_GPIO_Table *gpio_table = NULL; pp_table_information->vdd_dep_on_sclk = NULL; pp_table_information->vdd_dep_on_mclk = NULL; @@ -797,6 +964,80 @@ pp_table_information->pcie_table = NULL; pp_table_information->gpio_table = NULL; + /* + * Each USHORT offset is firmware-controlled and unbounded: validate + * the sub-table header fits the image before any dereference. + */ + if (powerplay_table->usMMDependencyTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usMMDependencyTableOffset), + sizeof(ATOM_Tonga_MM_Dependency_Table))) + return -EINVAL; + mm_dependency_table = + (const ATOM_Tonga_MM_Dependency_Table *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usMMDependencyTableOffset)); + } + + if (powerplay_table->usPowerTuneTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usPowerTuneTableOffset), + sizeof(PPTable_Generic_SubTable_Header))) + return -EINVAL; + pPowerTuneTable = + (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usPowerTuneTableOffset)); + } + + if (powerplay_table->usMclkDependencyTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usMclkDependencyTableOffset), + sizeof(ATOM_Tonga_MCLK_Dependency_Table))) + return -EINVAL; + mclk_dep_table = + (const ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usMclkDependencyTableOffset)); + } + + if (powerplay_table->usSclkDependencyTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usSclkDependencyTableOffset), + sizeof(PPTable_Generic_SubTable_Header))) + return -EINVAL; + sclk_dep_table = + (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usSclkDependencyTableOffset)); + } + + if (powerplay_table->usHardLimitTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usHardLimitTableOffset), + sizeof(ATOM_Tonga_Hard_Limit_Table))) + return -EINVAL; + pHardLimits = + (const ATOM_Tonga_Hard_Limit_Table *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usHardLimitTableOffset)); + } + + if (powerplay_table->usPCIETableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usPCIETableOffset), + sizeof(PPTable_Generic_SubTable_Header))) + return -EINVAL; + pcie_table = + (const PPTable_Generic_SubTable_Header *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usPCIETableOffset)); + } + + if (powerplay_table->usGPIOTableOffset != 0) { + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(powerplay_table->usGPIOTableOffset), + sizeof(ATOM_Tonga_GPIO_Table))) + return -EINVAL; + gpio_table = + (const ATOM_Tonga_GPIO_Table *)(((unsigned long) powerplay_table) + + le16_to_cpu(powerplay_table->usGPIOTableOffset)); + } + if (powerplay_table->usMMDependencyTableOffset != 0) result = get_mm_clock_voltage_table(hwmgr, &pp_table_information->mm_dep_table, mm_dependency_table); @@ -893,6 +1134,9 @@ le16_to_cpu(powerplay_table->usThermalControllerOffset)); PP_ASSERT_WITH_CODE((0 != powerplay_table->usThermalControllerOffset), "Thermal controller table not set!", return -1); + if (!PP_V1_SUBTABLE_OK(hwmgr, thermal_controller, + sizeof(ATOM_Tonga_Thermal_Controller))) + return -1; hwmgr->thermal_controller.ucType = thermal_controller->ucType; hwmgr->thermal_controller.ucI2cLine = thermal_controller->ucI2cLine; @@ -925,8 +1169,15 @@ PP_ASSERT_WITH_CODE((0 != powerplay_table->usFanTableOffset), "Fan table not set!", return -1); + if (!PP_V1_SUBTABLE_OK(hwmgr, fan_table, + sizeof(PPTable_Generic_SubTable_Header))) + return -1; PP_ASSERT_WITH_CODE((0 < fan_table->ucRevId), "Unsupported fan table format!", return -1); + if (!PP_V1_SUBTABLE_OK(hwmgr, fan_table, + (fan_table->ucRevId < 8) ? sizeof(ATOM_Tonga_Fan_Table) + : sizeof(ATOM_Fiji_Fan_Table))) + return -1; hwmgr->thermal_controller.advanceFanControlParameters.ulCycleDelay = 100000; @@ -1047,6 +1298,9 @@ "Unsupported PPTable format!", return -1); PP_ASSERT_WITH_CODE((0 != powerplay_table->usStateArrayOffset), "State table is not set!", return -1); + if (!PP_V1_SUBTABLE_OK(hwmgr, state_arrays, + sizeof(ATOM_Tonga_State_Array))) + return -1; PP_ASSERT_WITH_CODE((0 < powerplay_table->sHeader.usStructureSize), "Invalid PowerPlay Table!", return -1); PP_ASSERT_WITH_CODE((0 < state_arrays->ucNumEntries), @@ -1072,6 +1326,11 @@ PP_ASSERT_WITH_CODE((NULL != powerplay_table), "Missing PowerPlay Table!", return -1); + /* Every fixed-header read below (all USHORT sub-table offsets, caps, + * overdrive limits) must stay inside the actual table image. */ + if (!pp_v1_table_range_ok(hwmgr, 0, sizeof(ATOM_Tonga_POWERPLAYTABLE))) + return -EINVAL; + result = check_powerplay_tables(hwmgr, powerplay_table); PP_ASSERT_WITH_CODE((result == 0), @@ -1172,6 +1431,11 @@ state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)pp_table) + le16_to_cpu(pp_table->usStateArrayOffset)); + if (0 == pp_table->usStateArrayOffset || + !PP_V1_SUBTABLE_OK(hwmgr, state_arrays, + sizeof(ATOM_Tonga_State_Array))) + return 0; + return (uint32_t)(state_arrays->ucNumEntries); } @@ -1212,10 +1476,17 @@ const ATOM_Tonga_POWERPLAYTABLE *pp_table = get_powerplay_table(hwmgr); const ATOM_Tonga_VCE_State_Table *vce_state_table; - if (pp_table == NULL) return 0; + /* An omitted (or out-of-image) VCE table means "no VCE states"; + * do not fall back to reading the PowerPlay header itself. */ + if (0 == pp_table->usVCEStateTableOffset || + !pp_v1_table_range_ok(hwmgr, + le16_to_cpu(pp_table->usVCEStateTableOffset), + sizeof(ATOM_Tonga_VCE_State_Table))) + return 0; + vce_state_table = (void *)pp_table + le16_to_cpu(pp_table->usVCEStateTableOffset); @@ -1229,47 +1500,88 @@ ATOM_Tonga_SCLK_Dependency_Record *sclk_dep_record; ATOM_Tonga_MCLK_Dependency_Record *mclk_dep_record; ATOM_Tonga_MM_Dependency_Record *mm_dep_record; + uint8_t sclk_idx, mm_idx, mclk_idx; 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 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; + + /* All four sub-tables are addressed by firmware USHORT offsets. */ + if (pptable == NULL || + 0 == pptable->usVCEStateTableOffset || + 0 == pptable->usSclkDependencyTableOffset || + 0 == pptable->usMclkDependencyTableOffset || + 0 == pptable->usMMDependencyTableOffset) + return -EINVAL; + + vce_state_table = (ATOM_Tonga_VCE_State_Table *)(((unsigned long)pptable) + + le16_to_cpu(pptable->usVCEStateTableOffset)); + sclk_dep_table = (ATOM_Tonga_SCLK_Dependency_Table *)(((unsigned long)pptable) + + le16_to_cpu(pptable->usSclkDependencyTableOffset)); + mclk_dep_table = (ATOM_Tonga_MCLK_Dependency_Table *)(((unsigned long)pptable) + + le16_to_cpu(pptable->usMclkDependencyTableOffset)); + mm_dep_table = (ATOM_Tonga_MM_Dependency_Table *)(((unsigned long)pptable) + + le16_to_cpu(pptable->usMMDependencyTableOffset)); + + if (!pp_v1_table_range_ok(hwmgr, + le16_to_cpu(pptable->usVCEStateTableOffset), + sizeof(ATOM_Tonga_VCE_State_Table) + + i * sizeof(ATOM_Tonga_VCE_State_Record)) || + !PP_V1_SUBTABLE_OK(hwmgr, sclk_dep_table, + sizeof(ATOM_Tonga_SCLK_Dependency_Table)) || + !PP_V1_SUBTABLE_OK(hwmgr, mclk_dep_table, + sizeof(ATOM_Tonga_MCLK_Dependency_Table)) || + !PP_V1_SUBTABLE_OK(hwmgr, mm_dep_table, + sizeof(ATOM_Tonga_MM_Dependency_Table))) + return -EINVAL; PP_ASSERT_WITH_CODE((i < vce_state_table->ucNumEntries), "Requested state entry ID is out of range!", return -EINVAL); vce_state_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_VCE_State_Record, - entries, vce_state_table, i); + ATOM_Tonga_VCE_State_Record, + entries, vce_state_table, i); + + /* + * ucSCLKIndex/ucVCEClockIndex/ucMCLKIndex are firmware UCHARs used + * unchecked as entry indices; clamp each to the number of entries + * that actually fit in the image. + */ + sclk_idx = vce_state_record->ucSCLKIndex; + if (sclk_idx >= pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, sclk_dep_table), 2, + 0xFF, sizeof(ATOM_Tonga_SCLK_Dependency_Record))) + sclk_idx = 0; + + mm_idx = vce_state_record->ucVCEClockIndex; + if (mm_idx >= pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, mm_dep_table), 2, + 0xFF, sizeof(ATOM_Tonga_MM_Dependency_Record))) + mm_idx = 0; + + mclk_idx = vce_state_record->ucMCLKIndex; + if (mclk_idx >= pp_v1_clamp_count(hwmgr, + pp_v1_table_offset_of(hwmgr, mclk_dep_table), 2, + 0xFF, sizeof(ATOM_Tonga_MCLK_Dependency_Record))) + mclk_idx = 0; + sclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_SCLK_Dependency_Record, - entries, sclk_dep_table, - vce_state_record->ucSCLKIndex); + ATOM_Tonga_SCLK_Dependency_Record, + entries, sclk_dep_table, sclk_idx); mm_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_MM_Dependency_Record, - entries, mm_dep_table, - vce_state_record->ucVCEClockIndex); + ATOM_Tonga_MM_Dependency_Record, + entries, mm_dep_table, mm_idx); *flag = vce_state_record->ucFlag; 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 (vce_state_record->ucMCLKIndex >= mclk_dep_table->ucNumEntries) - mclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_MCLK_Dependency_Record, - entries, mclk_dep_table, - mclk_dep_table->ucNumEntries - 1); - else - mclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_MCLK_Dependency_Record, - entries, mclk_dep_table, - vce_state_record->ucMCLKIndex); + mclk_dep_record = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( + ATOM_Tonga_MCLK_Dependency_Record, + entries, mclk_dep_table, mclk_idx); vce_state->mclk = le32_to_cpu(mclk_dep_record->ulMclk); return 0; @@ -1300,19 +1612,30 @@ if (pp_table->sHeader.ucTableFormatRevision >= ATOM_Tonga_TABLE_REVISION_TONGA) { + PP_ASSERT_WITH_CODE((0 < pp_table->usStateArrayOffset), + "Invalid PowerPlay Table State Array Offset.", return -1); + state_arrays = (ATOM_Tonga_State_Array *)(((unsigned long)pp_table) + le16_to_cpu(pp_table->usStateArrayOffset)); - PP_ASSERT_WITH_CODE((0 < pp_table->usStateArrayOffset), - "Invalid PowerPlay Table State Array Offset.", return -1); + if (!PP_V1_SUBTABLE_OK(hwmgr, state_arrays, + sizeof(ATOM_Tonga_State_Array))) + return -1; PP_ASSERT_WITH_CODE((0 < state_arrays->ucNumEntries), "Invalid PowerPlay Table State Array.", return -1); - PP_ASSERT_WITH_CODE((entry_index <= state_arrays->ucNumEntries), + PP_ASSERT_WITH_CODE((entry_index < state_arrays->ucNumEntries), "Invalid PowerPlay Table State Array Entry.", return -1); + if (!pp_v1_table_range_ok(hwmgr, + pp_v1_table_offset_of(hwmgr, state_arrays) + + offsetof(ATOM_Tonga_State_Array, entries) + + entry_index * sizeof(ATOM_Tonga_State), + sizeof(ATOM_Tonga_State))) + return -1; + state_entry = GET_FLEXIBLE_ARRAY_MEMBER_ADDR( - ATOM_Tonga_State, entries, - state_arrays, entry_index); + ATOM_Tonga_State, entries, + state_arrays, entry_index); result = call_back_func(hwmgr, (void *)state_entry, power_state, (void *)pp_table, @@ -1325,12 +1648,21 @@ PP_StateClassificationFlag_Boot)) result = hwmgr->hwmgr_func->patch_boot_state(hwmgr, &(power_state->hardware)); - hwmgr->num_vce_state_tables = i = ppt_get_num_of_vce_state_table_entries_v1_0(hwmgr); - - if ((i != 0) && (i <= AMD_MAX_VCE_LEVELS)) { - for (j = 0; j < i; j++) - ppt_get_vce_state_table_entry_v1_0(hwmgr, j, &(hwmgr->vce_states[j]), NULL, &flags); + /* + * hwmgr->vce_states[] holds AMD_MAX_VCE_LEVELS entries: the stored + * count must be clamped to the array capacity or any consumer + * walking num_vce_state_tables reads past it (the amdgpu DPM path + * already clamps this way, amdgpu_dpm.c:580). + */ + i = ppt_get_num_of_vce_state_table_entries_v1_0(hwmgr); + if (i > AMD_MAX_VCE_LEVELS) { + pr_err("Number of VCE state entries exceeds AMD_MAX_VCE_LEVELS! Disregarding the excess entries...\n"); + i = AMD_MAX_VCE_LEVELS; } + hwmgr->num_vce_state_tables = i; + + for (j = 0; j < i; j++) + ppt_get_vce_state_table_entry_v1_0(hwmgr, j, &(hwmgr->vce_states[j]), NULL, &flags); return result; }