* [PATCH v6 0/2] hwmon: add Altera SoC FPGA hardware monitoring support
@ 2026-07-20 3:25 tze.yee.ng
2026-07-20 3:25 ` [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device tze.yee.ng
2026-07-20 3:25 ` [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver tze.yee.ng
0 siblings, 2 replies; 9+ messages in thread
From: tze.yee.ng @ 2026-07-20 3:25 UTC (permalink / raw)
To: Dinh Nguyen, linux-kernel, Guenter Roeck, Jonathan Corbet,
Shuah Khan, linux-hwmon, linux-doc
From: Tze Yee Ng <tze.yee.ng@altera.com>
This series adds hardware monitor support for Altera SoC FPGA devices.
Temperature and voltage sensors are accessed through the Stratix 10
service layer and Secure Device Manager (SDM).
Patch 1 adds async HWMON SMC support to stratix10-svc and registers the
socfpga-hwmon platform device.
Patch 2 adds the socfpga-hwmon driver, documentation, Kconfig, and
MAINTAINERS entry.
Changes in v6:
- Rebase on torvalds/master (given “Linux 7.2-rc4”)
- No functional changes in Patch 1 and Patch 2
Changes in v5:
- Rebase on dinguyen/socfpga_svc_fixes_for_v7.2
- Address Sashiko review feedback on socfpga-hwmon (Patch 2):
- Poll async responses until HWMON_TIMEOUT instead of a fixed
3-iteration retry loop (~3 ms), fixing premature timeouts on
silicon
- Add MODULE_ALIAS("platform:socfpga-hwmon")
- No functional changes in Patch 1
Changes in v4:
- Address maintainer and review feedback on socfpga-hwmon (Patch 2):
- Register devm_add_action_or_reset() before
devm_hwmon_device_register_with_info() to fix devres teardown order
- Remove unreferenced completion and pre-poll
wait_for_completion_io_timeout() from async reads; poll directly
with a retry loop after async_send()
- No functional changes in Patch 1
Changes in v3:
- Address review feedback on socfpga-hwmon (Patch 2):
- Fix 16-bit Q8.8 temperature sign extension
- Drop unused async callback; pass NULL to stratix10_svc_async_send()
- Document and retain pre-poll wait (RSU pattern; firmware needs time
before async_poll())
- Align async poll retry behaviour with RSU
- Use uninterruptible wait_for_completion_timeout() for sync reads
- Handle -EINVAL and -EOPNOTSUPP when falling back to sync mode
- Defer SVC channel cleanup via devm_add_action_or_reset()
- No functional changes in Patch 1
Changes in v2:
- Drop altr,stratix10-hwmon DT binding and intel,stratix10-svc hwmon
child property
- Drop Stratix 10 SoCDK DTS hwmon node
- Register socfpga-hwmon from stratix10-svc (RSU-style)
- Replace DT channel parsing with hardcoded Stratix 10 and Agilex tables
- Rename driver/module to socfpga-hwmon
(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)
- Add Agilex channel support
- Fix SDM value conversion (Q8.8 degrees Celsius and Q16 volts to hwmon
millidegrees/millivolts)
- Improve sync-mode error handling via last_err
Previous version:
https://lore.kernel.org/all/cover.1784096224.git.tze.yee.ng@altera.com/
Tze Yee Ng (2):
firmware: stratix10-svc: add async HWMON read commands and register
socfpga-hwmon device
hwmon: add Altera SoC FPGA hardware monitoring driver
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/socfpga-hwmon.rst | 34 ++
MAINTAINERS | 8 +
drivers/firmware/stratix10-svc.c | 46 +-
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/socfpga-hwmon.c | 579 +++++++++++++++++++
include/linux/firmware/intel/stratix10-smc.h | 38 ++
8 files changed, 714 insertions(+), 3 deletions(-)
create mode 100644 Documentation/hwmon/socfpga-hwmon.rst
create mode 100644 drivers/hwmon/socfpga-hwmon.c
--
2.43.7
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device 2026-07-20 3:25 [PATCH v6 0/2] hwmon: add Altera SoC FPGA hardware monitoring support tze.yee.ng @ 2026-07-20 3:25 ` tze.yee.ng 2026-07-20 3:37 ` sashiko-bot 2026-07-20 3:25 ` [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver tze.yee.ng 1 sibling, 1 reply; 9+ messages in thread From: tze.yee.ng @ 2026-07-20 3:25 UTC (permalink / raw) To: Dinh Nguyen, linux-kernel, Guenter Roeck, Jonathan Corbet, Shuah Khan, linux-hwmon, linux-doc From: Tze Yee Ng <tze.yee.ng@altera.com> Add asynchronous Stratix 10 service layer support for hardware monitor temperature and voltage read commands in stratix10_svc_async_send() and stratix10_svc_async_prepare_response(). Register a socfpga-hwmon platform device from the service layer driver when hardware monitor support is enabled, similar to the RSU device. Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com> --- Changes in v6: - No functional changes from v5 Changes in v5: - No functional changes from v4 Changes in v3: - No functional changes from v2 Changes in v2: - Extend patch scope beyond async SMC support: register socfpga-hwmon platform device from stratix10-svc when CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON is enabled - Follow RSU-style registration; RSU probe error handling is unchanged - Add err_unregister_clients to unregister hwmon and RSU on populate failure - Unregister hwmon platform device in stratix10-svc remove() --- drivers/firmware/stratix10-svc.c | 46 ++++++++++++++++++-- include/linux/firmware/intel/stratix10-smc.h | 38 ++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index c24ca5823078..fc38afed5b7f 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -45,6 +45,7 @@ /* stratix10 service layer clients */ #define STRATIX10_RSU "stratix10-rsu" +#define SOCFPGA_HWMON "socfpga-hwmon" /* Maximum number of SDM client IDs. */ #define MAX_SDM_CLIENT_IDS 16 @@ -104,9 +105,11 @@ struct stratix10_svc_chan; /** * struct stratix10_svc - svc private data * @stratix10_svc_rsu: pointer to stratix10 RSU device + * @stratix10_svc_hwmon: pointer to stratix10 HWMON device */ struct stratix10_svc { struct platform_device *stratix10_svc_rsu; + struct platform_device *stratix10_svc_hwmon; }; /** @@ -1329,6 +1332,14 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg, args.a0 = INTEL_SIP_SMC_ASYNC_RSU_NOTIFY; args.a2 = p_msg->arg[0]; break; + case COMMAND_HWMON_READTEMP: + args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READTEMP; + args.a2 = p_msg->arg[0]; + break; + case COMMAND_HWMON_READVOLT: + args.a0 = INTEL_SIP_SMC_ASYNC_HWMON_READVOLT; + args.a2 = p_msg->arg[0]; + break; default: dev_err(ctrl->dev, "Invalid command ,%d\n", p_msg->command); ret = -EINVAL; @@ -1422,6 +1433,10 @@ static int stratix10_svc_async_prepare_response(struct stratix10_svc_chan *chan, */ data->kaddr1 = (void *)&handle->res; break; + case COMMAND_HWMON_READTEMP: + case COMMAND_HWMON_READVOLT: + data->kaddr1 = (void *)&handle->res.a2; + break; default: dev_alert(ctrl->dev, "Invalid command\n ,%d", p_msg->command); @@ -2013,16 +2028,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) if (ret) goto err_put_device; + if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) { + svc->stratix10_svc_hwmon = + platform_device_alloc(SOCFPGA_HWMON, 0); + if (!svc->stratix10_svc_hwmon) { + dev_err(dev, "failed to allocate %s device\n", + SOCFPGA_HWMON); + } else { + svc->stratix10_svc_hwmon->dev.parent = dev; + + ret = platform_device_add(svc->stratix10_svc_hwmon); + if (ret) { + dev_err(dev, "failed to add %s device: %d\n", + SOCFPGA_HWMON, ret); + platform_device_put(svc->stratix10_svc_hwmon); + svc->stratix10_svc_hwmon = NULL; + } + } + } + ret = of_platform_default_populate(dev_of_node(dev), NULL, dev); if (ret) - goto err_unregister_rsu_dev; + goto err_unregister_clients; pr_info("Intel Service Layer Driver Initialized\n"); return 0; -err_unregister_rsu_dev: - platform_device_unregister(svc->stratix10_svc_rsu); +err_unregister_clients: + if (svc->stratix10_svc_hwmon) + platform_device_unregister(svc->stratix10_svc_hwmon); + if (svc->stratix10_svc_rsu) + platform_device_unregister(svc->stratix10_svc_rsu); goto err_free_fifos; err_put_device: platform_device_put(svc->stratix10_svc_rsu); @@ -2046,6 +2083,9 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); struct stratix10_svc *svc = ctrl->svc; + if (svc->stratix10_svc_hwmon) + platform_device_unregister(svc->stratix10_svc_hwmon); + stratix10_svc_async_exit(ctrl); of_platform_depopulate(ctrl->dev); diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h index 9116512169dc..18ac6fe96d9d 100644 --- a/include/linux/firmware/intel/stratix10-smc.h +++ b/include/linux/firmware/intel/stratix10-smc.h @@ -695,6 +695,44 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) #define INTEL_SIP_SMC_ASYNC_POLL \ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_POLL) +/** + * Request INTEL_SIP_SMC_ASYNC_HWMON_READTEMP + * Async call to request temperature + * + * Call register usage: + * a0 INTEL_SIP_SMC_ASYNC_HWMON_READTEMP + * a1 transaction job id + * a2 Temperature Channel + * a3-a17 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED + * or INTEL_SIP_SMC_STATUS_BUSY + * a1-a17 not used + */ +#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP 0xE8 +#define INTEL_SIP_SMC_ASYNC_HWMON_READTEMP \ + INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READTEMP) + +/** + * Request INTEL_SIP_SMC_ASYNC_HWMON_READVOLT + * Async call to request voltage + * + * Call register usage: + * a0 INTEL_SIP_SMC_ASYNC_HWMON_READVOLT + * a1 transaction job id + * a2 Voltage Channel + * a3-a17 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_STATUS_REJECTED + * or INTEL_SIP_SMC_STATUS_BUSY + * a1-a17 not used + */ +#define INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT 0xE9 +#define INTEL_SIP_SMC_ASYNC_HWMON_READVOLT \ + INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_HWMON_READVOLT) + /** * Request INTEL_SIP_SMC_ASYNC_RSU_GET_SPT * Async call to get RSU SPT from SDM. -- 2.43.7 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device 2026-07-20 3:25 ` [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device tze.yee.ng @ 2026-07-20 3:37 ` sashiko-bot 2026-07-20 4:12 ` Guenter Roeck 0 siblings, 1 reply; 9+ messages in thread From: sashiko-bot @ 2026-07-20 3:37 UTC (permalink / raw) To: tze.yee.ng; +Cc: linux-hwmon Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [Medium] Hardware monitoring functionality is registered as a platform device rather than an auxiliary device, violating hwmon subsystem layering rules. Pre-existing issues: - [High] Asynchronous service layer APIs and resources are destroyed before client devices are unregistered, leading to a Use-After-Free and NULL pointer dereference race condition. -- --- Patch [1]: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device --- commit 552947f984c2b5770daa9717c679362b5561cfa3 Author: Tze Yee Ng <tze.yee.ng@altera.com> firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device Add asynchronous Stratix 10 service layer support for hardware monitor temperature and voltage read commands in stratix10_svc_async_send() and stratix10_svc_async_prepare_response(). Register a socfpga-hwmon platform device from the service layer driver when hardware monitor support is enabled, similar to the RSU device. > diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c > index c24ca5823078f..fc38afed5b7fe 100644 > --- a/drivers/firmware/stratix10-svc.c > +++ b/drivers/firmware/stratix10-svc.c [ ... ] > @@ -2013,16 +2028,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) > if (ret) > goto err_put_device; > > + if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) { > + svc->stratix10_svc_hwmon = > + platform_device_alloc(SOCFPGA_HWMON, 0); > + if (!svc->stratix10_svc_hwmon) { > + dev_err(dev, "failed to allocate %s device\n", > + SOCFPGA_HWMON); > + } else { > + svc->stratix10_svc_hwmon->dev.parent = dev; > + > + ret = platform_device_add(svc->stratix10_svc_hwmon); [Severity: Medium] Should the hardware monitoring functionality be implemented as an auxiliary device driver instead of a platform device? The hardware monitoring subsystem guidelines state that when hardware monitoring is a secondary function of a chip, it should be implemented as an auxiliary device and reside in drivers/hwmon/. Does registering socfpga-hwmon directly as a platform device from this firmware service driver bypass the architectural layering rules for hwmon? [ ... ] > @@ -2046,6 +2083,9 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) > struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); > struct stratix10_svc *svc = ctrl->svc; > > + if (svc->stratix10_svc_hwmon) > + platform_device_unregister(svc->stratix10_svc_hwmon); > + > stratix10_svc_async_exit(ctrl); > > of_platform_depopulate(ctrl->dev); [Severity: High] This is a pre-existing issue, but can the destruction order in stratix10_svc_drv_remove() lead to a NULL pointer dereference or a use-after-free race condition? When stratix10_svc_async_exit() is called, it destroys the async ID pool and sets actrl->invoke_fn to NULL: drivers/firmware/stratix10-svc.c:stratix10_svc_async_exit() { ... ida_destroy(&actrl->async_id_pool); actrl->invoke_fn = NULL; ... } However, the RSU client device is unregistered after this destruction step via platform_device_unregister(svc->stratix10_svc_rsu). If a user interacts with a client device's sysfs attribute while the module is being unloaded, could the still-active client device invoke stratix10_svc_async_send()? If so, it would concurrently access freed asynchronous channels or attempt to execute actrl->invoke_fn(actrl, &args, &res) after it has been locklessly cleared, resulting in a kernel crash. Would it be safer to unregister all child and client devices before the resources they depend on are torn down? -- Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784516953.git.tze.yee.ng@altera.com?part=1 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device 2026-07-20 3:37 ` sashiko-bot @ 2026-07-20 4:12 ` Guenter Roeck 2026-07-20 11:04 ` NG, TZE YEE 0 siblings, 1 reply; 9+ messages in thread From: Guenter Roeck @ 2026-07-20 4:12 UTC (permalink / raw) To: sashiko-reviews, tze.yee.ng; +Cc: linux-hwmon On 7/19/26 20:37, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > > New issues: > - [Medium] Hardware monitoring functionality is registered as a platform device rather than an auxiliary device, violating hwmon subsystem layering rules. > > Pre-existing issues: > - [High] Asynchronous service layer APIs and resources are destroyed before client devices are unregistered, leading to a Use-After-Free and NULL pointer dereference race condition. > -- > > --- Patch [1]: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device --- > commit 552947f984c2b5770daa9717c679362b5561cfa3 > Author: Tze Yee Ng <tze.yee.ng@altera.com> > > firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device > > Add asynchronous Stratix 10 service layer support for hardware monitor > temperature and voltage read commands in stratix10_svc_async_send() and > stratix10_svc_async_prepare_response(). > > Register a socfpga-hwmon platform device from the service layer driver > when hardware monitor support is enabled, similar to the RSU device. > >> diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c >> index c24ca5823078f..fc38afed5b7fe 100644 >> --- a/drivers/firmware/stratix10-svc.c >> +++ b/drivers/firmware/stratix10-svc.c > [ ... ] >> @@ -2013,16 +2028,38 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) >> if (ret) >> goto err_put_device; >> >> + if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) { >> + svc->stratix10_svc_hwmon = >> + platform_device_alloc(SOCFPGA_HWMON, 0); >> + if (!svc->stratix10_svc_hwmon) { >> + dev_err(dev, "failed to allocate %s device\n", >> + SOCFPGA_HWMON); >> + } else { >> + svc->stratix10_svc_hwmon->dev.parent = dev; >> + >> + ret = platform_device_add(svc->stratix10_svc_hwmon); > > [Severity: Medium] > Should the hardware monitoring functionality be implemented as an auxiliary > device driver instead of a platform device? > > The hardware monitoring subsystem guidelines state that when hardware > monitoring is a secondary function of a chip, it should be implemented as an > auxiliary device and reside in drivers/hwmon/. > > Does registering socfpga-hwmon directly as a platform device from this > firmware service driver bypass the architectural layering rules for hwmon? > It should be implemented as auxiliary driver, but feel free to ignore since I only recently added this guidance to Sashiko. > [ ... ] >> @@ -2046,6 +2083,9 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev) >> struct stratix10_svc_controller *ctrl = platform_get_drvdata(pdev); >> struct stratix10_svc *svc = ctrl->svc; >> >> + if (svc->stratix10_svc_hwmon) >> + platform_device_unregister(svc->stratix10_svc_hwmon); >> + >> stratix10_svc_async_exit(ctrl); >> >> of_platform_depopulate(ctrl->dev); > > [Severity: High] > This is a pre-existing issue, but can the destruction order in > stratix10_svc_drv_remove() lead to a NULL pointer dereference or a > use-after-free race condition? > Pre-existing, so your call if you want to fix it or not. Thanks, Guenter > When stratix10_svc_async_exit() is called, it destroys the async ID pool > and sets actrl->invoke_fn to NULL: > > drivers/firmware/stratix10-svc.c:stratix10_svc_async_exit() { > ... > ida_destroy(&actrl->async_id_pool); > actrl->invoke_fn = NULL; > ... > } > > However, the RSU client device is unregistered after this destruction step > via platform_device_unregister(svc->stratix10_svc_rsu). > > If a user interacts with a client device's sysfs attribute while the module > is being unloaded, could the still-active client device invoke > stratix10_svc_async_send()? > > If so, it would concurrently access freed asynchronous channels or attempt > to execute actrl->invoke_fn(actrl, &args, &res) after it has been locklessly > cleared, resulting in a kernel crash. > > Would it be safer to unregister all child and client devices before the > resources they depend on are torn down? > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device 2026-07-20 4:12 ` Guenter Roeck @ 2026-07-20 11:04 ` NG, TZE YEE 0 siblings, 0 replies; 9+ messages in thread From: NG, TZE YEE @ 2026-07-20 11:04 UTC (permalink / raw) To: Guenter Roeck, sashiko-reviews@lists.linux.dev Cc: linux-hwmon@vger.kernel.org On 20/7/2026 12:12 pm, Guenter Roeck wrote: > On 7/19/26 20:37, sashiko-bot@kernel.org wrote: >> Thank you for your contribution! Sashiko AI review found 2 potential >> issue(s) to consider: >> >> New issues: >> - [Medium] Hardware monitoring functionality is registered as a >> platform device rather than an auxiliary device, violating hwmon >> subsystem layering rules. >> >> Pre-existing issues: >> - [High] Asynchronous service layer APIs and resources are destroyed >> before client devices are unregistered, leading to a Use-After-Free >> and NULL pointer dereference race condition. >> -- >> >> --- Patch [1]: [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON >> read commands and register socfpga-hwmon device --- >> commit 552947f984c2b5770daa9717c679362b5561cfa3 >> Author: Tze Yee Ng <tze.yee.ng@altera.com> >> >> firmware: stratix10-svc: add async HWMON read commands and register >> socfpga-hwmon device >> >> Add asynchronous Stratix 10 service layer support for hardware monitor >> temperature and voltage read commands in stratix10_svc_async_send() and >> stratix10_svc_async_prepare_response(). >> >> Register a socfpga-hwmon platform device from the service layer driver >> when hardware monitor support is enabled, similar to the RSU device. >> >>> diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/ >>> stratix10-svc.c >>> index c24ca5823078f..fc38afed5b7fe 100644 >>> --- a/drivers/firmware/stratix10-svc.c >>> +++ b/drivers/firmware/stratix10-svc.c >> [ ... ] >>> @@ -2013,16 +2028,38 @@ static int stratix10_svc_drv_probe(struct >>> platform_device *pdev) >>> if (ret) >>> goto err_put_device; >>> + if (IS_ENABLED(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON)) { >>> + svc->stratix10_svc_hwmon = >>> + platform_device_alloc(SOCFPGA_HWMON, 0); >>> + if (!svc->stratix10_svc_hwmon) { >>> + dev_err(dev, "failed to allocate %s device\n", >>> + SOCFPGA_HWMON); >>> + } else { >>> + svc->stratix10_svc_hwmon->dev.parent = dev; >>> + >>> + ret = platform_device_add(svc->stratix10_svc_hwmon); >> >> [Severity: Medium] >> Should the hardware monitoring functionality be implemented as an >> auxiliary >> device driver instead of a platform device? >> >> The hardware monitoring subsystem guidelines state that when hardware >> monitoring is a secondary function of a chip, it should be implemented >> as an >> auxiliary device and reside in drivers/hwmon/. >> >> Does registering socfpga-hwmon directly as a platform device from this >> firmware service driver bypass the architectural layering rules for >> hwmon? >> > > It should be implemented as auxiliary driver, but feel free to ignore since > I only recently added this guidance to Sashiko. > Thanks. I'll keep the platform device registration for this series. I’m happy to look at converting socfpga-hwmon to an auxiliary device in a follow-up if preferred. >> [ ... ] >>> @@ -2046,6 +2083,9 @@ static void stratix10_svc_drv_remove(struct >>> platform_device *pdev) >>> struct stratix10_svc_controller *ctrl = >>> platform_get_drvdata(pdev); >>> struct stratix10_svc *svc = ctrl->svc; >>> + if (svc->stratix10_svc_hwmon) >>> + platform_device_unregister(svc->stratix10_svc_hwmon); >>> + >>> stratix10_svc_async_exit(ctrl); >>> of_platform_depopulate(ctrl->dev); >> >> [Severity: High] >> This is a pre-existing issue, but can the destruction order in >> stratix10_svc_drv_remove() lead to a NULL pointer dereference or a >> use-after-free race condition? >> > Pre-existing, so your call if you want to fix it or not. > > Thanks, > Guenter > It is fixed in https://lore.kernel.org/all/6630a1568f162d9455e0580f7ccadc262db4718e.1784007275.git.adrian.ho.yin.ng@altera.com/ >> When stratix10_svc_async_exit() is called, it destroys the async ID pool >> and sets actrl->invoke_fn to NULL: >> >> drivers/firmware/stratix10-svc.c:stratix10_svc_async_exit() { >> ... >> ida_destroy(&actrl->async_id_pool); >> actrl->invoke_fn = NULL; >> ... >> } >> >> However, the RSU client device is unregistered after this destruction >> step >> via platform_device_unregister(svc->stratix10_svc_rsu). >> >> If a user interacts with a client device's sysfs attribute while the >> module >> is being unloaded, could the still-active client device invoke >> stratix10_svc_async_send()? >> >> If so, it would concurrently access freed asynchronous channels or >> attempt >> to execute actrl->invoke_fn(actrl, &args, &res) after it has been >> locklessly >> cleared, resulting in a kernel crash. >> >> Would it be safer to unregister all child and client devices before the >> resources they depend on are torn down? >> > ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver 2026-07-20 3:25 [PATCH v6 0/2] hwmon: add Altera SoC FPGA hardware monitoring support tze.yee.ng 2026-07-20 3:25 ` [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device tze.yee.ng @ 2026-07-20 3:25 ` tze.yee.ng 2026-07-20 3:36 ` sashiko-bot 1 sibling, 1 reply; 9+ messages in thread From: tze.yee.ng @ 2026-07-20 3:25 UTC (permalink / raw) To: Dinh Nguyen, linux-kernel, Guenter Roeck, Jonathan Corbet, Shuah Khan, linux-hwmon, linux-doc From: Tze Yee Ng <tze.yee.ng@altera.com> Add a hardware monitor driver for Altera SoC FPGA devices using the Stratix 10 service layer. Sensor channels are selected based on the service layer compatible string. Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com> Signed-off-by: Tze Yee Ng <tze.yee.ng@altera.com> --- Changes in v6: - No functional changes from v5 Changes in v5: - Poll async responses until HWMON_TIMEOUT (2 s) instead of a fixed 3-iteration retry loop (~3 ms), fixing premature timeouts observed on silicon - Add MODULE_ALIAS("platform:socfpga-hwmon") Changes in v4: - Register devm_add_action_or_reset() before devm_hwmon_device_register_with_info(); drop manual channel cleanup on hwmon registration failure - Remove unreferenced async completion and pre-poll wait_for_completion_io_timeout(); poll directly after async_send() with the existing retry loop Changes in v3: - Fix 16-bit signed Q8.8 temperature conversion (cast through s16) - Remove unused async callback; pass NULL to stratix10_svc_async_send() - Keep wait_for_completion_io_timeout() before polling with comment explaining the service layer never invokes the callback but firmware needs time to complete the transaction (RSU pattern) - Align async poll loop with RSU (retry on failure instead of aborting) - Use wait_for_completion_timeout() for synchronous reads - Handle -EINVAL and -EOPNOTSUPP when async client registration fails - Defer SVC channel/async cleanup via devm_add_action_or_reset(); drop .remove() Changes in v2: - Drop altr,stratix10-hwmon OF compatible and DT channel parsing - Select channels from hardcoded tables using parent SVC compatible (intel,stratix10-svc or intel,agilex-svc) - Rename driver from stratix10-hwmon to socfpga-hwmon - Rename Kconfig symbol to CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON - Add Agilex voltage and temperature channel tables - Convert SDM Q8.8 degrees Celsius to hwmon millidegrees - Convert SDM Q16 volts to hwmon millivolts - Use socfpga_hwmon as hwmon sysfs device name - Add last_err for synchronous SVC read error propagation - Update Documentation/hwmon and MAINTAINERS accordingly --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/socfpga-hwmon.rst | 34 ++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/socfpga-hwmon.c | 579 ++++++++++++++++++++++++++ 6 files changed, 633 insertions(+) create mode 100644 Documentation/hwmon/socfpga-hwmon.rst create mode 100644 drivers/hwmon/socfpga-hwmon.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 29130df44d12..3299417a24b8 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -254,6 +254,7 @@ Hardware Monitoring Kernel Drivers sparx5-temp spd5118 stpddc60 + socfpga-hwmon surface_fan sy7636a-hwmon tc654 diff --git a/Documentation/hwmon/socfpga-hwmon.rst b/Documentation/hwmon/socfpga-hwmon.rst new file mode 100644 index 000000000000..e5da42556a62 --- /dev/null +++ b/Documentation/hwmon/socfpga-hwmon.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver socfpga-hwmon +============================= + +Supported chips: + + * Altera Stratix 10 SoC FPGA + * Altera Agilex SoC FPGA + +Authors: + - Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com> + - Tze Yee Ng <tze.yee.ng@altera.com> + +Description +----------- + +This driver supports hardware monitoring for Altera SoC +FPGA devices through the Secure Device Manager and Stratix 10 service layer. + +The following sensor types are supported: + + * temperature + * voltage + +Usage Notes +----------- + +The stratix10-svc driver registers a socfpga-hwmon platform device when +hardware monitor support is enabled. Sensor channels are selected in the +driver based on the service layer compatible string: + + * intel,stratix10-svc + * intel,agilex-svc diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..ab6cb215a02f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -965,6 +965,14 @@ L: linux-gpio@vger.kernel.org S: Maintained F: drivers/gpio/gpio-altera.c +ALTERA SoC FPGA HWMON DRIVER +M: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com> +M: Tze Yee Ng <tze.yee.ng@altera.com> +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/socfpga-hwmon.rst +F: drivers/hwmon/socfpga-hwmon.c + ALTERA TRIPLE SPEED ETHERNET DRIVER M: Boon Khai Ng <boon.khai.ng@altera.com> L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 2bfbcc033d59..8e768661fec2 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2160,6 +2160,16 @@ config SENSORS_SMSC47M192 This driver can also be built as a module. If so, the module will be called smsc47m192. +config SENSORS_ALTERA_SOCFPGA_HWMON + tristate "Altera SoC FPGA hardware monitoring features" + depends on INTEL_STRATIX10_SERVICE + help + If you say yes here you get support for the temperature and + voltage sensors of Altera SoC FPGA devices. + + This driver can also be built as a module. If so, the module + will be called socfpga-hwmon. + config SENSORS_SMSC47B397 tristate "SMSC LPC47B397-NC" depends on HAS_IOPORT diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 63effc0ab8d1..aeedee80e1f0 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -221,6 +221,7 @@ obj-$(CONFIG_SENSORS_SMPRO) += smpro-hwmon.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o +obj-$(CONFIG_SENSORS_ALTERA_SOCFPGA_HWMON) += socfpga-hwmon.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c new file mode 100644 index 000000000000..72418201b65d --- /dev/null +++ b/drivers/hwmon/socfpga-hwmon.c @@ -0,0 +1,579 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Altera SoC FPGA hardware monitoring driver + * + * Copyright (c) 2026 Altera Corporation + * + * Authors: + * Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com> + * Tze Yee Ng <tze.yee.ng@altera.com> + */ + +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/firmware/intel/stratix10-svc-client.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define HWMON_TIMEOUT msecs_to_jiffies(SVC_HWMON_REQUEST_TIMEOUT_MS) +#define HWMON_RETRY_SLEEP_MS 1U +#define HWMON_ASYNC_MSG_RETRY 3U +#define SOCFPGA_HWMON_MAXSENSORS 16 +#define SOCFPGA_HWMON_CHANNEL_MASK GENMASK(15, 0) +#define SOCFPGA_HWMON_PAGE_SHIFT 16 +#define SOCFPGA_HWMON_CHAN(page, channel) \ + (((page) << SOCFPGA_HWMON_PAGE_SHIFT) | \ + ((channel) & SOCFPGA_HWMON_CHANNEL_MASK)) +#define SOCFPGA_HWMON_ATTR_VISIBLE 0444 +/* Temperature from SDM is signed Q8.8 degrees Celsius (8 fractional bits). */ +#define SOCFPGA_HWMON_TEMP_FRAC_BITS 8 +#define SOCFPGA_HWMON_TEMP_FRAC_DIV BIT(SOCFPGA_HWMON_TEMP_FRAC_BITS) +#define SOCFPGA_HWMON_TEMP_MDEG_SCALE 1000 +/* Voltage from SDM is unsigned Q16 volts (16 fractional bits). */ +#define SOCFPGA_HWMON_VOLT_FRAC_BITS 16 +#define SOCFPGA_HWMON_VOLT_FRAC_DIV BIT(SOCFPGA_HWMON_VOLT_FRAC_BITS) +#define SOCFPGA_HWMON_VOLT_MV_SCALE 1000 + +#define ETEMP_INACTIVE 0x80000000U +#define ETEMP_TOO_OLD 0x80000001U +#define ETEMP_NOT_PRESENT 0x80000002U +#define ETEMP_TIMEOUT 0x80000003U +#define ETEMP_CORRUPT 0x80000004U +#define ETEMP_BUSY 0x80000005U +#define ETEMP_NOT_INITIALIZED 0x800000FFU + +struct socfpga_hwmon_channel { + u32 reg; + const char *label; +}; + +struct socfpga_hwmon_board_data { + const struct socfpga_hwmon_channel *temp; + unsigned int num_temp; + const struct socfpga_hwmon_channel *volt; + unsigned int num_volt; +}; + +struct socfpga_hwmon_priv { + struct stratix10_svc_chan *chan; + struct stratix10_svc_client client; + struct completion completion; + struct mutex lock; /* protect SVC calls */ + bool async; + int last_err; /* sync-mode SVC result; 0 on success */ + u32 temperature; + u32 voltage; + int temperature_channels; + int voltage_channels; + const char *temp_chan_names[SOCFPGA_HWMON_MAXSENSORS]; + const char *volt_chan_names[SOCFPGA_HWMON_MAXSENSORS]; + u32 temp_chan[SOCFPGA_HWMON_MAXSENSORS]; + u32 volt_chan[SOCFPGA_HWMON_MAXSENSORS]; +}; + +static umode_t socfpga_hwmon_is_visible(const void *dev, + enum hwmon_sensor_types type, + u32 attr, int chan) +{ + const struct socfpga_hwmon_priv *priv = dev; + + switch (type) { + case hwmon_temp: + if (chan < priv->temperature_channels) + return SOCFPGA_HWMON_ATTR_VISIBLE; + return 0; + case hwmon_in: + if (chan < priv->voltage_channels) + return SOCFPGA_HWMON_ATTR_VISIBLE; + return 0; + default: + return 0; + } +} + +static void socfpga_hwmon_readtemp_cb(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct socfpga_hwmon_priv *priv = client->priv; + + priv->last_err = -EIO; + if (data->status == BIT(SVC_STATUS_OK)) { + priv->last_err = 0; + priv->temperature = (u32)*(unsigned long *)data->kaddr1; + } else if (data->kaddr1) { + dev_err(client->dev, "%s failed with status 0x%x, value 0x%lx\n", + __func__, data->status, + *(unsigned long *)data->kaddr1); + } else { + dev_err(client->dev, "%s failed with status 0x%x\n", + __func__, data->status); + } + + complete(&priv->completion); +} + +static void socfpga_hwmon_readvolt_cb(struct stratix10_svc_client *client, + struct stratix10_svc_cb_data *data) +{ + struct socfpga_hwmon_priv *priv = client->priv; + + priv->last_err = -EIO; + if (data->status == BIT(SVC_STATUS_OK)) { + priv->last_err = 0; + priv->voltage = (u32)*(unsigned long *)data->kaddr1; + } else if (data->kaddr1) { + dev_err(client->dev, "%s failed with status 0x%x, value 0x%lx\n", + __func__, data->status, + *(unsigned long *)data->kaddr1); + } else { + dev_err(client->dev, "%s failed with status 0x%x\n", + __func__, data->status); + } + + complete(&priv->completion); +} + +static int socfpga_hwmon_parse_temp(long *val, u32 temperature) +{ + switch (temperature) { + case ETEMP_INACTIVE: + case ETEMP_NOT_PRESENT: + case ETEMP_CORRUPT: + case ETEMP_NOT_INITIALIZED: + return -EOPNOTSUPP; + case ETEMP_TIMEOUT: + case ETEMP_BUSY: + case ETEMP_TOO_OLD: + return -EAGAIN; + default: + /* SDM returns a 16-bit signed Q8.8 value in the low 16 bits. */ + *val = (long)(s16)(temperature & SOCFPGA_HWMON_CHANNEL_MASK) * + SOCFPGA_HWMON_TEMP_MDEG_SCALE / SOCFPGA_HWMON_TEMP_FRAC_DIV; + return 0; + } +} + +static int socfpga_hwmon_encode_temp_arg(u32 reg, u64 *arg) +{ + u32 page = (reg >> SOCFPGA_HWMON_PAGE_SHIFT) & SOCFPGA_HWMON_CHANNEL_MASK; + u32 channel = reg & SOCFPGA_HWMON_CHANNEL_MASK; + + if (channel >= SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + *arg = (1ULL << channel) | ((u64)page << SOCFPGA_HWMON_PAGE_SHIFT); + return 0; +} + +static int socfpga_hwmon_encode_volt_arg(u32 reg, u64 *arg) +{ + u32 channel = reg & SOCFPGA_HWMON_CHANNEL_MASK; + + if (channel >= SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + *arg = 1ULL << channel; + return 0; +} + +static int socfpga_hwmon_async_read(struct device *dev, + enum hwmon_sensor_types type, + struct stratix10_svc_client_msg *msg) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + struct stratix10_svc_cb_data data = {}; + unsigned long deadline = jiffies + HWMON_TIMEOUT; + void *handle = NULL; + int status, index, ret; + + for (index = 0; index < HWMON_ASYNC_MSG_RETRY; index++) { + status = stratix10_svc_async_send(priv->chan, msg, &handle, + NULL, NULL); + if (status == 0) + break; + dev_warn(dev, "Failed to send async message: %d\n", status); + msleep(HWMON_RETRY_SLEEP_MS); + } + + if (status && !handle) { + dev_err(dev, "Failed to send async message after %u retries: %d\n", + HWMON_ASYNC_MSG_RETRY, status); + return status; + } + + ret = -ETIMEDOUT; + while (!time_after(jiffies, deadline)) { + status = stratix10_svc_async_poll(priv->chan, handle, &data); + if (status == -EAGAIN) { + dev_dbg(dev, "Async message is still in progress\n"); + } else if (status < 0) { + dev_alert(dev, "Failed to poll async message: %d\n", status); + ret = -ETIMEDOUT; + } else if (status == 0) { + ret = 0; + break; + } + msleep(HWMON_RETRY_SLEEP_MS); + } + + if (ret) { + dev_err(dev, "Failed to get async response\n"); + goto done; + } + + if (data.status) { + dev_err(dev, "%s returned 0x%x from SDM\n", __func__, + data.status); + ret = -EFAULT; + goto done; + } + + if (type == hwmon_temp) + priv->temperature = (u32)*(unsigned long *)data.kaddr1; + else + priv->voltage = (u32)*(unsigned long *)data.kaddr1; + + ret = 0; + +done: + stratix10_svc_async_done(priv->chan, handle); + return ret; +} + +static int socfpga_hwmon_sync_read(struct device *dev, + enum hwmon_sensor_types type, + struct stratix10_svc_client_msg *msg) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + int ret; + + reinit_completion(&priv->completion); + + if (type == hwmon_temp) + priv->client.receive_cb = socfpga_hwmon_readtemp_cb; + else + priv->client.receive_cb = socfpga_hwmon_readvolt_cb; + + ret = stratix10_svc_send(priv->chan, msg); + if (ret < 0) + goto status_done; + + ret = wait_for_completion_timeout(&priv->completion, HWMON_TIMEOUT); + if (!ret) { + dev_err(priv->client.dev, "timeout waiting for SMC call\n"); + ret = -ETIMEDOUT; + goto status_done; + } + + ret = priv->last_err; + +status_done: + stratix10_svc_done(priv->chan); + return ret; +} + +static int socfpga_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int chan, long *val) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + struct stratix10_svc_client_msg msg = {0}; + int ret; + + if (chan >= SOCFPGA_HWMON_MAXSENSORS) + return -EOPNOTSUPP; + + switch (type) { + case hwmon_temp: + ret = socfpga_hwmon_encode_temp_arg(priv->temp_chan[chan], + &msg.arg[0]); + if (ret) + return ret; + msg.command = COMMAND_HWMON_READTEMP; + break; + case hwmon_in: + ret = socfpga_hwmon_encode_volt_arg(priv->volt_chan[chan], + &msg.arg[0]); + if (ret) + return ret; + msg.command = COMMAND_HWMON_READVOLT; + break; + default: + return -EOPNOTSUPP; + } + + guard(mutex)(&priv->lock); + if (priv->async) + ret = socfpga_hwmon_async_read(dev, type, &msg); + else + ret = socfpga_hwmon_sync_read(dev, type, &msg); + if (ret) + return ret; + + if (type == hwmon_temp) + ret = socfpga_hwmon_parse_temp(val, priv->temperature); + else + /* SDM returns Q16 volts; convert to hwmon millivolts. */ + *val = (long)priv->voltage * SOCFPGA_HWMON_VOLT_MV_SCALE / + SOCFPGA_HWMON_VOLT_FRAC_DIV; + return ret; +} + +static int socfpga_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int chan, const char **str) +{ + struct socfpga_hwmon_priv *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_in: + *str = priv->volt_chan_names[chan]; + return 0; + case hwmon_temp: + *str = priv->temp_chan_names[chan]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops socfpga_hwmon_ops = { + .is_visible = socfpga_hwmon_is_visible, + .read = socfpga_hwmon_read, + .read_string = socfpga_hwmon_read_string, +}; + +static const struct hwmon_channel_info *socfpga_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static const struct hwmon_chip_info socfpga_hwmon_chip_info = { + .ops = &socfpga_hwmon_ops, + .info = socfpga_hwmon_info, +}; + +static const struct socfpga_hwmon_channel s10_hwmon_volt_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 2), "0.8V VCC" }, + { SOCFPGA_HWMON_CHAN(0, 3), "1.8V VCCIO_SDM" }, + { SOCFPGA_HWMON_CHAN(0, 6), "0.9V VCCERAM" }, +}; + +static const struct socfpga_hwmon_channel s10_hwmon_temp_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" }, +}; + +static const struct socfpga_hwmon_board_data s10_hwmon_board = { + .temp = s10_hwmon_temp_channels, + .num_temp = ARRAY_SIZE(s10_hwmon_temp_channels), + .volt = s10_hwmon_volt_channels, + .num_volt = ARRAY_SIZE(s10_hwmon_volt_channels), +}; + +static const struct socfpga_hwmon_channel agilex_hwmon_volt_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 2), "0.8V VCC" }, + { SOCFPGA_HWMON_CHAN(0, 3), "1.8V VCCIO_SDM" }, + { SOCFPGA_HWMON_CHAN(0, 4), "1.8V VCCPT" }, + { SOCFPGA_HWMON_CHAN(0, 5), "1.2V VCCCRCORE" }, + { SOCFPGA_HWMON_CHAN(0, 6), "0.9V VCCH" }, + { SOCFPGA_HWMON_CHAN(0, 7), "0.8V VCCL" }, +}; + +static const struct socfpga_hwmon_channel agilex_hwmon_temp_channels[] = { + { SOCFPGA_HWMON_CHAN(0, 0), "Main Die SDM" }, + { SOCFPGA_HWMON_CHAN(1, 0), "Main Die corner bottom left max" }, + { SOCFPGA_HWMON_CHAN(2, 0), "Main Die corner top left max" }, + { SOCFPGA_HWMON_CHAN(3, 0), "Main Die corner bottom right max" }, + { SOCFPGA_HWMON_CHAN(4, 0), "Main Die corner top right max" }, +}; + +static const struct socfpga_hwmon_board_data agilex_hwmon_board = { + .temp = agilex_hwmon_temp_channels, + .num_temp = ARRAY_SIZE(agilex_hwmon_temp_channels), + .volt = agilex_hwmon_volt_channels, + .num_volt = ARRAY_SIZE(agilex_hwmon_volt_channels), +}; + +static const struct socfpga_hwmon_board_data * +socfpga_hwmon_get_board(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!np) + return NULL; + + if (of_device_is_compatible(np, "intel,stratix10-svc")) + return &s10_hwmon_board; + if (of_device_is_compatible(np, "intel,agilex-svc")) + return &agilex_hwmon_board; + + return NULL; +} + +static int socfpga_hwmon_init_channels(struct device *dev, + const struct socfpga_hwmon_board_data *board, + struct socfpga_hwmon_priv *priv) +{ + unsigned int i; + + if (board->num_temp > SOCFPGA_HWMON_MAXSENSORS || + board->num_volt > SOCFPGA_HWMON_MAXSENSORS) + return -EINVAL; + + for (i = 0; i < board->num_temp; i++) { + priv->temp_chan_names[i] = board->temp[i].label; + priv->temp_chan[i] = board->temp[i].reg; + } + priv->temperature_channels = board->num_temp; + + for (i = 0; i < board->num_volt; i++) { + priv->volt_chan_names[i] = board->volt[i].label; + priv->volt_chan[i] = board->volt[i].reg; + } + priv->voltage_channels = board->num_volt; + + return 0; +} + +static void socfpga_hwmon_release_svc(void *data) +{ + struct socfpga_hwmon_priv *priv = data; + + if (priv->async) + stratix10_svc_remove_async_client(priv->chan); + stratix10_svc_free_channel(priv->chan); +} + +static int socfpga_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *parent = dev->parent; + const struct socfpga_hwmon_board_data *board; + struct socfpga_hwmon_priv *priv; + struct device *hwmon_dev; + int ret; + + if (!parent || !parent->of_node) { + dev_err(dev, "missing parent device node\n"); + return -ENODEV; + } + + board = socfpga_hwmon_get_board(parent); + if (!board) { + dev_err(dev, "unsupported service layer compatible\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client.dev = dev; + priv->client.priv = priv; + init_completion(&priv->completion); + mutex_init(&priv->lock); + + ret = socfpga_hwmon_init_channels(dev, board, priv); + if (ret) + return ret; + + priv->chan = stratix10_svc_request_channel_byname(&priv->client, + SVC_CLIENT_HWMON); + if (IS_ERR(priv->chan)) { + ret = PTR_ERR(priv->chan); + if (ret == -EPROBE_DEFER) + dev_dbg(dev, "service channel %s not ready, deferring probe\n", + SVC_CLIENT_HWMON); + else + dev_err(dev, "couldn't get service channel %s: %d\n", + SVC_CLIENT_HWMON, ret); + return ret; + } + + ret = stratix10_svc_add_async_client(priv->chan, false); + switch (ret) { + case 0: + priv->async = true; + break; + case -EINVAL: + case -EOPNOTSUPP: + /* + * stratix10_svc_add_async_client() returns -EINVAL when the + * async controller is not initialized; fall back to sync mode. + */ + dev_dbg(dev, "async operations not supported, using sync mode\n"); + priv->async = false; + break; + default: + dev_err(dev, "failed to add async client: %d\n", ret); + stratix10_svc_free_channel(priv->chan); + return ret; + } + + ret = devm_add_action_or_reset(dev, socfpga_hwmon_release_svc, priv); + if (ret) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, "socfpga_hwmon", + priv, + &socfpga_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + platform_set_drvdata(pdev, priv); + return 0; +} + +static struct platform_driver socfpga_hwmon_driver = { + .probe = socfpga_hwmon_probe, + .driver = { + .name = "socfpga-hwmon", + }, +}; +module_platform_driver(socfpga_hwmon_driver); + +MODULE_AUTHOR("Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>"); +MODULE_AUTHOR("Tze Yee Ng <tze.yee.ng@altera.com>"); +MODULE_DESCRIPTION("Altera SoC FPGA hardware monitoring driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:socfpga-hwmon"); -- 2.43.7 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver 2026-07-20 3:25 ` [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver tze.yee.ng @ 2026-07-20 3:36 ` sashiko-bot 2026-07-20 4:21 ` Guenter Roeck 0 siblings, 1 reply; 9+ messages in thread From: sashiko-bot @ 2026-07-20 3:36 UTC (permalink / raw) To: tze.yee.ng; +Cc: linux-hwmon Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Missing break in async poll loop causes severe kernel log spam and unnecessary blocking on hard errors. - [High] Synchronous read timeouts leave stale requests in the channel FIFO, causing all subsequent reads to process stale data and return mismatched sensor values. -- --- Patch [2]: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver --- commit ab6c0c0f95d3890d759e04d9d9aecd151d6406fd Author: Tze Yee Ng <tze.yee.ng@altera.com> Subject: hwmon: add Altera SoC FPGA hardware monitoring driver This commit adds a hardware monitoring driver for Altera SoC FPGA devices using the Stratix 10 service layer. > diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c > new file mode 100644 > index 0000000000000..72418201b65d6 > --- /dev/null > +++ b/drivers/hwmon/socfpga-hwmon.c [ ... ] > +static int socfpga_hwmon_async_read(struct device *dev, > + enum hwmon_sensor_types type, > + struct stratix10_svc_client_msg *msg) > +{ [ ... ] > + ret = -ETIMEDOUT; > + while (!time_after(jiffies, deadline)) { > + status = stratix10_svc_async_poll(priv->chan, handle, &data); > + if (status == -EAGAIN) { > + dev_dbg(dev, "Async message is still in progress\n"); > + } else if (status < 0) { > + dev_alert(dev, "Failed to poll async message: %d\n", status); > + ret = -ETIMEDOUT; [Severity: High] Does this loop need a break statement here when status < 0? If stratix10_svc_async_poll() returns a hard error, the code sets ret = -ETIMEDOUT but continues to loop for up to 2 seconds. This could flood the kernel log with dev_alert() messages on every 1ms retry. > + } else if (status == 0) { > + ret = 0; > + break; > + } > + msleep(HWMON_RETRY_SLEEP_MS); > + } [ ... ] > +static int socfpga_hwmon_sync_read(struct device *dev, > + enum hwmon_sensor_types type, > + struct stratix10_svc_client_msg *msg) > +{ [ ... ] > + ret = stratix10_svc_send(priv->chan, msg); > + if (ret < 0) > + goto status_done; > + > + ret = wait_for_completion_timeout(&priv->completion, HWMON_TIMEOUT); > + if (!ret) { > + dev_err(priv->client.dev, "timeout waiting for SMC call\n"); > + ret = -ETIMEDOUT; > + goto status_done; [Severity: High] If a timeout occurs here, could the unfulfilled request remain in the SVC channel's FIFO? When the timeout hits, this path calls stratix10_svc_done() which stops the worker thread, but it doesn't appear to remove the request from chan->svc_fifo. Would the next sensor read enqueue a new request and receive the stale data from this timed-out request, permanently desynchronizing all subsequent sensor readings? > + } > + > + ret = priv->last_err; > + > +status_done: > + stratix10_svc_done(priv->chan); > + return ret; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784516953.git.tze.yee.ng@altera.com?part=2 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver 2026-07-20 3:36 ` sashiko-bot @ 2026-07-20 4:21 ` Guenter Roeck 2026-07-20 12:44 ` NG, TZE YEE 0 siblings, 1 reply; 9+ messages in thread From: Guenter Roeck @ 2026-07-20 4:21 UTC (permalink / raw) To: sashiko-reviews, tze.yee.ng; +Cc: linux-hwmon On 7/19/26 20:36, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > - [High] Missing break in async poll loop causes severe kernel log spam and unnecessary blocking on hard errors. > - [High] Synchronous read timeouts leave stale requests in the channel FIFO, causing all subsequent reads to process stale data and return mismatched sensor values. > -- > > --- Patch [2]: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver --- > commit ab6c0c0f95d3890d759e04d9d9aecd151d6406fd > Author: Tze Yee Ng <tze.yee.ng@altera.com> > Subject: hwmon: add Altera SoC FPGA hardware monitoring driver > > This commit adds a hardware monitoring driver for Altera SoC FPGA devices using > the Stratix 10 service layer. > >> diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga-hwmon.c >> new file mode 100644 >> index 0000000000000..72418201b65d6 >> --- /dev/null >> +++ b/drivers/hwmon/socfpga-hwmon.c > [ ... ] >> +static int socfpga_hwmon_async_read(struct device *dev, >> + enum hwmon_sensor_types type, >> + struct stratix10_svc_client_msg *msg) >> +{ > [ ... ] >> + ret = -ETIMEDOUT; >> + while (!time_after(jiffies, deadline)) { >> + status = stratix10_svc_async_poll(priv->chan, handle, &data); >> + if (status == -EAGAIN) { >> + dev_dbg(dev, "Async message is still in progress\n"); This also creates a lot of noise if debugging is active. I don't know how long the command usually takes, but it does seem to me that this debug message isn't really helpful. >> + } else if (status < 0) { >> + dev_alert(dev, "Failed to poll async message: %d\n", status); >> + ret = -ETIMEDOUT; > > [Severity: High] > Does this loop need a break statement here when status < 0? > > If stratix10_svc_async_poll() returns a hard error, the code sets > ret = -ETIMEDOUT but continues to loop for up to 2 seconds. This could > flood the kernel log with dev_alert() messages on every 1ms retry. > Sashiko has a good point, especially since the function returns -EINVAL which, if seen, seems to be _real_ hard error and retrying seems pointless. Also, why replace -EINVAL with -ETIMEDOUT ? That would need an explanation. On top of that, "ret = -ETIMEDOUT;" is pointless here since ret is already set to -ETIMEDOUT. Oh, and the called code already displays an error message unless the parameters are bad. >> + } else if (status == 0) { >> + ret = 0; >> + break; >> + } >> + msleep(HWMON_RETRY_SLEEP_MS); I would suggest to use usleep_range(). >> + } > [ ... ] >> +static int socfpga_hwmon_sync_read(struct device *dev, >> + enum hwmon_sensor_types type, >> + struct stratix10_svc_client_msg *msg) >> +{ > [ ... ] >> + ret = stratix10_svc_send(priv->chan, msg); >> + if (ret < 0) >> + goto status_done; >> + >> + ret = wait_for_completion_timeout(&priv->completion, HWMON_TIMEOUT); >> + if (!ret) { >> + dev_err(priv->client.dev, "timeout waiting for SMC call\n"); >> + ret = -ETIMEDOUT; >> + goto status_done; > > [Severity: High] > If a timeout occurs here, could the unfulfilled request remain in the > SVC channel's FIFO? > > When the timeout hits, this path calls stratix10_svc_done() which stops the > worker thread, but it doesn't appear to remove the request from chan->svc_fifo. > > Would the next sensor read enqueue a new request and receive the stale data > from this timed-out request, permanently desynchronizing all subsequent > sensor readings? > Please either address or explain why it is not a problem. Thanks, Guenter >> + } >> + >> + ret = priv->last_err; >> + >> +status_done: >> + stratix10_svc_done(priv->chan); >> + return ret; >> +} > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver 2026-07-20 4:21 ` Guenter Roeck @ 2026-07-20 12:44 ` NG, TZE YEE 0 siblings, 0 replies; 9+ messages in thread From: NG, TZE YEE @ 2026-07-20 12:44 UTC (permalink / raw) To: Guenter Roeck, sashiko-reviews@lists.linux.dev Cc: linux-hwmon@vger.kernel.org On 20/7/2026 12:21 pm, Guenter Roeck wrote: > On 7/19/26 20:36, sashiko-bot@kernel.org wrote: >> Thank you for your contribution! Sashiko AI review found 2 potential >> issue(s) to consider: >> - [High] Missing break in async poll loop causes severe kernel log >> spam and unnecessary blocking on hard errors. >> - [High] Synchronous read timeouts leave stale requests in the channel >> FIFO, causing all subsequent reads to process stale data and return >> mismatched sensor values. >> -- >> >> --- Patch [2]: [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware >> monitoring driver --- >> commit ab6c0c0f95d3890d759e04d9d9aecd151d6406fd >> Author: Tze Yee Ng <tze.yee.ng@altera.com> >> Subject: hwmon: add Altera SoC FPGA hardware monitoring driver >> >> This commit adds a hardware monitoring driver for Altera SoC FPGA >> devices using >> the Stratix 10 service layer. >> >>> diff --git a/drivers/hwmon/socfpga-hwmon.c b/drivers/hwmon/socfpga- >>> hwmon.c >>> new file mode 100644 >>> index 0000000000000..72418201b65d6 >>> --- /dev/null >>> +++ b/drivers/hwmon/socfpga-hwmon.c >> [ ... ] >>> +static int socfpga_hwmon_async_read(struct device *dev, >>> + enum hwmon_sensor_types type, >>> + struct stratix10_svc_client_msg *msg) >>> +{ >> [ ... ] >>> + ret = -ETIMEDOUT; >>> + while (!time_after(jiffies, deadline)) { >>> + status = stratix10_svc_async_poll(priv->chan, handle, &data); >>> + if (status == -EAGAIN) { >>> + dev_dbg(dev, "Async message is still in progress\n"); > > This also creates a lot of noise if debugging is active. I don't know > how long the command usually takes, but it does seem to me that this debug > message isn't really helpful. > >>> + } else if (status < 0) { >>> + dev_alert(dev, "Failed to poll async message: %d\n", >>> status); >>> + ret = -ETIMEDOUT; >> >> [Severity: High] >> Does this loop need a break statement here when status < 0? >> >> If stratix10_svc_async_poll() returns a hard error, the code sets >> ret = -ETIMEDOUT but continues to loop for up to 2 seconds. This could >> flood the kernel log with dev_alert() messages on every 1ms retry. >> > > Sashiko has a good point, especially since the function returns > -EINVAL which, if seen, seems to be _real_ hard error and retrying > seems pointless. Also, why replace -EINVAL with -ETIMEDOUT ? > That would need an explanation. > > On top of that, "ret = -ETIMEDOUT;" is pointless here since ret is > already set to -ETIMEDOUT. > > Oh, and the called code already displays an error message unless > the parameters are bad. > Agreed. In v7 I'll break out of the poll loop on hard errors, return the real poll error instead of -ETIMEDOUT, drop the redundant alert (SVC already logs) and remove the -EAGAIN debug print. >>> + } else if (status == 0) { >>> + ret = 0; >>> + break; >>> + } >>> + msleep(HWMON_RETRY_SLEEP_MS); > > I would suggest to use usleep_range(). > Agreed. In v7 I'll switch the short waits to usleep_range(). >>> + } >> [ ... ] >>> +static int socfpga_hwmon_sync_read(struct device *dev, >>> + enum hwmon_sensor_types type, >>> + struct stratix10_svc_client_msg *msg) >>> +{ >> [ ... ] >>> + ret = stratix10_svc_send(priv->chan, msg); >>> + if (ret < 0) >>> + goto status_done; >>> + >>> + ret = wait_for_completion_timeout(&priv->completion, >>> HWMON_TIMEOUT); >>> + if (!ret) { >>> + dev_err(priv->client.dev, "timeout waiting for SMC call\n"); >>> + ret = -ETIMEDOUT; >>> + goto status_done; >> >> [Severity: High] >> If a timeout occurs here, could the unfulfilled request remain in the >> SVC channel's FIFO? >> >> When the timeout hits, this path calls stratix10_svc_done() which >> stops the >> worker thread, but it doesn't appear to remove the request from chan- >> >svc_fifo. >> >> Would the next sensor read enqueue a new request and receive the stale >> data >> from this timed-out request, permanently desynchronizing all subsequent >> sensor readings? >> > > Please either address or explain why it is not a problem. > > Thanks, > Guenter > Yes, that race is real if the worker exits via kthread_should_stop() before dequeuing. stratix10_svc_done() stops the thread but does not flush svc_fifo. For this driver I will invalidate an in-flight transfer generation after a timeout so a late/stale callback cannot complete the next read. A proper FIFO drain in stratix10_svc_done() would still be useful as a follow-up in the service layer. >>> + } >>> + >>> + ret = priv->last_err; >>> + >>> +status_done: >>> + stratix10_svc_done(priv->chan); >>> + return ret; >>> +} >> > ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-20 12:44 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-20 3:25 [PATCH v6 0/2] hwmon: add Altera SoC FPGA hardware monitoring support tze.yee.ng 2026-07-20 3:25 ` [PATCH v6 1/2] firmware: stratix10-svc: add async HWMON read commands and register socfpga-hwmon device tze.yee.ng 2026-07-20 3:37 ` sashiko-bot 2026-07-20 4:12 ` Guenter Roeck 2026-07-20 11:04 ` NG, TZE YEE 2026-07-20 3:25 ` [PATCH v6 2/2] hwmon: add Altera SoC FPGA hardware monitoring driver tze.yee.ng 2026-07-20 3:36 ` sashiko-bot 2026-07-20 4:21 ` Guenter Roeck 2026-07-20 12:44 ` NG, TZE YEE
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox