root/sys/arm/allwinner/aw_sid.c
/*-
 * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
 *
 * 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 ``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 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.
 */

/*
 * Allwinner secure ID controller
 */

#include <sys/cdefs.h>
#include <sys/endian.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <machine/bus.h>

#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>

#include <arm/allwinner/aw_sid.h>

#include "nvmem_if.h"

/* 
 * Starting at least from sun8iw6 (A83T) EFUSE starts at 0x200 
 * There is 3 registers in the low area to read/write protected EFUSE.
 */
#define SID_PRCTL               0x40
#define  SID_PRCTL_OFFSET_MASK  0xff
#define  SID_PRCTL_OFFSET(n)    (((n) & SID_PRCTL_OFFSET_MASK) << 16)
#define  SID_PRCTL_LOCK         (0xac << 8)
#define  SID_PRCTL_READ         (0x01 << 1)
#define  SID_PRCTL_WRITE        (0x01 << 0)
#define SID_PRKEY               0x50
#define SID_RDKEY               0x60

#define EFUSE_OFFSET            0x200
#define EFUSE_NAME_SIZE         32
#define EFUSE_DESC_SIZE         64

struct aw_sid_efuse {
        char                    name[EFUSE_NAME_SIZE];
        char                    desc[EFUSE_DESC_SIZE];
        bus_size_t              base;
        bus_size_t              offset;
        uint32_t                size;
        enum aw_sid_fuse_id     id;
        bool                    public;
};

static struct aw_sid_efuse a10_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .offset = 0x0,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
};

static struct aw_sid_efuse a64_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 8,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = true,
        },
};

static struct aw_sid_efuse a83t_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 8,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = true,
        },
};

static struct aw_sid_efuse h3_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 4,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = false,
        },
};

static struct aw_sid_efuse h5_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 4,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = true,
        },
};

static struct aw_sid_efuse h616_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 4,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = true,
        },
};

static struct aw_sid_efuse d1_efuses[] = {
        {
                .name = "rootkey",
                .desc = "Root Key or ChipID",
                .base = EFUSE_OFFSET,
                .offset = 0x00,
                .size = 16,
                .id = AW_SID_FUSE_ROOTKEY,
                .public = true,
        },
        {
                .name = "calibration",
                .desc = "Thermal Sensor Calibration Data",
                .base = EFUSE_OFFSET,
                .offset = 0x34,
                .size = 4,
                .id = AW_SID_FUSE_THSSENSOR,
                .public = true,
        },
};

struct aw_sid_conf {
        struct aw_sid_efuse     *efuses;
        size_t                  nfuses;
};

static const struct aw_sid_conf a10_conf = {
        .efuses = a10_efuses,
        .nfuses = nitems(a10_efuses),
};

static const struct aw_sid_conf a20_conf = {
        .efuses = a10_efuses,
        .nfuses = nitems(a10_efuses),
};

static const struct aw_sid_conf a64_conf = {
        .efuses = a64_efuses,
        .nfuses = nitems(a64_efuses),
};

static const struct aw_sid_conf a83t_conf = {
        .efuses = a83t_efuses,
        .nfuses = nitems(a83t_efuses),
};

static const struct aw_sid_conf h3_conf = {
        .efuses = h3_efuses,
        .nfuses = nitems(h3_efuses),
};

static const struct aw_sid_conf h5_conf = {
        .efuses = h5_efuses,
        .nfuses = nitems(h5_efuses),
};

static const struct aw_sid_conf h616_conf = {
        .efuses = h616_efuses,
        .nfuses = nitems(h616_efuses),
};

static const struct aw_sid_conf d1_conf = {
        .efuses = d1_efuses,
        .nfuses = nitems(d1_efuses),
};

static struct ofw_compat_data compat_data[] = {
        { "allwinner,sun4i-a10-sid",            (uintptr_t)&a10_conf},
        { "allwinner,sun7i-a20-sid",            (uintptr_t)&a20_conf},
        { "allwinner,sun50i-a64-sid",           (uintptr_t)&a64_conf},
        { "allwinner,sun8i-a83t-sid",           (uintptr_t)&a83t_conf},
        { "allwinner,sun8i-h3-sid",             (uintptr_t)&h3_conf},
        { "allwinner,sun50i-h5-sid",            (uintptr_t)&h5_conf},
        { "allwinner,sun50i-h616-sid",          (uintptr_t)&h616_conf},
        { "allwinner,sun20i-d1-sid",            (uintptr_t)&d1_conf},
        { NULL,                                 0 }
};

struct aw_sid_softc {
        device_t                sid_dev;
        struct resource         *res;
        struct aw_sid_conf      *sid_conf;
        struct mtx              prctl_mtx;
};

static struct aw_sid_softc *aw_sid_sc;

static struct resource_spec aw_sid_spec[] = {
        { SYS_RES_MEMORY,       0,      RF_ACTIVE },
        { -1, 0 }
};

#define RD1(sc, reg)            bus_read_1((sc)->res, (reg))
#define RD4(sc, reg)            bus_read_4((sc)->res, (reg))
#define WR4(sc, reg, val)       bus_write_4((sc)->res, (reg), (val))

static int aw_sid_sysctl(SYSCTL_HANDLER_ARGS);

