Add ampchwm(4) a driver to access the Ampere Altra Fine-Grained Power Telemetry
authorclaudio <claudio@openbsd.org>
Mon, 11 Dec 2023 11:15:44 +0000 (11:15 +0000)
committerclaudio <claudio@openbsd.org>
Mon, 11 Dec 2023 11:15:44 +0000 (11:15 +0000)
This adds per core energy sensors (in Joules) and one per SoC temparature
sensor.
OK kettenis@ deraadt@

sys/arch/arm64/conf/GENERIC
sys/arch/arm64/conf/files.arm64
sys/arch/arm64/dev/ampchwm.c [new file with mode: 0644]

index 3b6bc19..0b518c2 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: GENERIC,v 1.277 2023/07/22 22:43:53 patrick Exp $
+# $OpenBSD: GENERIC,v 1.278 2023/12/11 11:15:44 claudio Exp $
 #
 # GENERIC machine description file
 #
@@ -49,6 +49,7 @@ acpiiort*     at acpi?
 smmu*          at acpiiort?
 acpipci*       at acpi?
 pci*           at acpipci?
+ampchwm*       at acpi?
 acpipwrres*    at acpi?
 acpitz*                at acpi?
 ahci*          at acpi?
index 9ec032f..4414dd8 100644 (file)
@@ -1,4 +1,4 @@
-# $OpenBSD: files.arm64,v 1.66 2023/01/14 12:11:11 kettenis Exp $
+# $OpenBSD: files.arm64,v 1.67 2023/12/11 11:15:44 claudio Exp $
 
 maxpartitions  16
 maxusers       2 8 128
@@ -279,6 +279,10 @@ file       arch/arm64/dev/acpipci.c                acpipci
 attach smmu at acpiiort with smmu_acpi
 file   arch/arm64/dev/smmu_acpi.c              smmu_acpi
 
+device ampchwm
+attach ampchwm at acpi
+file   arch/arm64/dev/ampchwm.c                ampchwm
+
 device  apm
 attach  apm at mainbus
 file   arch/arm64/dev/apm.c                    apm             needs-flag
