From 4d5debea9ed853f066a9d3584fc242bdef4a34f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Tue, 21 Jan 2025 18:46:55 +0100 Subject: [PATCH] amd_thermal: new driver inspired by ksmn in OpenBSD Change-Id: Ic272fc15f1ea082496db99ed2df15c77655ba9c7 Reviewed-on: https://review.haiku-os.org/c/haiku/+/8855 Tested-by: Commit checker robot Reviewed-by: waddlesplash --- src/add-ons/kernel/drivers/power/Jamfile | 1 + .../kernel/drivers/power/amd_thermal/Jamfile | 8 + .../drivers/power/amd_thermal/amd_thermal.cpp | 390 ++++++++++++++++++ .../drivers/power/amd_thermal/amd_thermal.h | 34 ++ 4 files changed, 433 insertions(+) create mode 100644 src/add-ons/kernel/drivers/power/amd_thermal/Jamfile create mode 100644 src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.cpp create mode 100644 src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.h diff --git a/src/add-ons/kernel/drivers/power/Jamfile b/src/add-ons/kernel/drivers/power/Jamfile index 4c3c6ad964..7c7efe330d 100644 --- a/src/add-ons/kernel/drivers/power/Jamfile +++ b/src/add-ons/kernel/drivers/power/Jamfile @@ -5,5 +5,6 @@ SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_battery ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_button ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_lid ; SubInclude HAIKU_TOP src add-ons kernel drivers power acpi_thermal ; +SubInclude HAIKU_TOP src add-ons kernel drivers power amd_thermal ; SubInclude HAIKU_TOP src add-ons kernel drivers power pch_thermal ; SubInclude HAIKU_TOP src add-ons kernel drivers power x86_cpuidle ; diff --git a/src/add-ons/kernel/drivers/power/amd_thermal/Jamfile b/src/add-ons/kernel/drivers/power/amd_thermal/Jamfile new file mode 100644 index 0000000000..40ef5f096b --- /dev/null +++ b/src/add-ons/kernel/drivers/power/amd_thermal/Jamfile @@ -0,0 +1,8 @@ +SubDir HAIKU_TOP src add-ons kernel drivers power amd_thermal ; + +UsePrivateKernelHeaders ; + +KernelAddon amd_thermal : + amd_thermal.cpp + ; + diff --git a/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.cpp b/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.cpp new file mode 100644 index 0000000000..f31a092288 --- /dev/null +++ b/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.cpp @@ -0,0 +1,390 @@ +/* + * Copyright 2025, Jérôme Duval, jerome.duval@gmail.com. + * + * Distributed under the terms of the MIT License. + * + * AMD Thermal driver. + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "amd_thermal.h" + + +#define AMD_THERMAL_MODULE_NAME "drivers/power/amd_thermal/driver_v1" + +#define AMD_THERMAL_DEVICE_MODULE_NAME "drivers/power/amd_thermal/device_v1" + +/* Base Namespace devices are published to */ +#define AMD_THERMAL_BASENAME "power/amd_thermal/%d" + +// name of pnp generator of path ids +#define AMD_THERMAL_PATHID_GENERATOR "amd_thermal/path_id" + +static device_manager_info* sDeviceManager; + + +//#define TRACE_AMD_THERMAL +#ifdef TRACE_AMD_THERMAL +# define TRACE(x...) dprintf("amd_thermal: " x) +#else +# define TRACE(x...) +#endif +#define ERROR(x...) dprintf("amd_thermal: " x) + + +typedef struct amd_thermal_device_info { + device_node* node; + pci_device_module_info* pci; + pci_device* pci_cookie; + + uint32 ccdOffset; + uint32 tctlOffset; + uint32 ccdValid; +} amd_thermal_device_info; + + +status_t amd_thermal_control(void* _cookie, uint32 op, void* arg, size_t len); + + +static uint32 +ksmn_read_reg(amd_thermal_device_info* device, uint32 address) +{ + device->pci->write_pci_config(device->pci_cookie, AMD_SMN_17H_ADDR, 4, address); + return device->pci->read_pci_config(device->pci_cookie, AMD_SMN_17H_DATA, 4); +} + + +static void +ksmn_ccd_init(amd_thermal_device_info* device, int ccpCount) +{ + for (int i = 0; i < ccpCount; i++) { + uint32 reg = ksmn_read_reg(device, AMD_SMU_17H_CCD_THM(device->ccdOffset, i)); + if ((reg & CURTMP_CCD_VALID) == 0) + continue; + device->ccdValid |= (1 << i); + } +} + + +static status_t +amd_thermal_open(void* _cookie, const char* path, int flags, void** cookie) +{ + amd_thermal_device_info* device = (amd_thermal_device_info*)_cookie; + TRACE("amd_thermal_open %p\n", device); + *cookie = device; + return B_OK; +} + + +static status_t +amd_thermal_read(void* _cookie, off_t position, void* buf, size_t* num_bytes) +{ + amd_thermal_device_info* device = (amd_thermal_device_info*)_cookie; + TRACE("amd_thermal_read %p\n", device); + amd_thermal_type therm_info; + if (*num_bytes < 1) + return B_IO_ERROR; + + if (position == 0) { + char buffer[128]; + TRACE("amd_thermal: read()\n"); + amd_thermal_control(device, drvOpGetThermalType, &therm_info, 0); + + int32 copied = snprintf(buffer, sizeof(buffer), + " Current Temperature: %" B_PRIu32 ".%" B_PRIu32 " C\n", + (therm_info.current_temp / 10), (therm_info.current_temp % 10)); + + copied = min_c((int32)*num_bytes, copied + 1); + if (user_memcpy((char*)buf, buffer, copied) != B_OK) + return B_BAD_ADDRESS; + *num_bytes = copied; + } else { + *num_bytes = 0; + } + + return B_OK; +} + + +static status_t +amd_thermal_write(void* cookie, off_t position, const void* buffer, size_t* num_bytes) +{ + return B_ERROR; +} + + +status_t +amd_thermal_control(void* _cookie, uint32 op, void* arg, size_t len) +{ + amd_thermal_device_info* device = (amd_thermal_device_info*)_cookie; + status_t err = B_ERROR; + + amd_thermal_type* att = NULL; + + switch (op) { + case drvOpGetThermalType: + { + att = (amd_thermal_type*)arg; + + // Read basic temperature thresholds. + uint32 data = ksmn_read_reg(device, AMD_SMU_17H_THM); + uint16 raw = GET_CURTMP(data); + int32 offset = 0; + if ((data & CURTMP_17H_RANGE_SELECTION) != 0) + offset -= CURTMP_17H_RANGE_ADJUST; + offset -= device->tctlOffset; + offset *= 100000; + // convert to microCelsius then offset + uint32 value = raw * 125000 + offset; + // convert to tenth of Celsius + att->current_temp = value / 100000; + err = B_OK; + break; + } + } + return err; +} + + +static status_t +amd_thermal_close(void* cookie) +{ + return B_OK; +} + + +static status_t +amd_thermal_free(void* cookie) +{ + return B_OK; +} + + +// #pragma mark - driver module API + + +static float +amd_thermal_support(device_node* parent) +{ + const char* bus; + + // make sure parent is really the PCI bus manager + if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false)) + return -1; + + if (strcmp(bus, "pci")) + return 0.0; + + uint16 vendorID; + uint16 deviceID; + + // get vendor and device ID + if (sDeviceManager->get_attr_uint16(parent, B_DEVICE_VENDOR_ID, &vendorID, false) != B_OK + || sDeviceManager->get_attr_uint16(parent, B_DEVICE_ID, &deviceID, false) != B_OK) { + return -1; + } + + // check whether it's really an AMD thermal device + if (vendorID != 0x1022) + return 0.0; + + const uint16 devices[] + = {0x1450, 0x1480, 0x15d0, 0x1630, 0x14a4, 0x14b5, 0x14d8, 0x14e8, 0x1507, 0}; + for (const uint16* device = devices; *device != 0; device++) { + if (*device == deviceID) + return 0.6; + } + + return 0.0; +} + + +static status_t +amd_thermal_register_device(device_node* node) +{ + device_attr attrs[] = { + { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "AMD Thermal" }}, + { NULL } + }; + + return sDeviceManager->register_node(node, AMD_THERMAL_MODULE_NAME, attrs, NULL, NULL); +} + + +static status_t +amd_thermal_init_driver(device_node* node, void** _driverCookie) +{ + *_driverCookie = node; + + return B_OK; +} + + +static void +amd_thermal_uninit_driver(void* driverCookie) +{ +} + + +static status_t +amd_thermal_register_child_devices(void* _cookie) +{ + device_node* node = (device_node*)_cookie; + int path_id; + char name[128]; + + path_id = sDeviceManager->create_id(AMD_THERMAL_PATHID_GENERATOR); + if (path_id < 0) { + ERROR("amd_thermal_register_child_devices: couldn't create a path_id\n"); + return B_ERROR; + } + + snprintf(name, sizeof(name), AMD_THERMAL_BASENAME, path_id); + + return sDeviceManager->publish_device(node, name, AMD_THERMAL_DEVICE_MODULE_NAME); +} + + +static status_t +amd_thermal_init_device(void* _cookie, void** cookie) +{ + device_node* node = (device_node*)_cookie; + amd_thermal_device_info* device; + + device = (amd_thermal_device_info*)calloc(1, sizeof(*device)); + if (device == NULL) + return B_NO_MEMORY; + + device->node = node; + device->tctlOffset = 0; + device->ccdValid = 0; + + { + device_node* parent = sDeviceManager->get_parent_node(node); + sDeviceManager->get_driver(parent, (driver_module_info**)&device->pci, + (void**)&device->pci_cookie); + sDeviceManager->put_node(parent); + } + + if (gCPU[0].arch.family == 0x17 || gCPU[0].arch.family == 0x18) { + switch (gCPU[0].arch.model) { + case 0x1: + case 0x8: + case 0x11: + case 0x18: + device->ccdOffset = 0x154; + ksmn_ccd_init(device, 4); + break; + case 0x31: + case 0x60: + case 0x68: + case 0x71: + device->ccdOffset = 0x154; + ksmn_ccd_init(device, 8); + break; + case 0xa0 ... 0xaf: + device->ccdOffset = 0x300; + ksmn_ccd_init(device, 8); + break; + } + } else if (gCPU[0].arch.family == 0x19) { + switch (gCPU[0].arch.model) { + case 0x0 ... 0x1: + case 0x8: + case 0x21: + case 0x50 ... 0x5f: + device->ccdOffset = 0x154; + ksmn_ccd_init(device, 8); + break; + case 0x60 ... 0x6f: + case 0x70 ... 0x7f: + device->ccdOffset = 0x308; + ksmn_ccd_init(device, 8); + break; + case 0x10 ... 0x1f: + case 0xa0 ... 0xaf: + device->ccdOffset = 0x300; + ksmn_ccd_init(device, 12); + break; + } + } + TRACE("amd_thermal_init_device %p\n", device); + + *cookie = device; + return B_OK; +} + + +static void +amd_thermal_uninit_device(void* _cookie) +{ + amd_thermal_device_info* device = (amd_thermal_device_info*)_cookie; + TRACE("amd_thermal_uninit_device %p\n", device); + free(device); +} + + + +module_dependency module_dependencies[] = { + { B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager }, + {} +}; + + +driver_module_info amd_thermal_driver_module = { + { + AMD_THERMAL_MODULE_NAME, + 0, + NULL + }, + + amd_thermal_support, + amd_thermal_register_device, + amd_thermal_init_driver, + amd_thermal_uninit_driver, + amd_thermal_register_child_devices, + NULL, // rescan + NULL, // removed +}; + + +struct device_module_info amd_thermal_device_module = { + { + AMD_THERMAL_DEVICE_MODULE_NAME, + 0, + NULL + }, + + amd_thermal_init_device, + amd_thermal_uninit_device, + NULL, + + amd_thermal_open, + amd_thermal_close, + amd_thermal_free, + amd_thermal_read, + amd_thermal_write, + NULL, + amd_thermal_control, + + NULL, + NULL +}; + +module_info *modules[] = { + (module_info *)&amd_thermal_driver_module, + (module_info *)&amd_thermal_device_module, + NULL +}; diff --git a/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.h b/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.h new file mode 100644 index 0000000000..38cab5656c --- /dev/null +++ b/src/add-ons/kernel/drivers/power/amd_thermal/amd_thermal.h @@ -0,0 +1,34 @@ +/* + * Copyright 2025, Jérôme Duval, jerome.duval@gmail.com. + * + * Distributed under the terms of the MIT License. + */ +#ifndef _AMD_THERMAL_H +#define _AMD_THERMAL_H + + +enum { /* ioctl op-codes */ + drvOpGetThermalType = B_DEVICE_OP_CODES_END + 10001, +}; + + +struct amd_thermal_type { + /* Required fields for thermal devices */ + uint32 current_temp; +}; + +// Registers +#define AMD_SMN_17H_ADDR 0x60 +#define AMD_SMN_17H_DATA 0x64 + +#define AMD_SMU_17H_THM 0x59800 +#define AMD_SMU_17H_CCD_THM(x, y) (AMD_SMU_17H_THM + (x) + ((y) * 4)) + +#define GET_CURTMP(reg) (((reg) >> 21) & 0x7ff) + +#define CURTMP_17H_RANGE_SELECTION (1 << 19) +#define CURTMP_17H_RANGE_ADJUST 490 +#define CURTMP_CCD_VALID (1 << 11) +#define CURTMP_CCD_MASK 0x7ff + +#endif // _AMD_THERMAL_H