Node counter is a timer presents on many Loongson-3 series CPUs.
It is maintained on every node in system. To avoid synchronisation
complexity we only access the copy from first node in system.
It also has many ways to be accessed, on latest Loongson-3 CPU with
IOCSR instruction support it should be accessed with a IOCSR request,
while on earlier Loongson-3 CPUs it is attached to a 32 bits MMIO bus.
For QEMU's Loongson-3 virt system it is mapped to a 64 bit MMIO location.
On some rare case the counter is disabled by firmware or not present
on chip, so we need to perform a lightweight test to ensure it is
running before actually use it.
Signed-off-by: Jiaxun Yang <[email protected]>
---
Changes in v2:
- Fix build failure when it's not enabled.
- Link to v1: https://lore.kernel.org/r/[email protected]
---
MAINTAINERS | 1 +
arch/mips/include/asm/mach-loongson64/loongson.h | 3 +
arch/mips/loongson64/time.c | 3 +
drivers/clocksource/Kconfig | 8 ++
drivers/clocksource/loongson-nodecnt.c | 112 +++++++++++++++++++++++
5 files changed, 127 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c675fc296b19..b36bff5b9803 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15117,6 +15117,7 @@ L: [email protected]
S: Maintained
F: arch/mips/include/asm/mach-loongson64/
F: arch/mips/loongson64/
+F: drivers/clocksource/loongson-nodecnt.c
F: drivers/irqchip/irq-loongson*
F: drivers/platform/mips/cpu_hwmon.c
diff --git a/arch/mips/include/asm/mach-loongson64/loongson.h b/arch/mips/include/asm/mach-loongson64/loongson.h
index f7c3ab6d724e..d07f4be06595 100644
--- a/arch/mips/include/asm/mach-loongson64/loongson.h
+++ b/arch/mips/include/asm/mach-loongson64/loongson.h
@@ -56,6 +56,9 @@ extern void *loongson_fdt_blob;
extern void mach_irq_dispatch(unsigned int pending);
extern int mach_i8259_irq(void);
+/* Time functions */
+extern int __init nodecnt_clocksource_init(void);
+
/* We need this in some places... */
#define delay() ({ \
int x; \
diff --git a/arch/mips/loongson64/time.c b/arch/mips/loongson64/time.c
index f6d2c1e30570..6e0603a6d713 100644
--- a/arch/mips/loongson64/time.c
+++ b/arch/mips/loongson64/time.c
@@ -44,4 +44,7 @@ void __init plat_time_init(void)
#ifdef CONFIG_RS780_HPET
setup_hpet_timer();
#endif
+#ifdef CONFIG_LOONGSON_NODECNT
+ nodecnt_clocksource_init();
+#endif
}
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 34faa0320ece..1c068f604333 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -743,4 +743,12 @@ config EP93XX_TIMER
Enables support for the Cirrus Logic timer block
EP93XX.
+config LOONGSON_NODECNT
+ bool "Loongson Node Conunter timer driver"
+ default y if MIPS && MACH_LOONGSON64
+ depends on (MIPS && MACH_LOONGSON64) || COMPILE_TEST
+ depends on GENERIC_SCHED_CLOCK
+ help
+ Enables support for the Loongson Node Counter timer.
+
endmenu
diff --git a/drivers/clocksource/loongson-nodecnt.c b/drivers/clocksource/loongson-nodecnt.c
new file mode 100644
index 000000000000..3cea4045ce75
--- /dev/null
+++ b/drivers/clocksource/loongson-nodecnt.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024, Jiaxun Yang <[email protected]>
+ * Loongson-3 Node Counter clocksource
+ */
+
+#include <linux/clocksource.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/sched_clock.h>
+
+#include <loongson.h>
+#include <loongson_regs.h>
+
+#define NODECNT_REGBASE 0x3ff00408
+
+static void __iomem *nodecnt_reg;
+static u64 (*nodecnt_read_fn)(void);
+
+static u64 notrace nodecnt_read_2x32(void)
+{
+ unsigned int hi, hi2, lo;
+
+ do {
+ hi = readl_relaxed(nodecnt_reg + 4);
+ lo = readl_relaxed(nodecnt_reg);
+ hi2 = readl_relaxed(nodecnt_reg + 4);
+ } while (hi2 != hi);
+
+ return (((u64) hi) << 32) + lo;
+}
+
+static u64 notrace nodecnt_read_64(void)
+{
+ return readq_relaxed(nodecnt_reg);
+}
+
+static u64 notrace nodecnt_read_csr(void)
+{
+ return csr_readq(LOONGSON_CSR_NODECNT);
+}
+
+static u64 nodecnt_clocksource_read(struct clocksource *cs)
+{
+ return nodecnt_read_fn();
+}
+
+static struct clocksource nodecnt_clocksource = {
+ .name = "nodecnt",
+ .read = nodecnt_clocksource_read,
+ .mask = CLOCKSOURCE_MASK(64),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+int __init nodecnt_clocksource_init(void)
+{
+ int err;
+ uint64_t delta;
+
+ if (!cpu_clock_freq)
+ return -ENODEV;
+
+ if (cpu_has_csr() && csr_readl(LOONGSON_CSR_FEATURES) & LOONGSON_CSRF_NODECNT) {
+ nodecnt_read_fn = nodecnt_read_csr;
+ } else if (loongson_sysconf.bridgetype == VIRTUAL) {
+ nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
+ if (!nodecnt_reg)
+ return -ENOMEM;
+ nodecnt_read_fn = nodecnt_read_64;
+ } else {
+ switch (boot_cpu_data.processor_id & (PRID_IMP_MASK | PRID_REV_MASK)) {
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_1:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_1:
+ break;
+ default:
+ return -ENODEV;
+ }
+ nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
+ if (!nodecnt_reg)
+ return -ENOMEM;
+ nodecnt_read_fn = nodecnt_read_2x32;
+ }
+
+ /* Test if nodecnt is usable */
+ delta = nodecnt_read_fn();
+ udelay(10);
+ delta = nodecnt_read_fn() - delta;
+
+ if (!delta) {
+ pr_info("nodecnt: clocksource unusable\n");
+ err = -ENODEV;
+ goto out;
+ }
+
+ err = clocksource_register_hz(&nodecnt_clocksource, cpu_clock_freq);
+ if (err) {
+ pr_err("nodecnt: clocksource register failed\n");
+ goto out;
+ }
+
+ /* It fits for sched_clock if we don't suffer from cross node access */
+ if (loongson_sysconf.bridgetype == VIRTUAL || loongson_sysconf.nr_nodes <= 1)
+ sched_clock_register(nodecnt_read_fn, 64, cpu_clock_freq);
+
+out:
+ if (nodecnt_reg)
+ iounmap(nodecnt_reg);
+ return err;
+}
---
base-commit: 75fa778d74b786a1608d55d655d42b480a6fa8bd
change-id: 20240512-loongson_nodecnt-0704f76bc959
Best regards,
--
Jiaxun Yang <[email protected]>
Hi Jiaxun,
On 17/5/24 19:13, Jiaxun Yang wrote:
> Node counter is a timer presents on many Loongson-3 series CPUs.
> It is maintained on every node in system. To avoid synchronisation
> complexity we only access the copy from first node in system.
>
> It also has many ways to be accessed, on latest Loongson-3 CPU with
> IOCSR instruction support it should be accessed with a IOCSR request,
> while on earlier Loongson-3 CPUs it is attached to a 32 bits MMIO bus.
> For QEMU's Loongson-3 virt system it is mapped to a 64 bit MMIO location.
>
> On some rare case the counter is disabled by firmware or not present
> on chip, so we need to perform a lightweight test to ensure it is
> running before actually use it.
>
> Signed-off-by: Jiaxun Yang <[email protected]>
> ---
> Changes in v2:
> - Fix build failure when it's not enabled.
> - Link to v1: https://lore.kernel.org/r/[email protected]
> ---
> MAINTAINERS | 1 +
> arch/mips/include/asm/mach-loongson64/loongson.h | 3 +
> arch/mips/loongson64/time.c | 3 +
> drivers/clocksource/Kconfig | 8 ++
> drivers/clocksource/loongson-nodecnt.c | 112 +++++++++++++++++++++++
> 5 files changed, 127 insertions(+)
> diff --git a/drivers/clocksource/loongson-nodecnt.c b/drivers/clocksource/loongson-nodecnt.c
> new file mode 100644
> index 000000000000..3cea4045ce75
> --- /dev/null
> +++ b/drivers/clocksource/loongson-nodecnt.c
> +#define NODECNT_REGBASE 0x3ff00408
> +
> +static void __iomem *nodecnt_reg;
> +static u64 (*nodecnt_read_fn)(void);
> +
> +static u64 notrace nodecnt_read_2x32(void)
> +{
> + unsigned int hi, hi2, lo;
> +
> + do {
> + hi = readl_relaxed(nodecnt_reg + 4);
> + lo = readl_relaxed(nodecnt_reg);
> + hi2 = readl_relaxed(nodecnt_reg + 4);
> + } while (hi2 != hi);
> +
> + return (((u64) hi) << 32) + lo;
> +}
> +
> +static u64 notrace nodecnt_read_64(void)
> +{
> + return readq_relaxed(nodecnt_reg);
> +}
> +int __init nodecnt_clocksource_init(void)
> +{
> + int err;
> + uint64_t delta;
> +
> + if (!cpu_clock_freq)
> + return -ENODEV;
> +
> + if (cpu_has_csr() && csr_readl(LOONGSON_CSR_FEATURES) & LOONGSON_CSRF_NODECNT) {
> + nodecnt_read_fn = nodecnt_read_csr;
> + } else if (loongson_sysconf.bridgetype == VIRTUAL) {
> + nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
> + if (!nodecnt_reg)
> + return -ENOMEM;
> + nodecnt_read_fn = nodecnt_read_64;
> + } else {
> + switch (boot_cpu_data.processor_id & (PRID_IMP_MASK | PRID_REV_MASK)) {
> + case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_0:
> + case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_1:
> + case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_0:
> + case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_1:
> + break;
> + default:
> + return -ENODEV;
> + }
> + nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
> + if (!nodecnt_reg)
> + return -ENOMEM;
> + nodecnt_read_fn = nodecnt_read_2x32;
> + }
> +
> + /* Test if nodecnt is usable */
> + delta = nodecnt_read_fn();
> + udelay(10);
> + delta = nodecnt_read_fn() - delta;
> +
> + if (!delta) {
> + pr_info("nodecnt: clocksource unusable\n");
> + err = -ENODEV;
> + goto out;
> + }
> +
> + err = clocksource_register_hz(&nodecnt_clocksource, cpu_clock_freq);
> + if (err) {
> + pr_err("nodecnt: clocksource register failed\n");
> + goto out;
> + }
> +
> + /* It fits for sched_clock if we don't suffer from cross node access */
> + if (loongson_sysconf.bridgetype == VIRTUAL || loongson_sysconf.nr_nodes <= 1)
> + sched_clock_register(nodecnt_read_fn, 64, cpu_clock_freq);
return 0; ? ...
> +
> +out:
.. or:
if (err) ?
> + if (nodecnt_reg)
> + iounmap(nodecnt_reg);
> + return err;
> +}
>
> ---
> base-commit: 75fa778d74b786a1608d55d655d42b480a6fa8bd
> change-id: 20240512-loongson_nodecnt-0704f76bc959
>
> Best regards,
Jiaxun!
On Fri, May 17 2024 at 18:13, Jiaxun Yang wrote:
> +static void __iomem *nodecnt_reg;
> +static u64 (*nodecnt_read_fn)(void);
> +
> +static u64 notrace nodecnt_read_2x32(void)
> +{
> + unsigned int hi, hi2, lo;
> +
> + do {
> + hi = readl_relaxed(nodecnt_reg + 4);
> + lo = readl_relaxed(nodecnt_reg);
> + hi2 = readl_relaxed(nodecnt_reg + 4);
> + } while (hi2 != hi);
> +
> + return (((u64) hi) << 32) + lo;
> +}
> +
> +static u64 notrace nodecnt_read_64(void)
> +{
> + return readq_relaxed(nodecnt_reg);
> +}
> +
> +static u64 notrace nodecnt_read_csr(void)
> +{
> + return csr_readq(LOONGSON_CSR_NODECNT);
> +}
> +
> +static u64 nodecnt_clocksource_read(struct clocksource *cs)
> +{
> + return nodecnt_read_fn();
What's this indirection for? Why dont you update
> +static struct clocksource nodecnt_clocksource = {
> + .name = "nodecnt",
> + .read = nodecnt_clocksource_read,
the read function pointer here and spare the indirection?
Thanks
tglx
在2024年5月27日五月 下午4:51,Thomas Gleixner写道:
> Jiaxun!
>
Hi Thomas,
[...]
>
> What's this indirection for? Why dont you update
[...]
>
>> +static struct clocksource nodecnt_clocksource = {
>> + .name = "nodecnt",
>> + .read = nodecnt_clocksource_read,
>
> the read function pointer here and spare the indirection?
Smart! sched_clock takes slightly different function pointer argument type
but as we don't use the argument anyway, it should be safe to relax this
indirection.
Will fix in v3.
Thanks
>
> Thanks
>
> tglx
--
- Jiaxun
在2024年5月27日五月 下午5:32,Jiaxun Yang写道:
> 在2024年5月27日五月 下午4:51,Thomas Gleixner写道:
>> Jiaxun!
>>
> Hi Thomas,
> [...]
>>
>> What's this indirection for? Why dont you update
> [...]
>>
>>> +static struct clocksource nodecnt_clocksource = {
>>> + .name = "nodecnt",
>>> + .read = nodecnt_clocksource_read,
>>
>> the read function pointer here and spare the indirection?
> Smart! sched_clock takes slightly different function pointer argument type
> but as we don't use the argument anyway, it should be safe to relax this
> indirection.
Sadly, I'm unable to remove it with force type catsing :-/
drivers/clocksource/loongson-nodecnt.c: In function ‘nodecnt_clocksource_init’:
drivers/clocksource/loongson-nodecnt.c:89:36: warning: cast between incompatible function types from ‘u64 (*)(void)’ {aka ‘long long unsigned int (*)(void)’} to ‘u64 (*)(struct clocksource *)’ {aka ‘long long unsigned int (*)(struct clocksource *)’} [-Wcast-function-type]
89 | nodecnt_clocksource.read = (u64 (*)(struct clocksource *))nodecnt_read_fn;
I'll leave it here.
Thanks
>
> Will fix in v3.
>
> Thanks
>>
>> Thanks
>>
>> tglx
>
> --
> - Jiaxun
--
- Jiaxun
On Tue, May 28 2024 at 15:31, Jiaxun Yang wrote:
> 在2024年5月27日五月 下午5:32,Jiaxun Yang写道:
>> 在2024年5月27日五月 下午4:51,Thomas Gleixner写道:
>>> Jiaxun!
>>>
>> Hi Thomas,
>> [...]
>>>
>>> What's this indirection for? Why dont you update
>> [...]
>>>
>>>> +static struct clocksource nodecnt_clocksource = {
>>>> + .name = "nodecnt",
>>>> + .read = nodecnt_clocksource_read,
>>>
>>> the read function pointer here and spare the indirection?
>> Smart! sched_clock takes slightly different function pointer argument type
>> but as we don't use the argument anyway, it should be safe to relax this
>> indirection.
>
> Sadly, I'm unable to remove it with force type catsing :-/
>
> drivers/clocksource/loongson-nodecnt.c: In function ‘nodecnt_clocksource_init’:
> drivers/clocksource/loongson-nodecnt.c:89:36: warning: cast between incompatible function types from ‘u64 (*)(void)’ {aka ‘long long unsigned int (*)(void)’} to ‘u64 (*)(struct clocksource *)’ {aka ‘long long unsigned int (*)(struct clocksource *)’} [-Wcast-function-type]
> 89 | nodecnt_clocksource.read = (u64 (*)(struct clocksource *))nodecnt_read_fn;
>
What about making the actual read functions have the required function
signature? The clocksource argument is not used in those real functions.
Thanks,
tglx
在2024年5月28日五月 下午8:17,Thomas Gleixner写道:
> On Tue, May 28 2024 at 15:31, Jiaxun Yang wrote:
>> 在2024年5月27日五月 下午5:32,Jiaxun Yang写道:
>>> 在2024年5月27日五月 下午4:51,Thomas Gleixner写道:
>>>> Jiaxun!
>>>>
>>> Hi Thomas,
>>> [...]
>>>>
>>>> What's this indirection for? Why dont you update
>>> [...]
>>>>
>>>>> +static struct clocksource nodecnt_clocksource = {
>>>>> + .name = "nodecnt",
>>>>> + .read = nodecnt_clocksource_read,
>>>>
>>>> the read function pointer here and spare the indirection?
>>> Smart! sched_clock takes slightly different function pointer argument type
>>> but as we don't use the argument anyway, it should be safe to relax this
>>> indirection.
>>
>> Sadly, I'm unable to remove it with force type catsing :-/
>>
>> drivers/clocksource/loongson-nodecnt.c: In function ‘nodecnt_clocksource_init’:
>> drivers/clocksource/loongson-nodecnt.c:89:36: warning: cast between incompatible function types from ‘u64 (*)(void)’ {aka ‘long long unsigned int (*)(void)’} to ‘u64 (*)(struct clocksource *)’ {aka ‘long long unsigned int (*)(struct clocksource *)’} [-Wcast-function-type]
>> 89 | nodecnt_clocksource.read = (u64 (*)(struct clocksource *))nodecnt_read_fn;
>>
>
> What about making the actual read functions have the required function
> signature? The clocksource argument is not used in those real functions.
Sadly sched_clock requires another signature :-/
I can see timer-clint hit into the same pitfall.
Thanks
>
> Thanks,
>
> tglx
--
- Jiaxun