diff --git a/sys/arch/arm64/dev/ampchwm.c b/sys/arch/arm64/dev/ampchwm.c
new file mode 100644 (file)
index 0000000..2115e0d
--- /dev/null
@@ -0,0 +1,272 @@
+/*     $OpenBSD: ampchwm.c,v 1.1 2023/12/11 11:15:44 claudio Exp $ */
+/*
+ * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
+#include <sys/sensors.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/amltypes.h>
+
+int    ampchwm_match(struct device *, void *, void *);
+void   ampchwm_attach(struct device *, struct device *, void *);
+
+#define HWMON_ID                       0x304d5748
+#define HWMON_UNIT_CELSIUS             0x01
+#define HWMON_UNIT_JOULES              0x10
+#define HWMON_UNIT_MILIJOULES          0x11
+#define HWMON_UNIT_MICROJOULES         0x12
+#define HWMON_MAX_METRIC_COUNT         2
+
+union metrics_hdr {
+       uint64_t        data;
+       struct {
+               uint32_t        id;
+               uint16_t        version;
+               uint16_t        count;
+       };
+};
+
+union metric_hdr {
+       uint64_t        data[3];
+       struct {
+               char            label[16];
+               uint8_t         unit;
+               uint8_t         data_size;
+               uint16_t        data_count;
+               uint32_t        pad;
+       };
+};
+
+
+struct ampchwm_softc {
+       struct device                   sc_dev;
+       struct acpi_softc               *sc_acpi;
+       struct aml_node                 *sc_node;
+
+       bus_space_tag_t                 sc_iot;
+       bus_space_handle_t              sc_ioh;
+       size_t                          sc_size;
+
+       uint16_t                        sc_count;
+       struct {
+               struct ksensor                  *sc_sens;
+               uint16_t                        sc_sens_offset;
+               uint16_t                        sc_sens_count;
+               uint16_t                        sc_sens_size;
+               uint16_t                        sc_sens_unit;
+       }                               sc_metrics[HWMON_MAX_METRIC_COUNT];
+
+       struct ksensordev               sc_sensdev;
+       struct sensor_task              *sc_sens_task;
+};
+
+const struct cfattach ampchwm_ca = {
+       sizeof(struct ampchwm_softc), ampchwm_match, ampchwm_attach
+};
+
+struct cfdriver ampchwm_cd = {
+        NULL, "ampchwm", DV_DULL
+};
+
+const char *ampchwm_hids[] = {
+       "AMPC0005",
+       NULL
+};
+
+int    ampchwm_attach_sensors(struct ampchwm_softc *, int,
+           union metric_hdr *, uint16_t *);
+void   ampchwm_refresh_sensors(void *);
+void   ampchwm_update_sensor(struct ampchwm_softc *, int, int);
+
+
+int
+ampchwm_match(struct device *parent, void *match, void *aux)
+{
+       struct acpi_attach_args *aaa = aux;
+       struct cfdata *cf = match;
+
+       if (aaa->aaa_naddr < 1)
+               return (0);
+       return (acpi_matchhids(aaa, ampchwm_hids, cf->cf_driver->cd_name));
+}
+
+void
+ampchwm_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct ampchwm_softc    *sc = (struct ampchwm_softc *)self;
+       struct acpi_attach_args *aaa = aux;
+       union metrics_hdr hdr;
+       union metric_hdr metric;
+       uint16_t offset = 0;
+       int i;
+
+       sc->sc_acpi = (struct acpi_softc *)parent;
+       sc->sc_node = aaa->aaa_node;
+
+       printf(" %s", sc->sc_node->name);
+       printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);
+
+       sc->sc_iot = aaa->aaa_bst[0];
+       sc->sc_size = aaa->aaa_size[0];
+
+       if (bus_space_map(sc->sc_iot, aaa->aaa_addr[0], aaa->aaa_size[0],
+           0, &sc->sc_ioh)) {
+               printf(": can't map registers\n");
+               return;
+       }
+
+       bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset, &hdr.data, 1);
+
+       if (hdr.id != HWMON_ID) {
+               printf(": bad id %x\n", hdr.id);
+               goto unmap;
+       }
+
+       printf(": ver %d", hdr.version);
+
+       strlcpy(sc->sc_sensdev.xname, sc->sc_dev.dv_xname,
+           sizeof(sc->sc_sensdev.xname));
+
+       offset += sizeof(hdr);
+       for (i = 0; i < hdr.count; i++) {
+               bus_space_read_region_8(sc->sc_iot, sc->sc_ioh, offset,
+                   metric.data, 3);
+               if (ampchwm_attach_sensors(sc, i, &metric, &offset))
+                       goto unmap;
+       }
+       sc->sc_count = MIN(hdr.count, HWMON_MAX_METRIC_COUNT);
+
+       sensordev_install(&sc->sc_sensdev);
+       sc->sc_sens_task = sensor_task_register(sc, ampchwm_refresh_sensors, 1);
+       printf("\n");
+
+       return;
+unmap:
+       bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
+       return;
+}
+
+int
+ampchwm_attach_sensors(struct ampchwm_softc *sc, int num,
+    union metric_hdr *metric, uint16_t *offsetp)
+{
+       uint16_t off = *offsetp;
+       int i, count = 0;
+
+       if (num >= HWMON_MAX_METRIC_COUNT) {
+               if (num == HWMON_MAX_METRIC_COUNT)
+                       printf(" ignoring extra metrics");
+               return 0;
+       }
+
+       off += sizeof(*metric);
+       /* skip 0 values since those are disabled cores */
+       for (i = 0; i < metric->data_count; i++) {
+               if (bus_space_read_8(sc->sc_iot, sc->sc_ioh,
+                   off + i * 8) == 0)
+                       continue;
+               count++;
+       }
+
+       sc->sc_metrics[num].sc_sens = mallocarray(count,
+           sizeof(struct ksensor), M_DEVBUF, M_NOWAIT);
+       if (sc->sc_metrics[num].sc_sens == NULL) {
+               printf(" out of memory\n");
+               return -1;
+       }
+
+       sc->sc_metrics[num].sc_sens_offset = off;
+       sc->sc_metrics[num].sc_sens_count = count;
+       sc->sc_metrics[num].sc_sens_unit = metric->unit;
+       if (metric->data_size == 0)
+               sc->sc_metrics[num].sc_sens_size = 8;
+       else
+               sc->sc_metrics[num].sc_sens_size = 4;
+
+       for (i = 0; i < count; i++) {
+               struct ksensor *s = &sc->sc_metrics[num].sc_sens[i];
+
+               strlcpy(s->desc, metric->label, sizeof(s->desc));
+               if (metric->unit == HWMON_UNIT_CELSIUS)
+                       s->type = SENSOR_TEMP;
+               else
+                       s->type = SENSOR_ENERGY;
+               sensor_attach(&sc->sc_sensdev, s);
+
+               ampchwm_update_sensor(sc, num, i);
+       }
+
+       off += metric->data_count * 8;
+
+       printf(", %d \"%s\"", count, metric->label);
+       *offsetp = off;
+       return 0;
+}
+
+void
+ampchwm_refresh_sensors(void *arg)
+{
+       struct ampchwm_softc *sc = arg;
+       int num, i;
+
+       for (num = 0; num < sc->sc_count; num++)
+               for (i = 0; i < sc->sc_metrics[num].sc_sens_count; i++)
+                       ampchwm_update_sensor(sc, num, i);
+}
+
+void
+ampchwm_update_sensor(struct ampchwm_softc *sc, int num, int i)
+{
+       struct ksensor *s;
+       uint64_t v;
+
+       KASSERT(i < sc->sc_metrics[num].sc_sens_count);
+
+       s = &sc->sc_metrics[num].sc_sens[i];
+       if (sc->sc_metrics[num].sc_sens_size == 8) {
+               v = bus_space_read_8(sc->sc_iot, sc->sc_ioh,
+                   sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
+       } else {
+               v = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+                   sc->sc_metrics[num].sc_sens_offset + i * sizeof(v));
+       }
+
+       if (v == 0) {
+               s->flags = SENSOR_FUNKNOWN;
+               s->status = SENSOR_S_UNKNOWN;
+       } else {
+               s->flags = 0;
+               s->status = SENSOR_S_OK;
+       }
+
+       switch (sc->sc_metrics[num].sc_sens_unit) {
+       case HWMON_UNIT_CELSIUS:
+               s->value = v * 1000 * 1000 + 273150000;
+               break;
+       case HWMON_UNIT_JOULES:
+               v *= 1000;
+       case HWMON_UNIT_MILIJOULES:
+               v *= 1000;
+       case HWMON_UNIT_MICROJOULES:
+               s->value = v;
+               break;
+       }
+}