root/sbin/efi/devpath2.c
/* $NetBSD: devpath2.c,v 1.7 2025/12/15 17:06:42 joe Exp $ */

/*
 * 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 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 <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: devpath2.c,v 1.7 2025/12/15 17:06:42 joe Exp $");
#endif /* not lint */

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <util.h>

#include "defs.h"
#include "devpath.h"
#include "devpath2.h"
#include "utils.h"

#define easprintf       (size_t)easprintf

/************************************************************************
 * Type 2 - ACPI Device Path
 ************************************************************************/

/*
 * See 19.3.4 of ACPI Specification, Release 6.5 for compressed EISAID
 * algorithm.
 */

#define PNP0A03         0x0a0341d0
#define PNP0A08         0x0a0841d0

static const char *
eisaid_to_str(uint32_t eisaid)
{
        static const char hexdigits[] = "0123456789ABCDEF";
        static char text[8];
        union {
                uint32_t eisaid;
                uint8_t b[4];
        } u;

        u.eisaid = eisaid;

        text[0]  = (char)__SHIFTOUT(u.b[0], __BITS(4,0));
        text[1]  = (char)__SHIFTOUT(u.b[0], __BITS(7,5));
        text[1] |= (char)__SHIFTOUT(u.b[1], __BITS(1,0)) << 3;
        text[2]  = (char)__SHIFTOUT(u.b[1], __BITS(7,2));

        text[0] += 0x40;        /* '@' */
        text[1] += 0x40;
        text[2] += 0x40;

#define hi_nib(v)       (((v) >> 4) & 0x0f)
#define lo_nib(v)       (((v) >> 0) & 0x0f)
        text[3] = hexdigits[hi_nib(u.b[3])];
        text[4] = hexdigits[lo_nib(u.b[3])];
        text[5] = hexdigits[hi_nib(u.b[2])];
        text[6] = hexdigits[lo_nib(u.b[2])];
        text[7] = 0;
#undef lo_nib
#undef hi_nib

        return text;
}

static inline const char *
devpath_acpi_acpi_eisaid(uint32_t eisaid)
{

#define PNP(v)  (0x000041d0 | ((v) << 16))
        switch (eisaid) {
//      case PNP(0x0f03):       return "PS2Mouse";      /* legacy? */
//      case PNP(0x0303):       return "FloppyPNP";     /* legacy? */
        case PNP(0x0a03):       return "PciRoot";
        case PNP(0x0a08):       return "PcieRoot";
        case PNP(0x0604):       return "Floppy";
        case PNP(0x0301):       return "Keyboard";
        case PNP(0x0501):       return "Serial";
        case PNP(0x0401):       return "ParallelPort";
        default:                return NULL;
        }
#undef PNP
}

static inline void
devpath_acpi_acpi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.3 */
        struct {                        /* Sub-Type 1 */
                devpath_t       hdr;    /* Length = 12 */
                uint32_t        _HID;
                uint32_t        _UID;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 12);
        const char *str;

        str = devpath_acpi_acpi_eisaid(p->_HID);
        if (str == NULL) {
                path->sz = easprintf(&path->cp, "ACPI(%s,0x%x)",
                    eisaid_to_str(p->_HID), p->_UID);
        } else {
                path->sz = easprintf(&path->cp, "%s(0x%x)", str, p->_UID);
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(_HID: 0x%08x(%s)\n)
                    DEVPATH_FMT(_UID: 0x%08x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->_HID,
                    eisaid_to_str(p->_HID),
                    p->_UID);
        }
}

