Index: sys/arm64/intel/firmware.c =================================================================== --- sys/arm64/intel/firmware.c +++ sys/arm64/intel/firmware.c @@ -55,10 +55,11 @@ node = ofw_bus_get_node(dev); /* - * The firmware node has no property compatible. + * The firmware node has no 'compatible' property. * Look for a known child. */ - if (!fdt_depth_search_compatible(node, "intel,stratix10-svc", 0)) + if (!fdt_depth_search_compatible(node, "intel,stratix10-svc", 0) && + !fdt_depth_search_compatible(node, "arm,scmi", 0)) return (ENXIO); if (!ofw_bus_status_okay(dev)) Index: sys/arm64/scmi/doorbell.h =================================================================== --- /dev/null +++ sys/arm64/scmi/doorbell.h @@ -0,0 +1,52 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _ARM64_SCMI_DOORBELL_DOORBELL_H_ +#define _ARM64_SCMI_DOORBELL_DOORBELL_H_ + +static MALLOC_DEFINE(M_DOORBELL, "doorbell", "Doorbell"); + +struct doorbell { + device_t dev; + device_t db_dev; + int chan; + int db; + void (*func)(void *); + void *arg; +}; + +void doorbell_set(struct doorbell *db); +int doorbell_get(struct doorbell *db); +struct doorbell * doorbell_ofw_get(device_t dev, const char *name); +void doorbell_set_handler(struct doorbell *db, void (*func)(void *), void *arg); + +#endif /* !_ARM64_SCMI_DOORBELL_DOORBELL_H_ */ Index: sys/arm64/scmi/doorbell.c =================================================================== --- /dev/null +++ sys/arm64/scmi/doorbell.c @@ -0,0 +1,341 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "doorbell.h" + +#define MHU_CHAN_RX_LP 0x000 /* Low priority channel */ +#define MHU_CHAN_RX_HP 0x020 /* High priority channel */ +#define MHU_CHAN_RX_SEC 0x200 /* Secure channel */ +#define MHU_INTR_STAT 0x00 +#define MHU_INTR_SET 0x08 +#define MHU_INTR_CLEAR 0x10 + +#define MHU_TX_REG_OFFSET 0x100 + +#define DOORBELL_N_CHANNELS 3 +#define DOORBELL_N_DOORBELLS (DOORBELL_N_CHANNELS * 32) + +struct doorbell dbells[DOORBELL_N_DOORBELLS]; + +static struct resource_spec doorbell_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 1, RF_ACTIVE }, + { -1, 0 } +}; + +struct doorbell_softc { + struct resource *res[3]; + void *lp_intr_cookie; + void *hp_intr_cookie; + device_t dev; +}; + +static void +doorbell_lp_intr(void *arg) +{ + struct doorbell_softc *sc; + struct doorbell *db; + uint32_t reg; + int i; + + sc = arg; + + reg = bus_read_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_STAT); + for (i = 0; i < 32; i++) { + if (reg & (1 << i)) { + db = &dbells[i]; + bus_write_4(sc->res[0], MHU_CHAN_RX_LP + MHU_INTR_CLEAR, + (1 << i)); + if (db->func != NULL) + db->func(db->arg); + } + } +} + +static void +doorbell_hp_intr(void *arg) +{ + struct doorbell_softc *sc; + struct doorbell *db; + uint32_t reg; + int i; + + sc = arg; + + reg = bus_read_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_STAT); + for (i = 0; i < 32; i++) { + if (reg & (1 << i)) { + db = &dbells[i]; + bus_write_4(sc->res[0], MHU_CHAN_RX_HP + MHU_INTR_CLEAR, + (1 << i)); + if (db->func != NULL) + db->func(db->arg); + } + } +} + +static int +doorbell_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "arm,mhu-doorbell")) + return (ENXIO); + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + device_set_desc(dev, "ARM MHU Doorbell"); + + return (BUS_PROBE_DEFAULT); +} + +static int +doorbell_attach(device_t dev) +{ + struct doorbell_softc *sc; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + + node = ofw_bus_get_node(dev); + if (node == -1) + return (ENXIO); + + if (bus_alloc_resources(dev, doorbell_spec, sc->res) != 0) { + device_printf(dev, "Can't allocate resources for device.\n"); + return (ENXIO); + } + + /* Setup interrupt handlers. */ + error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, + NULL, doorbell_lp_intr, sc, &sc->lp_intr_cookie); + if (error != 0) { + device_printf(dev, "Can't setup LP interrupt handler.\n"); + bus_release_resources(dev, doorbell_spec, sc->res); + return (ENXIO); + } + + error = bus_setup_intr(dev, sc->res[2], INTR_TYPE_MISC | INTR_MPSAFE, + NULL, doorbell_hp_intr, sc, &sc->hp_intr_cookie); + if (error != 0) { + device_printf(dev, "Can't setup HP interrupt handler.\n"); + bus_release_resources(dev, doorbell_spec, sc->res); + return (ENXIO); + } + + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); +} + +static int +doorbell_detach(device_t dev) +{ + + return (0); +} + +struct doorbell * +doorbell_ofw_get(device_t dev, const char *name) +{ + phandle_t node, parent; + struct doorbell *db; + device_t db_dev; + pcell_t *cells; + int nmboxes; + int ncells; + int idx; + int db_id; + int error; + int chan; + + node = ofw_bus_get_node(dev); + + error = ofw_bus_parse_xref_list_get_length(node, "mboxes", + "#mbox-cells", &nmboxes); + if (error) { + device_printf(dev, "%s can't get mboxes list.\n", __func__); + return (NULL); + } + + if (nmboxes == 0) { + device_printf(dev, "%s mbox list is empty.\n", __func__); + return (NULL); + } + + error = ofw_bus_find_string_index(node, "mbox-names", name, &idx); + if (error != 0) { + device_printf(dev, "%s can't find string index.\n", + __func__); + return (NULL); + } + + error = ofw_bus_parse_xref_list_alloc(node, "mboxes", "#mbox-cells", + idx, &parent, &ncells, &cells); + if (error != 0) { + device_printf(dev, "%s can't get mbox device xref\n", + __func__); + return (NULL); + } + + if (ncells != 2) { + device_printf(dev, "Unexpected data size.\n"); + return (NULL); + } + + db_dev = OF_device_from_xref(parent); + if (db_dev == NULL) { + device_printf(dev, "%s: Can't get doorbell device\n", __func__); + return (NULL); + } + + chan = cells[0]; + if (chan >= DOORBELL_N_CHANNELS) { + device_printf(dev, "Unexpected channel number.\n"); + return (NULL); + } + + db_id = cells[1]; + if (db_id >= 32) { + device_printf(dev, "Unexpected channel bit.\n"); + return (NULL); + } + + db = &dbells[chan * db_id]; + db->dev = dev; + db->db_dev = db_dev; + db->chan = chan; + db->db = db_id; + + free(cells, M_OFWPROP); + + return (db); +} + +void +doorbell_set(struct doorbell *db) +{ + struct doorbell_softc *sc; + uint32_t offset; + + sc = device_get_softc(db->db_dev); + + switch (db->chan) { + case 0: + offset = MHU_CHAN_RX_LP; + break; + case 1: + offset = MHU_CHAN_RX_HP; + break; + case 2: + offset = MHU_CHAN_RX_SEC; + break; + default: + panic("not reached"); + }; + + offset |= MHU_TX_REG_OFFSET; + + bus_write_4(sc->res[0], offset + MHU_INTR_SET, (1 << db->db)); +} + +int +doorbell_get(struct doorbell *db) +{ + struct doorbell_softc *sc; + uint32_t offset; + uint32_t reg; + + sc = device_get_softc(db->db_dev); + + switch (db->chan) { + case 0: + offset = MHU_CHAN_RX_LP; + break; + case 1: + offset = MHU_CHAN_RX_HP; + break; + case 2: + offset = MHU_CHAN_RX_SEC; + break; + default: + panic("not reached"); + }; + + reg = bus_read_4(sc->res[0], offset + MHU_INTR_STAT); + if (reg & (1 << db->db)) { + bus_write_4(sc->res[0], offset + MHU_INTR_CLEAR, + (1 << db->db)); + return (1); + } + + return (0); +} + +void +doorbell_set_handler(struct doorbell *db, void (*func)(void *), void *arg) +{ + + db->func = func; + db->arg = arg; +} + +static device_method_t doorbell_methods[] = { + DEVMETHOD(device_probe, doorbell_probe), + DEVMETHOD(device_attach, doorbell_attach), + DEVMETHOD(device_detach, doorbell_detach), + DEVMETHOD_END +}; + +DEFINE_CLASS_1(doorbell, doorbell_driver, doorbell_methods, + sizeof(struct doorbell_softc), simplebus_driver); + +EARLY_DRIVER_MODULE(doorbell, simplebus, doorbell_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(doorbell, 1); Index: sys/arm64/scmi/dt_cpufreq.c =================================================================== --- /dev/null +++ sys/arm64/scmi/dt_cpufreq.c @@ -0,0 +1,220 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "cpufreq_if.h" + +#define dprintf(fmt, ...) + +struct dt_cpufreq_softc { + device_t dev; + clk_t clk; +}; + +#define DT_CPUFREQ_MAX_FREQ_COUNT 4096 + +static void +dt_cpufreq_identify(driver_t *driver, device_t parent) +{ + + if (device_find_child(parent, "dt_cpufreq", -1) != NULL) + return; + if (BUS_ADD_CHILD(parent, 0, "dt_cpufreq", -1) == NULL) + device_printf(parent, "add child failed\n"); +} + +static int +dt_cpufreq_probe(device_t dev) +{ + + device_set_desc(dev, "ARM SCMI CPU Frequency driver"); + + return (BUS_PROBE_DEFAULT); +} + +static int +dt_cpufreq_attach(device_t dev) +{ + struct dt_cpufreq_softc *sc; + device_t parent; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + + parent = device_get_parent(dev); + + node = ofw_bus_get_node(parent); + if (node <= 0) + return (ENXIO); + + if (clk_get_by_ofw_index(dev, node, 0, &sc->clk) != 0) + device_printf(dev, "no clock for %s\n", + ofw_bus_get_name(device_get_parent(dev))); + + cpufreq_register(sc->dev); + + return (0); +} + +static int +dt_cpufreq_detach(device_t dev) +{ + + return (0); +} + +static int +dt_cpufreq_set(device_t dev, const struct cf_setting *cf) +{ + struct dt_cpufreq_softc *sc; + int error; + + dprintf("%s\n", __func__); + + if (cf == NULL) + return (EINVAL); + + sc = device_get_softc(dev); + + error = clk_set_freq(sc->clk, cf->freq * 1000000, 0); + if (error) + return (error); + + return (0); +} + +static int +dt_cpufreq_get(device_t dev, struct cf_setting *cf) +{ + struct dt_cpufreq_softc *sc; + uint64_t freq; + int error; + + dprintf("%s\n", __func__); + + if (cf == NULL) + return (EINVAL); + + sc = device_get_softc(dev); + + error = clk_get_freq(sc->clk, &freq); + if (error) + return (error); + + memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); + cf->freq = freq / 1000000; + cf->dev = dev; + + dprintf("%s: freq %d\n", __func__, cf->freq); + + return (0); +} + +static int +dt_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) +{ + struct dt_cpufreq_softc *sc; + uint64_t freq[DT_CPUFREQ_MAX_FREQ_COUNT]; + int freq_count; + int error; + int i; + + sc = device_get_softc(dev); + + freq_count = DT_CPUFREQ_MAX_FREQ_COUNT; + + error = clk_list_freq(sc->clk, freq, &freq_count); + if (error) + return (error); + + /* fill data with unknown value */ + memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); + + for (i = 0; i < freq_count; i++) { + sets[i].freq = freq[i] / 1000000; + sets[i].dev = dev; + } + + *count = freq_count; + + return (0); +} + +static int +dt_cpufreq_type(device_t dev, int *type) +{ + + if (type == NULL) + return (EINVAL); + + *type = CPUFREQ_TYPE_ABSOLUTE; + + return (0); +} + +static device_method_t dt_cpufreq_methods[] = { + DEVMETHOD(device_identify, dt_cpufreq_identify), + DEVMETHOD(device_probe, dt_cpufreq_probe), + DEVMETHOD(device_attach, dt_cpufreq_attach), + DEVMETHOD(device_detach, dt_cpufreq_detach), + + DEVMETHOD(cpufreq_drv_set, dt_cpufreq_set), + DEVMETHOD(cpufreq_drv_get, dt_cpufreq_get), + DEVMETHOD(cpufreq_drv_settings, dt_cpufreq_settings), + DEVMETHOD(cpufreq_drv_type, dt_cpufreq_type), + + DEVMETHOD_END +}; + +static driver_t dt_cpufreq_driver = { + "dt_cpufreq", + dt_cpufreq_methods, + sizeof(struct dt_cpufreq_softc), +}; + +DRIVER_MODULE(dt_cpufreq, cpu, dt_cpufreq_driver, 0, 0); Index: sys/arm64/scmi/mmio.c =================================================================== --- /dev/null +++ sys/arm64/scmi/mmio.c @@ -0,0 +1,159 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "mmio_if.h" + +#define dprintf(fmt, ...) + +static struct resource_spec mmio_sram_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +struct mmio_sram_softc { + struct simplebus_softc simplebus_sc; + struct resource *res[1]; + device_t dev; +}; + +static int +mmio_sram_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "mmio-sram")) + return (ENXIO); + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + device_set_desc(dev, "MMIO SRAM"); + + return (BUS_PROBE_DEFAULT); +} + +static int +mmio_sram_attach(device_t dev) +{ + struct mmio_sram_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + + if (bus_alloc_resources(dev, mmio_sram_spec, sc->res) != 0) { + device_printf(dev, "Can't allocate resources for device.\n"); + return (ENXIO); + } + + node = ofw_bus_get_node(dev); + if (node == -1) + return (ENXIO); + + simplebus_init(dev, node); + + /* + * Allow devices to identify. + */ + bus_generic_probe(dev); + + /* + * Now walk the OFW tree and attach top-level devices. + */ + for (node = OF_child(node); node > 0; node = OF_peer(node)) + simplebus_add_device(dev, node, 0, NULL, -1, NULL); + + return (bus_generic_attach(dev)); +} + +static int +mmio_sram_detach(device_t dev) +{ + + return (0); +} + +static uint8_t +mmio_sram_read_1(device_t dev, bus_size_t offset) +{ + struct mmio_sram_softc *sc; + + sc = device_get_softc(dev); + + dprintf("%s: reading from %lx\n", __func__, offset); + + return (bus_read_1(sc->res[0], offset)); +} + +static void +mmio_sram_write_1(device_t dev, bus_size_t offset, uint8_t val) +{ + struct mmio_sram_softc *sc; + + sc = device_get_softc(dev); + + dprintf("%s: writing to %lx val %x\n", __func__, offset, val); + + bus_write_1(sc->res[0], offset, val); +} + +static device_method_t mmio_sram_methods[] = { + /* Device Interface */ + DEVMETHOD(device_probe, mmio_sram_probe), + DEVMETHOD(device_attach, mmio_sram_attach), + DEVMETHOD(device_detach, mmio_sram_detach), + + /* MMIO interface */ + DEVMETHOD(mmio_read_1, mmio_sram_read_1), + DEVMETHOD(mmio_write_1, mmio_sram_write_1), + DEVMETHOD_END +}; + +DEFINE_CLASS_1(mmio_sram, mmio_sram_driver, mmio_sram_methods, + sizeof(struct mmio_sram_softc), simplebus_driver); + +EARLY_DRIVER_MODULE(mmio_sram, simplebus, mmio_sram_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(mmio_sram, 1); Index: sys/arm64/scmi/mmio_if.m =================================================================== --- /dev/null +++ sys/arm64/scmi/mmio_if.m @@ -0,0 +1,44 @@ +#- +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2022 Ruslan Bukin +# +# This work was supported by Innovate UK project 105694, "Digital Security by +# Design (DSbD) Technology Platform Prototype". +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $FreeBSD$ +# + +#include + +INTERFACE mmio; + +METHOD uint8_t read_1 { + device_t dev; + bus_size_t offset; +}; + +METHOD void write_1 { + device_t dev; + bus_size_t offset; + uint8_t value; +}; Index: sys/arm64/scmi/scmi.h =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi.h @@ -0,0 +1,93 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _ARM64_SCMI_SCMI_H_ +#define _ARM64_SCMI_SCMI_H_ + +#define SCMI_LOCK(sc) mtx_lock(&(sc)->mtx) +#define SCMI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) +#define SCMI_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) + +#define dprintf(fmt, ...) + +/* Shared Memory Transfer. */ +struct scmi_smt_header { + uint32_t reserved; + uint32_t channel_status; +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR (1 << 1) +#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE (1 << 0) + uint32_t reserved1[2]; + uint32_t flags; +#define SCMI_SHMEM_FLAG_INTR_ENABLED (1 << 0) + uint32_t length; + uint32_t msg_header; + uint8_t msg_payload[0]; +}; + +#define SMT_HEADER_SIZE sizeof(struct scmi_smt_header) + +#define SMT_HEADER_TOKEN_S 18 +#define SMT_HEADER_TOKEN_M (0x3fff << SMT_HEADER_TOKEN_S) +#define SMT_HEADER_PROTOCOL_ID_S 10 +#define SMT_HEADER_PROTOCOL_ID_M (0xff << SMT_HEADER_PROTOCOL_ID_S) +#define SMT_HEADER_MESSAGE_TYPE_S 8 +#define SMT_HEADER_MESSAGE_TYPE_M (0x3 << SMT_HEADER_MESSAGE_TYPE_S) +#define SMT_HEADER_MESSAGE_ID_S 0 +#define SMT_HEADER_MESSAGE_ID_M (0xff << SMT_HEADER_MESSAGE_ID_S) + +struct scmi_req { + int protocol_id; + int message_id; + uint8_t *in_buf; + uint32_t in_size; + uint8_t *out_buf; + uint32_t out_size; +}; + +struct scmi_perf_level { + int id; + uint32_t rate; +}; + +struct scmi_perf_domain { + int id; + struct scmi_perf_level *levels; + int level_count; +}; + +int scmi_request(device_t dev, struct scmi_req *req); +void scmi_shmem_read(device_t dev, bus_size_t offset, void *buf, + bus_size_t len); +void scmi_shmem_write(device_t dev, bus_size_t offset, void *buf, + bus_size_t len); + +#endif /* !_ARM64_SCMI_SCMI_H_ */ Index: sys/arm64/scmi/scmi.c =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi.c @@ -0,0 +1,276 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "doorbell.h" + +#include "scmi.h" +#include "scmi_protocols.h" + +struct scmi_softc { + struct simplebus_softc simplebus_sc; + device_t dev; + device_t tx_shmem; + struct doorbell *db; + struct mtx mtx; + int req_done; +}; + +static device_t +scmi_get_shmem(struct scmi_softc *sc, int index) +{ + uint32_t *shmems; + phandle_t node; + device_t dev; + size_t len; + int count; + + node = ofw_bus_get_node(sc->dev); + if (node <= 0) + return (NULL); + + len = OF_getproplen(node, "shmem"); + if (len <= 0) + return (NULL); + + count = len / sizeof(pcell_t); + if (index >= count) + return (NULL); + + len = OF_getencprop_alloc_multi(node, "shmem", sizeof(*shmems), + (void **)&shmems); + if (len <= 0) { + device_printf(sc->dev, "%s: Can't get shmem node.\n", __func__); + return (NULL); + } + + dev = OF_device_from_xref(shmems[index]); + if (dev == NULL) + device_printf(sc->dev, "%s: Can't get shmem device.\n", + __func__); + + OF_prop_free(shmems); + + return (dev); +} + +static void +scmi_callback(void *arg) +{ + struct scmi_softc *sc; + + sc = arg; + + dprintf("%s sc %p\n", __func__, sc); + + SCMI_LOCK(sc); + sc->req_done = 1; + wakeup(sc); + SCMI_UNLOCK(sc); +} + +static int +scmi_request_locked(struct scmi_softc *sc, struct scmi_req *req) +{ + struct scmi_smt_header hdr; + int timeout; + + bzero(&hdr, sizeof(struct scmi_smt_header)); + + SCMI_ASSERT_LOCKED(sc); + + /* Read header */ + scmi_shmem_read(sc->tx_shmem, 0, (void *)&hdr, SMT_HEADER_SIZE); + + if ((hdr.channel_status & SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE) == 0) + return (1); + + /* Update header */ + hdr.channel_status &= ~SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE; + hdr.msg_header = req->protocol_id << SMT_HEADER_PROTOCOL_ID_S; + hdr.msg_header |= req->message_id << SMT_HEADER_MESSAGE_ID_S; + hdr.length = sizeof(hdr.msg_header) + req->in_size; + hdr.flags |= SCMI_SHMEM_FLAG_INTR_ENABLED; + + /* Write header */ + scmi_shmem_write(sc->tx_shmem, 0, (void *)&hdr, SMT_HEADER_SIZE); + + /* Write request */ + scmi_shmem_write(sc->tx_shmem, SMT_HEADER_SIZE, req->in_buf, + req->in_size); + + sc->req_done = 0; + + /* Interrupt SCP firmware. */ + doorbell_set(sc->db); + + timeout = 200; + + do { + if (cold) { + if (doorbell_get(sc->db)) + break; + DELAY(10000); + } else { + msleep(sc, &sc->mtx, 0, "scmi", hz / 10); + if (sc->req_done) + break; + } + } while (timeout--); + + if (timeout <= 0) + return (-1); + + dprintf("%s: got reply, timeout %d\n", __func__, timeout); + + /* Read header. */ + scmi_shmem_read(sc->tx_shmem, 0, (void *)&hdr, SMT_HEADER_SIZE); + + /* Read response */ + scmi_shmem_read(sc->tx_shmem, SMT_HEADER_SIZE, req->out_buf, + req->out_size); + + return (0); +} + +int +scmi_request(device_t dev, struct scmi_req *req) +{ + struct scmi_softc *sc; + int error; + + sc = device_get_softc(dev); + + SCMI_LOCK(sc); + error = scmi_request_locked(sc, req); + SCMI_UNLOCK(sc); + + return (error); +} + +static int +scmi_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "arm,scmi")) + return (ENXIO); + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + device_set_desc(dev, "ARM SCMI interface driver"); + + return (BUS_PROBE_DEFAULT); +} + +static int +scmi_attach(device_t dev) +{ + struct scmi_softc *sc; + phandle_t node; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + + node = ofw_bus_get_node(dev); + if (node == -1) + return (ENXIO); + + sc->tx_shmem = scmi_get_shmem(sc, 0); + if (sc->tx_shmem == NULL) { + device_printf(dev, "TX shmem dev not found.\n"); + return (ENXIO); + } + + sc->db = doorbell_ofw_get(sc->dev, "tx"); + if (sc->db == NULL) { + device_printf(dev, "Doorbell device not found.\n"); + return (ENXIO); + } + + mtx_init(&sc->mtx, device_get_nameunit(dev), "SCMI", MTX_DEF); + + doorbell_set_handler(sc->db, scmi_callback, sc); + + simplebus_init(dev, node); + + /* + * Allow devices to identify. + */ + bus_generic_probe(dev); + + /* + * Now walk the OFW tree and attach top-level devices. + */ + for (node = OF_child(node); node > 0; node = OF_peer(node)) + simplebus_add_device(dev, node, 0, NULL, -1, NULL); + + error = bus_generic_attach(dev); + + return (error); +} + +static int +scmi_detach(device_t dev) +{ + + return (0); +} + +static device_method_t scmi_methods[] = { + DEVMETHOD(device_probe, scmi_probe), + DEVMETHOD(device_attach, scmi_attach), + DEVMETHOD(device_detach, scmi_detach), + DEVMETHOD_END +}; + +DEFINE_CLASS_1(scmi, scmi_driver, scmi_methods, sizeof(struct scmi_softc), + simplebus_driver); + +EARLY_DRIVER_MODULE(scmi, firmware, scmi_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(scmi, 1); Index: sys/arm64/scmi/scmi_clk.c =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi_clk.c @@ -0,0 +1,433 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "scmi.h" +#include "scmi_protocols.h" + +struct scmi_clk_softc { + device_t dev; + device_t scmi; + struct clkdom *clkdom; +}; + +struct scmi_clknode_softc { + device_t dev; + int clock_id; +}; + +static int +scmi_clk_get_rate(struct scmi_clk_softc *sc, int clk_id, uint64_t *rate) +{ + struct scmi_clk_rate_get_out out; + struct scmi_clk_rate_get_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_CLOCK_RATE_GET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_clk_rate_get_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_rate_get_out); + + in.clock_id = clk_id; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + *rate = out.rate_lsb | ((uint64_t)out.rate_msb << 32); + + return (0); +} + +static int +scmi_clk_set_rate(struct scmi_clk_softc *sc, int clk_id, uint64_t rate) +{ + struct scmi_clk_rate_set_out out; + struct scmi_clk_rate_set_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_CLOCK_RATE_SET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_clk_rate_set_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_rate_set_out); + + in.clock_id = clk_id; + in.flags = SCMI_CLK_RATE_ROUND_CLOSEST; + in.rate_lsb = (uint32_t)rate; + in.rate_msb = (uint32_t)(rate >> 32); + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + return (0); +} + +static int +scmi_clk_gate(struct scmi_clk_softc *sc, int clk_id, int enable) +{ + struct scmi_clk_state_out out; + struct scmi_clk_state_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_CLOCK_CONFIG_SET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_clk_state_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_state_out); + + in.clock_id = clk_id; + in.attributes = enable; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + return (0); +} + +static int +scmi_clknode_init(struct clknode *clk, device_t dev) +{ + + clknode_init_parent_idx(clk, 0); + + return (0); +} + +static int +scmi_clknode_recalc_freq(struct clknode *clk, uint64_t *freq) +{ + + return (0); +} + +static int +scmi_clknode_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, + int flags, int *stop) +{ + struct scmi_clknode_softc *clk_sc; + struct scmi_clk_softc *sc; + + clk_sc = clknode_get_softc(clk); + sc = device_get_softc(clk_sc->dev); + + dprintf("%s: %ld\n", __func__, *fout); + + scmi_clk_set_rate(sc, clk_sc->clock_id, *fout); + + *stop = 1; + + return (0); +} + +static clknode_method_t scmi_clknode_methods[] = { + /* Device interface */ + CLKNODEMETHOD(clknode_init, scmi_clknode_init), + CLKNODEMETHOD(clknode_recalc_freq, scmi_clknode_recalc_freq), + CLKNODEMETHOD(clknode_set_freq, scmi_clknode_set_freq), + CLKNODEMETHOD_END +}; + +DEFINE_CLASS_1(scmi_clknode, scmi_clknode_class, scmi_clknode_methods, + sizeof(struct scmi_clknode_softc), clknode_class); + +static int +scmi_clk_add_node(struct scmi_clk_softc *sc, int index, char *clock_name) +{ + struct scmi_clknode_softc *clk_sc; + struct clknode_init_def def; + struct clknode *clk; + + memset(&def, 0, sizeof(def)); + def.id = index; + def.name = clock_name; + def.parent_names = NULL; + def.parent_cnt = 0; + + clk = clknode_create(sc->clkdom, &scmi_clknode_class, &def); + if (clk == NULL) { + device_printf(sc->dev, "Cannot create clknode.\n"); + return (ENXIO); + } + + clk_sc = clknode_get_softc(clk); + clk_sc->dev = sc->dev; + clk_sc->clock_id = index; + + if (clknode_register(sc->clkdom, clk) == NULL) { + device_printf(sc->dev, "Could not register clock '%s'.\n", + def.name); + return (ENXIO); + } + + device_printf(sc->dev, "Clock '%s' registered.\n", def.name); + + return (0); +} + +static int +scmi_clk_get_name(struct scmi_clk_softc *sc, int index, char **result) +{ + struct scmi_clk_name_get_out out; + struct scmi_clk_name_get_in in; + struct scmi_req req; + char *clock_name; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_CLOCK_NAME_GET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_clk_name_get_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_name_get_out); + + in.clock_id = index; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + clock_name = malloc(sizeof(out.name), M_DEVBUF, M_WAITOK); + strncpy(clock_name, out.name, sizeof(out.name)); + + *result = clock_name; + + return (0); +} + +static int +scmi_clk_attrs(struct scmi_clk_softc *sc, int index) +{ + struct scmi_clk_attrs_out out; + struct scmi_clk_attrs_in in; + struct scmi_req req; + int error; + char *clock_name; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_CLOCK_ATTRIBUTES; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_clk_attrs_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_attrs_out); + + in.clock_id = index; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + if (out.attributes & CLK_ATTRS_EXT_CLK_NAME) { + error = scmi_clk_get_name(sc, index, &clock_name); + if (error) + return (error); + } else { + clock_name = malloc(sizeof(out.clock_name), M_DEVBUF, M_WAITOK); + strncpy(clock_name, out.clock_name, sizeof(out.clock_name)); + } + + error = scmi_clk_add_node(sc, index, clock_name); + + return (error); +} + +static int +scmi_clk_discover(struct scmi_clk_softc *sc) +{ + struct scmi_clk_protocol_attrs_out out; + struct scmi_req req; + int nclocks; + int failing; + int error; + int i; + + req.protocol_id = SCMI_PROTOCOL_ID_CLOCK; + req.message_id = SCMI_PROTOCOL_ATTRIBUTES; + req.in_buf = NULL; + req.in_size = 0; + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_clk_protocol_attrs_out); + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + nclocks = (out.attributes & CLK_ATTRS_NCLOCKS_M) >> + CLK_ATTRS_NCLOCKS_S; + + device_printf(sc->dev, "Found %d clocks.\n", nclocks); + + failing = 0; + + for (i = 0; i < nclocks; i++) { + error = scmi_clk_attrs(sc, i); + if (error) { + device_printf(sc->dev, + "Could not process clock index %d.\n", i); + failing++; + } + } + + if (failing == nclocks) + return (ENXIO); + + return (0); +} + +static int +scmi_clk_init(struct scmi_clk_softc *sc) +{ + int error; + + /* Create clock domain */ + sc->clkdom = clkdom_create(sc->dev); + if (sc->clkdom == NULL) + return (ENXIO); + + error = scmi_clk_discover(sc); + if (error) { + device_printf(sc->dev, "Could not discover clocks.\n"); + return (ENXIO); + } + + error = clkdom_finit(sc->clkdom); + if (error) { + device_printf(sc->dev, "Failed to init clock domain.\n"); + return (ENXIO); + } + + return (0); +} + +static int +scmi_clk_probe(device_t dev) +{ + phandle_t node; + uint32_t reg; + int error; + + node = ofw_bus_get_node(dev); + + error = OF_getencprop(node, "reg", ®, sizeof(uint32_t)); + if (error < 0) + return (ENXIO); + + if (reg != SCMI_PROTOCOL_ID_CLOCK) + return (ENXIO); + + device_set_desc(dev, "SCMI Clock Management Unit"); + + return (BUS_PROBE_DEFAULT); +} + +static int +scmi_clk_attach(device_t dev) +{ + struct scmi_clk_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->scmi = device_get_parent(dev); + + node = ofw_bus_get_node(sc->dev); + + OF_device_register_xref(OF_xref_from_node(node), sc->dev); + + scmi_clk_init(sc); + + return (0); +} + +static int +scmi_clk_detach(device_t dev) +{ + + return (0); +} + +static device_method_t scmi_clk_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, scmi_clk_probe), + DEVMETHOD(device_attach, scmi_clk_attach), + DEVMETHOD(device_detach, scmi_clk_detach), + { 0, 0 } +}; + +static driver_t scmi_clk_driver = { + "scmi_clk", + scmi_clk_methods, + sizeof(struct scmi_clk_softc), +}; + +EARLY_DRIVER_MODULE(scmi_clk, scmi, scmi_clk_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(scmi_clk, 1); Index: sys/arm64/scmi/scmi_perf.c =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi_perf.c @@ -0,0 +1,514 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "scmi.h" +#include "scmi_protocols.h" + +struct scmi_perf_softc { + device_t scmi; + device_t dev; + struct clkdom *clkdom; + struct scmi_perf_domain *domains; + int domain_count; +}; + +struct scmi_perf_clknode_softc { + device_t dev; + int domain_id; +}; + +static int +scmi_perf_clknode_init(struct clknode *clk, device_t dev) +{ + + clknode_init_parent_idx(clk, 0); + + return (0); +} + +static int +scmi_perf_clknode_list_freq(struct clknode *clk, uint64_t *freq, + int *freq_count) +{ + struct scmi_perf_clknode_softc *clk_sc; + struct scmi_perf_domain *domain; + struct scmi_perf_level *levels; + struct scmi_perf_softc *sc; + int i; + + clk_sc = clknode_get_softc(clk); + sc = device_get_softc(clk_sc->dev); + + domain = &sc->domains[clk_sc->domain_id]; + levels = domain->levels; + + if (freq == NULL) + return (ENXIO); + + if (*freq_count < domain->level_count) + return (ENXIO); + + for (i = 0; i < domain->level_count; i++) + freq[i] = levels[i].rate; + + *freq_count = domain->level_count; + + return (0); +} + +static int +scmi_perf_get_level(struct scmi_perf_softc *sc, int domain_id, uint64_t *freq) +{ + struct scmi_perf_level_get_out out; + struct scmi_perf_level_get_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PERFORMANCE_LEVEL_GET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_perf_level_get_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_perf_level_get_out); + + in.domain_id = domain_id; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + *freq = out.performance_level; + + return (0); +} + +static int +scmi_perf_clknode_recalc_freq(struct clknode *clk, uint64_t *freq_out) +{ + struct scmi_perf_clknode_softc *clk_sc; + struct scmi_perf_softc *sc; + uint64_t freq; + int error; + + dprintf("%s\n", __func__); + + clk_sc = clknode_get_softc(clk); + sc = device_get_softc(clk_sc->dev); + + error = scmi_perf_get_level(sc, clk_sc->domain_id, &freq); + if (error) + return (ENXIO); + + *freq_out = freq; + + return (0); +} + +static int +scmi_perf_set_level(struct scmi_perf_softc *sc, int domain_id, uint64_t freq) +{ + struct scmi_perf_level_set_out out; + struct scmi_perf_level_set_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PERFORMANCE_LEVEL_SET; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_perf_level_set_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_perf_level_set_out); + + in.domain_id = domain_id; + in.performance_level = freq; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + return (0); +} + +static int +scmi_perf_clknode_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, + int flags, int *stop) +{ + struct scmi_perf_clknode_softc *clk_sc; + struct scmi_perf_softc *sc; + + clk_sc = clknode_get_softc(clk); + sc = device_get_softc(clk_sc->dev); + + dprintf("%s: %lu\n", __func__, *fout); + + scmi_perf_set_level(sc, clk_sc->domain_id, *fout); + + *stop = 1; + + return (0); +} + +static clknode_method_t scmi_perf_clknode_methods[] = { + /* Device interface */ + CLKNODEMETHOD(clknode_init, scmi_perf_clknode_init), + CLKNODEMETHOD(clknode_list_freq, scmi_perf_clknode_list_freq), + CLKNODEMETHOD(clknode_recalc_freq, scmi_perf_clknode_recalc_freq), + CLKNODEMETHOD(clknode_set_freq, scmi_perf_clknode_set_freq), + CLKNODEMETHOD_END +}; + +DEFINE_CLASS_1(scmi_perf_clknode, scmi_perf_clknode_class, + scmi_perf_clknode_methods, sizeof(struct scmi_perf_clknode_softc), + clknode_class); + +static int +scmi_perf_add_node(struct scmi_perf_softc *sc, int index, char *clock_name) +{ + struct scmi_perf_clknode_softc *clk_sc; + struct clknode_init_def def; + struct clknode *clk; + + memset(&def, 0, sizeof(def)); + def.id = index; + def.name = clock_name; + def.parent_names = NULL; + def.parent_cnt = 0; + + clk = clknode_create(sc->clkdom, &scmi_perf_clknode_class, &def); + if (clk == NULL) { + device_printf(sc->dev, "Cannot create clknode.\n"); + return (ENXIO); + } + + clk_sc = clknode_get_softc(clk); + clk_sc->dev = sc->dev; + clk_sc->domain_id = index; + + if (clknode_register(sc->clkdom, clk) == NULL) { + device_printf(sc->dev, "Could not register perf clock '%s'.\n", + def.name); + return (ENXIO); + } + + device_printf(sc->dev, "Perf clock '%s' registered.\n", def.name); + + return (0); +} + +static int +scmi_perf_get_num_levels(struct scmi_perf_softc *sc, int domain, + int *num_levels) +{ + struct scmi_perf_describe_levels_out out; + struct scmi_perf_describe_levels_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PERFORMANCE_DESCRIBE_LEVELS; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_perf_describe_levels_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_perf_describe_levels_out); + + in.domain_id = domain; + in.level_index = 0; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + device_printf(sc->dev, "%s: status %d, num_levels %d\n", __func__, + out.status, out.num_levels); + + *num_levels = (out.num_levels & NUM_LEVELS_M) >> NUM_LEVELS_S; + + return (0); +} + +static int +scmi_perf_describe_levels(struct scmi_perf_softc *sc, int domain, + int num_levels) +{ + struct scmi_perf_describe_levels_out *out; + struct scmi_perf_describe_levels_in in; + struct scmi_perf_level_out *level; + struct scmi_req req; + int error; + int size; + int i; + + size = sizeof(struct scmi_perf_describe_levels_out) + + sizeof(struct scmi_perf_level_out) * num_levels; + out = malloc(size, M_DEVBUF, M_WAITOK); + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PERFORMANCE_DESCRIBE_LEVELS; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_perf_describe_levels_in); + req.out_buf = (uint8_t *)out; + req.out_size = size; + + in.domain_id = domain; + in.level_index = 0; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out->status != 0) + return (ENXIO); + + for (i = 0; i < num_levels; i++) { + level = &out->levels[i]; + device_printf(sc->dev, " -- Perf level #%d\n", i); + device_printf(sc->dev, " Perf level value %x\n", + level->perf_level_value); + device_printf(sc->dev, " Power cost %x\n", level->power_cost); + device_printf(sc->dev, " Attributes %x\n", level->attributes); + + sc->domains[domain].levels[i].rate = level->perf_level_value; + } + + free(out, M_DEVBUF); + + return (0); +} + +static int +scmi_perf_domain_info(struct scmi_perf_softc *sc, int index) +{ + struct scmi_perf_domain_attrs_out out; + struct scmi_perf_domain_attrs_in in; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES; + req.in_buf = (uint8_t *)∈ + req.in_size = sizeof(struct scmi_perf_domain_attrs_in); + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_perf_domain_attrs_out); + + in.domain_id = index; + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + device_printf(sc->dev, "Perf domain name: %s\n", out.name); + device_printf(sc->dev, "Perf protocol attributes: %x\n", + out.attributes); + device_printf(sc->dev, "Perf rate limit: %x\n", out.rate_limit); + device_printf(sc->dev, "Perf sustained freq: %x\n", out.sustained_freq); + device_printf(sc->dev, "Sustained Perf Level: %x\n", + out.sustained_perf_level); + + scmi_perf_add_node(sc, index, out.name); + + return (0); +} + +static int +scmi_perf_get_ndomains(struct scmi_perf_softc *sc, int *ndomains) +{ + struct scmi_perf_protocol_attrs_out out; + struct scmi_req req; + int error; + + req.protocol_id = SCMI_PROTOCOL_ID_PERF; + req.message_id = SCMI_PROTOCOL_ATTRIBUTES; + req.in_buf = NULL; + req.in_size = 0; + req.out_buf = (uint8_t *)&out; + req.out_size = sizeof(struct scmi_perf_protocol_attrs_out); + + error = scmi_request(sc->scmi, &req); + if (error != 0) + return (error); + + if (out.status != 0) + return (ENXIO); + + device_printf(sc->dev, "Perf protocol attributes: %x\n", + out.attributes); + + *ndomains = out.attributes & 0xffff; + + return (0); +} + +static int +scmi_perf_init(struct scmi_perf_softc *sc) +{ + struct scmi_perf_domain *domain; + phandle_t node; + int num_levels; + int ndomains; + int error; + int i; + + node = ofw_bus_get_node(sc->dev); + if (node <= 0) + return (ENXIO); + + OF_device_register_xref(OF_xref_from_node(node), sc->dev); + + sc->clkdom = clkdom_create(sc->dev); + if (sc->clkdom == NULL) + return (ENXIO); + + error = scmi_perf_get_ndomains(sc, &ndomains); + if (error) + return (error); + + sc->domain_count = ndomains; + sc->domains = malloc(sizeof(struct scmi_perf_domain) * ndomains, + M_DEVBUF, M_WAITOK); + + for (i = 0; i < ndomains; i++) { + domain = &sc->domains[i]; + domain->id = i; + + scmi_perf_domain_info(sc, i); + + error = scmi_perf_get_num_levels(sc, i, &num_levels); + if (error) + return (error); + + domain->level_count = num_levels; + domain->levels = malloc(sizeof(struct scmi_perf_level) * + num_levels, M_DEVBUF, M_WAITOK); + + scmi_perf_describe_levels(sc, i, num_levels); + } + + error = clkdom_finit(sc->clkdom); + if (error) { + device_printf(sc->dev, "Failed to init perf clock domain.\n"); + return (ENXIO); + } + + return (0); +} + +static int +scmi_perf_probe(device_t dev) +{ + phandle_t node; + uint32_t reg; + int error; + + node = ofw_bus_get_node(dev); + + error = OF_getencprop(node, "reg", ®, sizeof(uint32_t)); + if (error < 0) + return (ENXIO); + + if (reg != SCMI_PROTOCOL_ID_PERF) + return (ENXIO); + + device_set_desc(dev, "SCMI Performance Management Unit"); + + return (BUS_PROBE_DEFAULT); +} + +static int +scmi_perf_attach(device_t dev) +{ + struct scmi_perf_softc *sc; + phandle_t node; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->scmi = device_get_parent(dev); + + node = ofw_bus_get_node(sc->dev); + + OF_device_register_xref(OF_xref_from_node(node), sc->dev); + + scmi_perf_init(sc); + + return (0); +} + +static int +scmi_perf_detach(device_t dev) +{ + + return (0); +} + +static device_method_t scmi_perf_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, scmi_perf_probe), + DEVMETHOD(device_attach, scmi_perf_attach), + DEVMETHOD(device_detach, scmi_perf_detach), + { 0, 0 } +}; + +static driver_t scmi_perf_driver = { + "scmi_perf", + scmi_perf_methods, + sizeof(struct scmi_perf_softc), +}; + +EARLY_DRIVER_MODULE(scmi_perf, scmi, scmi_perf_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(scmi_perf, 1); Index: sys/arm64/scmi/scmi_protocols.h =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi_protocols.h @@ -0,0 +1,394 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (c) 2015-2019, Arm Limited and Contributors. All rights reserved. + * Copyright (C) 2019-2020, Linaro Limited + */ +#ifndef _SCMI_PROTOCOLS_H +#define _SCMI_PROTOCOLS_H + +/* + * Subset the SCMI protocols definition + * based on SCMI specification v2.0 (DEN0056B) + * https://developer.arm.com/docs/den0056/b + */ + +enum scmi_std_protocol { + SCMI_PROTOCOL_ID_BASE = 0x10, + SCMI_PROTOCOL_ID_POWER_DOMAIN = 0x11, + SCMI_PROTOCOL_ID_SYSTEM = 0x12, + SCMI_PROTOCOL_ID_PERF = 0x13, + SCMI_PROTOCOL_ID_CLOCK = 0x14, + SCMI_PROTOCOL_ID_SENSOR = 0x15, + SCMI_PROTOCOL_ID_RESET_DOMAIN = 0x16, + SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN = 0x17, +}; + +enum scmi_status_code { + SCMI_SUCCESS = 0, + SCMI_NOT_SUPPORTED = -1, + SCMI_INVALID_PARAMETERS = -2, + SCMI_DENIED = -3, + SCMI_NOT_FOUND = -4, + SCMI_OUT_OF_RANGE = -5, + SCMI_BUSY = -6, + SCMI_COMMS_ERROR = -7, + SCMI_GENERIC_ERROR = -8, + SCMI_HARDWARE_ERROR = -9, + SCMI_PROTOCOL_ERROR = -10, +}; + +/* + * SCMI Perf Protocol + */ + +#define SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES 0x3 +#define SCMI_PERFORMANCE_DESCRIBE_LEVELS 0x4 +#define SCMI_PERFORMANCE_LEVEL_SET 0x7 +#define SCMI_PERFORMANCE_LEVEL_GET 0x8 + +struct scmi_perf_level_get_in { + uint32_t domain_id; +}; + +struct scmi_perf_level_get_out { + int32_t status; + uint32_t performance_level; +}; + +struct scmi_perf_level_set_in { + uint32_t domain_id; + uint32_t performance_level; +}; + +struct scmi_perf_level_set_out { + int32_t status; +}; + +struct scmi_perf_describe_levels_in { + uint32_t domain_id; + uint32_t level_index; +}; + +struct scmi_perf_level_out { + uint32_t perf_level_value; + uint32_t power_cost; + uint32_t attributes; +}; + +struct scmi_perf_describe_levels_out { + int32_t status; + uint32_t num_levels; +#define NUM_LEVEL_REM_S 16 +#define NUM_LEVEL_REM_M (0xffff << NUM_LEVEL_REM_S) +#define NUM_LEVELS_S 0 +#define NUM_LEVELS_M (0xfff << NUM_LEVELS_S) + struct scmi_perf_level_out levels[]; +}; + +struct scmi_perf_protocol_attrs_out { + int32_t status; + uint32_t attributes; + uint32_t statistics_address_low; + uint32_t statistics_address_high; + uint32_t statistics_len; +}; + +struct scmi_perf_domain_attrs_in { + uint32_t domain_id; +}; + +struct scmi_perf_domain_attrs_out { + int32_t status; + uint32_t attributes; + uint32_t rate_limit; + uint32_t sustained_freq; + uint32_t sustained_perf_level; + uint8_t name[16]; +}; + +/* + * SCMI Clock Protocol + */ + +#define SCMI_PROTOCOL_ATTRIBUTES 0x1 + +struct scmi_clk_protocol_attrs_out { + int32_t status; + uint32_t attributes; +#define CLK_ATTRS_NCLOCKS_S 0 +#define CLK_ATTRS_NCLOCKS_M (0xffff << CLK_ATTRS_NCLOCKS_S) +}; + +struct scmi_clk_attrs_in { + uint32_t clock_id; +}; + +struct scmi_clk_attrs_out { + int32_t status; + uint32_t attributes; +#define CLK_ATTRS_RATE_CHANGE_NOTIFY_SUPP (1 << 31) +#define CLK_ATTRS_RATE_REQ_CHANGE_NOTIFY_SUPP (1 << 30) +#define CLK_ATTRS_EXT_CLK_NAME (1 << 29) +#define CLK_ATTRS_ENABLED (1 << 0) + uint8_t clock_name[16]; /* only if attrs bit 29 unset */ + uint32_t clock_enable_delay; /* worst case */ +}; + +struct scmi_clk_name_get_in { + uint32_t clock_id; +}; + +struct scmi_clk_name_get_out { + int32_t status; + uint32_t flags; + uint8_t name[64]; +}; + +enum scmi_clock_message_id { + SCMI_CLOCK_ATTRIBUTES = 0x3, + SCMI_CLOCK_RATE_SET = 0x5, + SCMI_CLOCK_RATE_GET = 0x6, + SCMI_CLOCK_CONFIG_SET = 0x7, + SCMI_CLOCK_NAME_GET = 0x8, +}; + +#define SCMI_CLK_RATE_ASYNC_NOTIFY (1 << 0) +#define SCMI_CLK_RATE_ASYNC_NORESP (1 << 0 | 1 << 1) +#define SCMI_CLK_RATE_ROUND_DOWN 0 +#define SCMI_CLK_RATE_ROUND_UP (1 << 2) +#define SCMI_CLK_RATE_ROUND_CLOSEST (1 << 3) + +/** + * struct scmi_clk_state_in - Message payload for CLOCK_CONFIG_SET command + * @clock_id: SCMI clock ID + * @attributes: Attributes of the targets clock state + */ +struct scmi_clk_state_in { + uint32_t clock_id; + uint32_t attributes; +}; + +/** + * struct scmi_clk_state_out - Response payload for CLOCK_CONFIG_SET command + * @status: SCMI command status + */ +struct scmi_clk_state_out { + int32_t status; +}; + +/** + * struct scmi_clk_state_in - Message payload for CLOCK_RATE_GET command + * @clock_id: SCMI clock ID + * @attributes: Attributes of the targets clock state + */ +struct scmi_clk_rate_get_in { + uint32_t clock_id; +}; + +/** + * struct scmi_clk_rate_get_out - Response payload for CLOCK_RATE_GET command + * @status: SCMI command status + * @rate_lsb: 32bit LSB of the clock rate in Hertz + * @rate_msb: 32bit MSB of the clock rate in Hertz + */ +struct scmi_clk_rate_get_out { + int32_t status; + uint32_t rate_lsb; + uint32_t rate_msb; +}; + +/** + * struct scmi_clk_state_in - Message payload for CLOCK_RATE_SET command + * @flags: Flags for the clock rate set request + * @clock_id: SCMI clock ID + * @rate_lsb: 32bit LSB of the clock rate in Hertz + * @rate_msb: 32bit MSB of the clock rate in Hertz + */ +struct scmi_clk_rate_set_in { + uint32_t flags; + uint32_t clock_id; + uint32_t rate_lsb; + uint32_t rate_msb; +}; + +/** + * struct scmi_clk_rate_set_out - Response payload for CLOCK_RATE_SET command + * @status: SCMI command status + */ +struct scmi_clk_rate_set_out { + int32_t status; +}; + +/* + * SCMI Reset Domain Protocol + */ + +enum scmi_reset_domain_message_id { + SCMI_RESET_DOMAIN_ATTRIBUTES = 0x3, + SCMI_RESET_DOMAIN_RESET = 0x4, +}; + +#define SCMI_RD_NAME_LEN 16 + +#define SCMI_RD_ATTRIBUTES_FLAG_ASYNC (1 << 31) +#define SCMI_RD_ATTRIBUTES_FLAG_NOTIF (1 << 30) + +#define SCMI_RD_RESET_FLAG_ASYNC (1 << 2) +#define SCMI_RD_RESET_FLAG_ASSERT (1 << 1) +#define SCMI_RD_RESET_FLAG_CYCLE (1 << 0) + +/** + * struct scmi_rd_attr_in - Payload for RESET_DOMAIN_ATTRIBUTES message + * @domain_id: SCMI reset domain ID + */ +struct scmi_rd_attr_in { + uint32_t domain_id; +}; + +/** + * struct scmi_rd_attr_out - Payload for RESET_DOMAIN_ATTRIBUTES response + * @status: SCMI command status + * @attributes: Retrieved attributes of the reset domain + * @latency: Reset cycle max lantency + * @name: Reset domain name + */ +struct scmi_rd_attr_out { + int32_t status; + uint32_t attributes; + uint32_t latency; + char name[SCMI_RD_NAME_LEN]; +}; + +/** + * struct scmi_rd_reset_in - Message payload for RESET command + * @domain_id: SCMI reset domain ID + * @flags: Flags for the reset request + * @reset_state: Reset target state + */ +struct scmi_rd_reset_in { + uint32_t domain_id; + uint32_t flags; + uint32_t reset_state; +}; + +/** + * struct scmi_rd_reset_out - Response payload for RESET command + * @status: SCMI command status + */ +struct scmi_rd_reset_out { + int32_t status; +}; + +/* + * SCMI Voltage Domain Protocol + */ + +enum scmi_voltage_domain_message_id { + SCMI_VOLTAGE_DOMAIN_ATTRIBUTES = 0x3, + SCMI_VOLTAGE_DOMAIN_CONFIG_SET = 0x5, + SCMI_VOLTAGE_DOMAIN_CONFIG_GET = 0x6, + SCMI_VOLTAGE_DOMAIN_LEVEL_SET = 0x7, + SCMI_VOLTAGE_DOMAIN_LEVEL_GET = 0x8, +}; + +#define SCMI_VOLTD_NAME_LEN 16 + +#define SCMI_VOLTD_CONFIG_MASK GENMASK(3, 0) +#define SCMI_VOLTD_CONFIG_OFF 0 +#define SCMI_VOLTD_CONFIG_ON 0x7 + +/** + * struct scmi_voltd_attr_in - Payload for VOLTAGE_DOMAIN_ATTRIBUTES message + * @domain_id: SCMI voltage domain ID + */ +struct scmi_voltd_attr_in { + uint32_t domain_id; +}; + +/** + * struct scmi_voltd_attr_out - Payload for VOLTAGE_DOMAIN_ATTRIBUTES response + * @status: SCMI command status + * @attributes: Retrieved attributes of the voltage domain + * @name: Voltage domain name + */ +struct scmi_voltd_attr_out { + int32_t status; + uint32_t attributes; + char name[SCMI_VOLTD_NAME_LEN]; +}; + +/** + * struct scmi_voltd_config_set_in - Message payload for VOLTAGE_CONFIG_SET cmd + * @domain_id: SCMI voltage domain ID + * @config: Configuration data of the voltage domain + */ +struct scmi_voltd_config_set_in { + uint32_t domain_id; + uint32_t config; +}; + +/** + * struct scmi_voltd_config_set_out - Response for VOLTAGE_CONFIG_SET command + * @status: SCMI command status + */ +struct scmi_voltd_config_set_out { + int32_t status; +}; + +/** + * struct scmi_voltd_config_get_in - Message payload for VOLTAGE_CONFIG_GET cmd + * @domain_id: SCMI voltage domain ID + */ +struct scmi_voltd_config_get_in { + uint32_t domain_id; +}; + +/** + * struct scmi_voltd_config_get_out - Response for VOLTAGE_CONFIG_GET command + * @status: SCMI command status + * @config: Configuration data of the voltage domain + */ +struct scmi_voltd_config_get_out { + int32_t status; + uint32_t config; +}; + +/** + * struct scmi_voltd_level_set_in - Message payload for VOLTAGE_LEVEL_SET cmd + * @domain_id: SCMI voltage domain ID + * @flags: Parameter flags for configuring target level + * @voltage_level: Target voltage level in microvolts (uV) + */ +struct scmi_voltd_level_set_in { + uint32_t domain_id; + uint32_t flags; + int32_t voltage_level; +}; + +/** + * struct scmi_voltd_level_set_out - Response for VOLTAGE_LEVEL_SET command + * @status: SCMI command status + */ +struct scmi_voltd_level_set_out { + int32_t status; +}; + +/** + * struct scmi_voltd_level_get_in - Message payload for VOLTAGE_LEVEL_GET cmd + * @domain_id: SCMI voltage domain ID + */ +struct scmi_voltd_level_get_in { + uint32_t domain_id; +}; + +/** + * struct scmi_voltd_level_get_out - Response for VOLTAGE_LEVEL_GET command + * @status: SCMI command status + * @voltage_level: Voltage level in microvolts (uV) + */ +struct scmi_voltd_level_get_out { + int32_t status; + int32_t voltage_level; +}; + +#endif /* _SCMI_PROTOCOLS_H */ Index: sys/arm64/scmi/scmi_shmem.c =================================================================== --- /dev/null +++ sys/arm64/scmi/scmi_shmem.c @@ -0,0 +1,145 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Ruslan Bukin + * + * This work was supported by Innovate UK project 105694, "Digital Security + * by Design (DSbD) Technology Platform Prototype". + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mmio_if.h" + +#include "scmi.h" + +struct shmem_softc { + device_t dev; + device_t parent; + int reg; +}; + +static int +shmem_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "arm,scmi-shmem")) + return (ENXIO); + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + device_set_desc(dev, "ARM SCMI Shared Memory driver"); + + return (BUS_PROBE_DEFAULT); +} + +static int +shmem_attach(device_t dev) +{ + struct shmem_softc *sc; + phandle_t node; + int reg; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->parent = device_get_parent(dev); + + node = ofw_bus_get_node(dev); + if (node == -1) + return (ENXIO); + + OF_getencprop(node, "reg", ®, sizeof(reg)); + + dprintf("%s: reg %x\n", __func__, reg); + + sc->reg = reg; + + OF_device_register_xref(OF_xref_from_node(node), dev); + + return (0); +} + +static int +shmem_detach(device_t dev) +{ + + return (0); +} + +void +scmi_shmem_read(device_t dev, bus_size_t offset, void *buf, bus_size_t len) +{ + struct shmem_softc *sc; + uint8_t *addr; + int i; + + sc = device_get_softc(dev); + + addr = (uint8_t *)buf; + + for (i = 0; i < len; i++) + addr[i] = MMIO_READ_1(sc->parent, sc->reg + offset + i); +} + +void +scmi_shmem_write(device_t dev, bus_size_t offset, void *buf, bus_size_t len) +{ + struct shmem_softc *sc; + uint8_t *addr; + int i; + + sc = device_get_softc(dev); + + addr = (uint8_t *)buf; + + for (i = 0; i < len; i++) + MMIO_WRITE_1(sc->parent, sc->reg + offset + i, addr[i]); +} + +static device_method_t shmem_methods[] = { + DEVMETHOD(device_probe, shmem_probe), + DEVMETHOD(device_attach, shmem_attach), + DEVMETHOD(device_detach, shmem_detach), + DEVMETHOD_END +}; + +DEFINE_CLASS_1(shmem, shmem_driver, shmem_methods, sizeof(struct shmem_softc), + simplebus_driver); + +EARLY_DRIVER_MODULE(shmem, mmio_sram, shmem_driver, 0, 0, + BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +MODULE_VERSION(scmi, 1); Index: sys/conf/files.arm64 =================================================================== --- sys/conf/files.arm64 +++ sys/conf/files.arm64 @@ -88,6 +88,15 @@ arm64/arm64/vfp.c standard arm64/arm64/vm_machdep.c standard +arm64/scmi/doorbell.c optional fdt +arm64/scmi/dt_cpufreq.c optional fdt +arm64/scmi/mmio.c optional fdt +arm64/scmi/mmio_if.m optional fdt +arm64/scmi/scmi.c optional fdt +arm64/scmi/scmi_clk.c optional fdt +arm64/scmi/scmi_perf.c optional fdt +arm64/scmi/scmi_shmem.c optional fdt + # Allwinner Video Controller dev/drm/allwinner/aw_de2.c optional drm fdt aw_de2_drm dev/drm/allwinner/aw_de2_drm.c optional drm fdt aw_de2_drm compile-with "${DRM_C}" Index: sys/dev/extres/clk/clk.h =================================================================== --- sys/dev/extres/clk/clk.h +++ sys/dev/extres/clk/clk.h @@ -112,6 +112,7 @@ device_t clknode_get_device(struct clknode *clk); struct clknode *clknode_find_by_name(const char *name); struct clknode *clknode_find_by_id(struct clkdom *clkdom, intptr_t id); +int clknode_list_freq(struct clknode *clknode, uint64_t *freq, int *freq_count); int clknode_get_freq(struct clknode *clknode, uint64_t *freq); int clknode_set_freq(struct clknode *clknode, uint64_t freq, int flags, int enablecnt); @@ -127,6 +128,7 @@ int clk_get_by_name(device_t dev, const char *name, clk_t *clk); int clk_get_by_id(device_t dev, struct clkdom *clkdom, intptr_t id, clk_t *clk); int clk_release(clk_t clk); +int clk_list_freq(clk_t clk, uint64_t *freq, int *freq_count); int clk_get_freq(clk_t clk, uint64_t *freq); int clk_set_freq(clk_t clk, uint64_t freq, int flags); int clk_test_freq(clk_t clk, uint64_t freq, int flags); Index: sys/dev/extres/clk/clk.c =================================================================== --- sys/dev/extres/clk/clk.c +++ sys/dev/extres/clk/clk.c @@ -65,6 +65,8 @@ /* Default clock methods. */ static int clknode_method_init(struct clknode *clk, device_t dev); +static int clknode_method_list_freq(struct clknode *clknode, uint64_t *freq, + int *count); static int clknode_method_recalc_freq(struct clknode *clk, uint64_t *freq); static int clknode_method_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, int flags, int *stop); @@ -76,6 +78,7 @@ */ static clknode_method_t clknode_methods[] = { CLKNODEMETHOD(clknode_init, clknode_method_init), + CLKNODEMETHOD(clknode_list_freq, clknode_method_list_freq), CLKNODEMETHOD(clknode_recalc_freq, clknode_method_recalc_freq), CLKNODEMETHOD(clknode_set_freq, clknode_method_set_freq), CLKNODEMETHOD(clknode_set_gate, clknode_method_set_gate), @@ -205,6 +208,13 @@ return (0); } +static int +clknode_method_list_freq(struct clknode *clknode, uint64_t *freq, int *count) +{ + + return (0); +} + static int clknode_method_recalc_freq(struct clknode *clknode, uint64_t *freq) { @@ -929,6 +939,20 @@ /* * Real consumers executive */ +int +clknode_list_freq(struct clknode *clknode, uint64_t *freq, int *freq_count) +{ + int rv; + + CLK_TOPO_ASSERT(); + + CLKNODE_XLOCK(clknode); + rv = CLKNODE_LIST_FREQ(clknode, freq, freq_count); + CLKNODE_UNLOCK(clknode); + + return (rv); +} + int clknode_get_freq(struct clknode *clknode, uint64_t *freq) { @@ -1186,6 +1210,20 @@ return (rv); } +int +clk_list_freq(clk_t clk, uint64_t *freq, int *freq_count) +{ + int rv; + struct clknode *clknode; + + clknode = clk->clknode; + + CLK_TOPO_XLOCK(); + rv = clknode_list_freq(clknode, freq, freq_count); + CLK_TOPO_UNLOCK(); + return (rv); +} + int clk_set_freq(clk_t clk, uint64_t freq, int flags) { Index: sys/dev/extres/clk/clknode_if.m =================================================================== --- sys/dev/extres/clk/clknode_if.m +++ sys/dev/extres/clk/clknode_if.m @@ -40,6 +40,15 @@ device_t dev; }; +# +# List all possible frequencies. +# +METHOD int list_freq { + struct clknode *clk; + uint64_t *freq; + int *freq_count; +}; + # # Recalculate frequency # req - in/out recalulated frequency