static int
aw_sid_probe(device_t dev)
{
        if (!ofw_bus_status_okay(dev))
                return (ENXIO);

        if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
                return (ENXIO);

        device_set_desc(dev, "Allwinner Secure ID Controller");
        return (BUS_PROBE_DEFAULT);
}

static int
aw_sid_attach(device_t dev)
{
        struct aw_sid_softc *sc;
        phandle_t node;
        int i;

        node = ofw_bus_get_node(dev);
        sc = device_get_softc(dev);
        sc->sid_dev = dev;

        if (bus_alloc_resources(dev, aw_sid_spec, &sc->res) != 0) {
                device_printf(dev, "cannot allocate resources for device\n");
                return (ENXIO);
        }

        mtx_init(&sc->prctl_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
        sc->sid_conf = (struct aw_sid_conf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
        aw_sid_sc = sc;

        /* Register ourself so device can resolve who we are */
        OF_device_register_xref(OF_xref_from_node(node), dev);

        for (i = 0; i < sc->sid_conf->nfuses; i++) {
                SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
                    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
                    OID_AUTO, sc->sid_conf->efuses[i].name,
                    CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
                    dev, sc->sid_conf->efuses[i].id, aw_sid_sysctl,
                    "A", sc->sid_conf->efuses[i].desc);
        }
        return (0);
}

int
aw_sid_get_fuse(enum aw_sid_fuse_id id, uint8_t *out, uint32_t *size)
{
        struct aw_sid_softc *sc;
        uint32_t val;
        int i, j;

        sc = aw_sid_sc;
        if (sc == NULL)
                return (ENXIO);

        for (i = 0; i < sc->sid_conf->nfuses; i++)
                if (id == sc->sid_conf->efuses[i].id)
                        break;

        if (i == sc->sid_conf->nfuses)
                return (ENOENT);

        if (*size != sc->sid_conf->efuses[i].size) {
                *size = sc->sid_conf->efuses[i].size;
                return (ENOMEM);
        }

        if (out == NULL)
                return (ENOMEM);

        if (sc->sid_conf->efuses[i].public == false)
                mtx_lock(&sc->prctl_mtx);
        for (j = 0; j < sc->sid_conf->efuses[i].size; j += 4) {
                if (sc->sid_conf->efuses[i].public == false) {
                        val = SID_PRCTL_OFFSET(sc->sid_conf->efuses[i].offset + j) |
                                SID_PRCTL_LOCK |
                                SID_PRCTL_READ;
                        WR4(sc, SID_PRCTL, val);
                        /* Read bit will be cleared once read has concluded */
                        while (RD4(sc, SID_PRCTL) & SID_PRCTL_READ)
                                continue;
                        val = RD4(sc, SID_RDKEY);
                } else
                        val = RD4(sc, sc->sid_conf->efuses[i].base +
                            sc->sid_conf->efuses[i].offset + j);
                out[j] = val & 0xFF;
                if (j + 1 < *size)
                        out[j + 1] = (val & 0xFF00) >> 8;
                if (j + 2 < *size)
                        out[j + 2] = (val & 0xFF0000) >> 16;
                if (j + 3 < *size)
                        out[j + 3] = (val & 0xFF000000) >> 24;
        }
        if (sc->sid_conf->efuses[i].public == false)
                mtx_unlock(&sc->prctl_mtx);

        return (0);
}

static int
aw_sid_read(device_t dev, uint32_t offset, uint32_t size, uint8_t *buffer)
{
        struct aw_sid_softc *sc;
        enum aw_sid_fuse_id fuse_id = 0;
        int i;

        sc = device_get_softc(dev);

        for (i = 0; i < sc->sid_conf->nfuses; i++)
                if (offset == sc->sid_conf->efuses[i].offset) {
                        fuse_id = sc->sid_conf->efuses[i].id;
                        break;
                }

        if (fuse_id == 0)
                return (ENOENT);

        return (aw_sid_get_fuse(fuse_id, buffer, &size));
}

static int
aw_sid_sysctl(SYSCTL_HANDLER_ARGS)
{
        device_t dev = arg1;
        enum aw_sid_fuse_id fuse = arg2;
        uint8_t data[32];
        char out[128];
        uint32_t size;
        int ret, i;

        /* Get the size of the efuse data */
        size = 0;
        aw_sid_get_fuse(fuse, NULL, &size);
        /* We now have the real size */
        ret = aw_sid_get_fuse(fuse, data, &size);
        if (ret != 0) {
                device_printf(dev, "Cannot get fuse id %d: %d\n", fuse, ret);
                return (ENOENT);
        }

        for (i = 0; i < size; i++)
                snprintf(out + (i * 2), sizeof(out) - (i * 2),
                  "%.2x", data[i]);

        return sysctl_handle_string(oidp, out, sizeof(out), req);
}

static device_method_t aw_sid_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe,         aw_sid_probe),
        DEVMETHOD(device_attach,        aw_sid_attach),

        /* NVMEM interface */
        DEVMETHOD(nvmem_read,           aw_sid_read),
        DEVMETHOD_END
};

static driver_t aw_sid_driver = {
        "aw_sid",
        aw_sid_methods,
        sizeof(struct aw_sid_softc),
};

EARLY_DRIVER_MODULE(aw_sid, simplebus, aw_sid_driver, 0, 0,
    BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST);
MODULE_VERSION(aw_sid, 1);
SIMPLEBUS_PNP_INFO(compat_data);