static inline void
devpath_acpi_acpiex(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.3 */
        struct {                        /* Sub-Type 2 */
                devpath_t       hdr;    /* Length = 19 + n */
                uint32_t        _HID;
                uint32_t        _UID;
                uint32_t        _CID;
                char            _HIDSTR[];      /* at least NUL byte */
//              char            _UIDSTR[];      /* at least NUL byte */
//              char            _CIDSTR[];      /* at least NUL byte */
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 16);
        char *hidstr = p->_HIDSTR;
        char *uidstr = hidstr + strlen(hidstr) + 1;
        char *cidstr = uidstr + strlen(uidstr) + 1;

#if 0
        There are 4 different subsubtypes depending on the ?ID, ?IDSTR values.

        "AcpiEx"
        "AcpiExp"
        "PciRoot"
        "PcipRoot"
#endif
        if (p->_HID == PNP0A08 || p->_CID == PNP0A08) {
                // PcieRoot(UID|UIDSTR)
                path->sz = easprintf(&path->cp, "PcieRoot(%s)",
                    *uidstr != '\0' ? uidstr : eisaid_to_str(p->_UID));
        }
        else if (p->_HID == PNP0A03 || p->_CID == PNP0A03) {
                assert(p->_HID != PNP0A08);
                // PciRoot(UID|UIDSTR)
                path->sz = easprintf(&path->cp, "PciRoot(%s)",
                    *uidstr != '\0' ? uidstr : eisaid_to_str(p->_UID));
        }
        else if (hidstr[0] == '\0' && cidstr[0] == '\0' && uidstr[0] != '\0') {
                assert(p->_HID != 0);
                path->sz = easprintf(&path->cp, "AcpiExp(%s,%s,%s)",
                    eisaid_to_str(p->_HID),
                    eisaid_to_str(p->_CID),
                    uidstr);
        }
        else {
                path->sz = easprintf(&path->cp, "ACPIEX(%s,%s,0x%x,%s,%s,%s)",
                    eisaid_to_str(p->_HID), eisaid_to_str(p->_CID),
                    p->_UID, hidstr, cidstr, uidstr);
        }

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(_HID: 0x%08x(%s)\n)
                    DEVPATH_FMT(_UID: 0x%08x\n)
                    DEVPATH_FMT(_CID: 0x%08x(%s)\n)
                    DEVPATH_FMT(_HIDSTR: %s\n)
                    DEVPATH_FMT(_UIDSTR: %s\n)
                    DEVPATH_FMT(_CIDSTR: %s\n),
                    DEVPATH_DAT_HDR(dp),
                    p->_HID,
                    eisaid_to_str(p->_HID),
                    p->_UID,
                    p->_CID,
                    eisaid_to_str(p->_CID),
                    hidstr,
                    uidstr,
                    cidstr);
        }
}

static inline void
devpath_acpi_adr(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.3.1 */
        struct {                /* Sub-Type 3 */
                devpath_t       hdr;    /* Length = 8; at least one _ADR */
                uint32_t        _ADR[];
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 4);
        char *tp;
        size_t cnt;

        cnt = (p->hdr.Length - sizeof(p->hdr)) / sizeof(p->_ADR[0]);

        tp = estrdup("AcpiAdr(");
        for (size_t i = 0; i < cnt; i++) {
                path->sz = easprintf(&path->cp, "%s0x%08x%s", tp, p->_ADR[i],
                    i + 1 < cnt ? "," : "");
                free(tp);
                tp = path->cp;
        }
        path->sz = easprintf(&path->cp, "%s)", tp);
        free(tp);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR,
                    DEVPATH_DAT_HDR(dp));
                tp = dbg->cp;
                for (size_t i = 0; i < cnt; i++) {
                        dbg->sz = easprintf(&dbg->cp, "%s"
                            DEVPATH_FMT(_ADR[%zu]: 0x%08x\n),
                            tp, i, p->_ADR[i]);
                        free(tp);
                        tp = dbg->cp;
                }
        }
}

static inline void
devpath_acpi_nvdimm(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{       /* See 10.3.3.2 */
        struct {                /* Sub-Type 4 */
                devpath_t       hdr;    /* Length = 8 */
                uint32_t        NFIT_DevHdl;
        } __packed *p = (void *)dp;
        __CTASSERT(sizeof(*p) == 8);

        path->sz = easprintf(&path->cp, "NvdimmAcpiAdr(0x%x)", p->NFIT_DevHdl);

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR
                    DEVPATH_FMT(NFIT_DevHdl: 0x%08x\n),
                    DEVPATH_DAT_HDR(dp),
                    p->NFIT_DevHdl);
        }
}

#ifdef notdef
static inline void
devpath_acpi_unknown(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{

        path->sz = easprintf(&path->cp, "Msg(%d,%s)", dp->SubType,
            encode_data((uint8_t *)(dp + 1), dp->Length - sizeof(*dp)));

        if (dbg != NULL) {
                dbg->sz = easprintf(&dbg->cp,
                    DEVPATH_FMT_HDR,
                    DEVPATH_DAT_HDR(dp));
        }
}
#endif

PUBLIC void
devpath_acpi(devpath_t *dp, devpath_elm_t *path, devpath_elm_t *dbg)
{

        assert(dp->Type == 2);

        switch (dp->SubType) {
        case 1:   devpath_acpi_acpi(dp, path, dbg);     return;
        case 2:   devpath_acpi_acpiex(dp, path, dbg);   return;
        case 3:   devpath_acpi_adr(dp, path, dbg);      return;
        case 4:   devpath_acpi_nvdimm(dp, path, dbg);   return;
        default:  devpath_unsupported(dp, path, dbg);   return;
        }
}