blob: 7f2829157c08618a408cc51063087574111e5cf6 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <driver-info/driver-info.h>
#include <elf.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <zircon/driver/binding.h>
#include <zircon/types.h>
typedef Elf64_Ehdr elfhdr;
typedef Elf64_Phdr elfphdr;
typedef Elf64_Nhdr notehdr;
static zx_status_t find_note(const char* name, size_t nlen, uint32_t type,
void* data, size_t size,
zx_status_t (*func)(void* note, size_t sz,
void* cookie),
void* cookie) {
while (size >= sizeof(notehdr)) {
const notehdr* hdr = data;
uint32_t nsz = (hdr->n_namesz + 3) & (~3);
if (nsz > (size - sizeof(notehdr))) {
return ZX_ERR_INTERNAL;
}
size_t hsz = sizeof(notehdr) + nsz;
data += hsz;
size -= hsz;
uint32_t dsz = (hdr->n_descsz + 3) & (~3);
if (dsz > size) {
return ZX_ERR_INTERNAL;
}
if ((hdr->n_type == type) &&
(hdr->n_namesz == nlen) &&
(memcmp(name, hdr + 1, nlen) == 0)) {
return func(data - hsz, hdr->n_descsz + hsz, cookie);
}
data += dsz;
size -= dsz;
}
return ZX_ERR_NOT_FOUND;
}
static zx_status_t for_each_note(int fd, const char* name, uint32_t type,
void* data, size_t dsize,
zx_status_t (*func)(void* note,
size_t sz, void* cookie),
void* cookie) {
size_t nlen = strlen(name) + 1;
elfphdr ph[64];
elfhdr eh;
if (pread(fd, &eh, sizeof(eh), 0) != sizeof(eh)) {
printf("for_each_note: pread(eh) failed\n");
return ZX_ERR_IO;
}
if (memcmp(&eh, ELFMAG, 4) ||
(eh.e_ehsize != sizeof(elfhdr)) ||
(eh.e_phentsize != sizeof(elfphdr))) {
printf("for_each_note: bad elf magic\n");
return ZX_ERR_INTERNAL;
}
size_t sz = sizeof(elfphdr) * eh.e_phnum;
if (sz > sizeof(ph)) {
printf("for_each_note: too many phdrs\n");
return ZX_ERR_INTERNAL;
}
if ((pread(fd, ph, sz, eh.e_phoff) != (ssize_t)sz)) {
printf("for_each_note: pread(sz,eh.e_phoff) failed\n");
return ZX_ERR_IO;
}
for (int i = 0; i < eh.e_phnum; i++) {
if ((ph[i].p_type != PT_NOTE) ||
(ph[i].p_filesz > dsize)) {
continue;
}
if ((pread(fd, data, ph[i].p_filesz, ph[i].p_offset) != (ssize_t)ph[i].p_filesz)) {
printf("for_each_note: pread(ph[i]) failed\n");
return ZX_ERR_IO;
}
int r = find_note(name, nlen, type, data, ph[i].p_filesz, func, cookie);
if (r == ZX_OK) {
return r;
}
}
return ZX_ERR_NOT_FOUND;
}
typedef struct {
void* cookie;
void (*func)(zircon_driver_note_payload_t* note,
const zx_bind_inst_t* binding, void* cookie);
} context;
static zx_status_t callback(void* note, size_t sz, void* _ctx) {
context* ctx = _ctx;
zircon_driver_note_t* dn = note;
if (sz < sizeof(zircon_driver_note_t)) {
return ZX_ERR_INTERNAL;
}
size_t max = (sz - sizeof(zircon_driver_note_t)) / sizeof(zx_bind_inst_t);
if (dn->payload.bindcount > max) {
return ZX_ERR_INTERNAL;
}
const zx_bind_inst_t* binding = (const void*)(&dn->payload + 1);
ctx->func(&dn->payload, binding, ctx->cookie);
return ZX_OK;
}
zx_status_t di_read_driver_info(int fd, void *cookie,
void (*func)(
zircon_driver_note_payload_t* note,
const zx_bind_inst_t* binding,
void *cookie)) {
context ctx = {
.cookie = cookie,
.func = func,
};
uint8_t data[4096];
return for_each_note(fd, ZIRCON_NOTE_NAME, ZIRCON_NOTE_DRIVER,
data, sizeof(data), callback, &ctx);
}
const char* di_bind_param_name(uint32_t param_num) {
switch (param_num) {
case BIND_FLAGS: return "Flags";
case BIND_PROTOCOL: return "Protocol";
case BIND_AUTOBIND: return "Autobind";
case BIND_PCI_VID: return "PCI.VID";
case BIND_PCI_DID: return "PCI.DID";
case BIND_PCI_CLASS: return "PCI.Class";
case BIND_PCI_SUBCLASS: return "PCI.Subclass";
case BIND_PCI_INTERFACE: return "PCI.Interface";
case BIND_PCI_REVISION: return "PCI.Revision";
case BIND_PCI_BDF_ADDR: return "PCI.BDFAddr";
case BIND_USB_VID: return "USB.VID";
case BIND_USB_PID: return "USB.PID";
case BIND_USB_CLASS: return "USB.Class";
case BIND_USB_SUBCLASS: return "USB.Subclass";
case BIND_USB_PROTOCOL: return "USB.Protocol";
case BIND_PLATFORM_DEV_VID: return "PlatDev.VID";
case BIND_PLATFORM_DEV_PID: return "PlatDev.PID";
case BIND_PLATFORM_DEV_DID: return "PlatDev.DID";
case BIND_ACPI_HID_0_3: return "ACPI.HID[0-3]";
case BIND_ACPI_HID_4_7: return "ACPI.HID[4-7]";
case BIND_IHDA_CODEC_VID: return "IHDA.Codec.VID";
case BIND_IHDA_CODEC_DID: return "IHDA.Codec.DID";
case BIND_IHDA_CODEC_MAJOR_REV: return "IHDACodec.MajorRev";
case BIND_IHDA_CODEC_MINOR_REV: return "IHDACodec.MinorRev";
case BIND_IHDA_CODEC_VENDOR_REV: return "IHDACodec.VendorRev";
case BIND_IHDA_CODEC_VENDOR_STEP: return "IHDACodec.VendorStep";
default: return NULL;
}
}
void di_dump_bind_inst(const zx_bind_inst_t* b, char* buf, size_t buf_len) {
if (!b || !buf || !buf_len) {
return;
}
uint32_t cc = BINDINST_CC(b->op);
uint32_t op = BINDINST_OP(b->op);
uint32_t pa = BINDINST_PA(b->op);
uint32_t pb = BINDINST_PB(b->op);
size_t off = 0;
buf[0] = 0;
switch (op) {
case OP_ABORT:
case OP_MATCH:
case OP_GOTO:
case OP_SET:
case OP_CLEAR:
break;
case OP_LABEL:
snprintf(buf + off, buf_len - off, "L.%u:", pa);
return;
default:
snprintf(buf + off, buf_len - off,
"Unknown Op 0x%1x [0x%08x, 0x%08x]", op, b->op, b->arg);
return;
}
off += snprintf(buf + off, buf_len - off, "if (");
if (cc == COND_AL) {
off += snprintf(buf + off, buf_len - off, "true");
} else {
const char* pb_name = di_bind_param_name(pb);
if (pb_name) {
off += snprintf(buf + off, buf_len - off, "%s", pb_name);
} else {
off += snprintf(buf + off, buf_len - off, "P.%04x", pb);
}
switch (cc) {
case COND_EQ:
off += snprintf(buf + off, buf_len - off, " == 0x%08x", b->arg);
break;
case COND_NE:
off += snprintf(buf + off, buf_len - off, " != 0x%08x", b->arg);
break;
case COND_GT:
off += snprintf(buf + off, buf_len - off, " > 0x%08x", b->arg);
break;
case COND_LT:
off += snprintf(buf + off, buf_len - off, " < 0x%08x", b->arg);
break;
case COND_GE:
off += snprintf(buf + off, buf_len - off, " >= 0x%08x", b->arg);
break;
case COND_LE:
off += snprintf(buf + off, buf_len - off, " <= 0x%08x", b->arg);
break;
case COND_MASK:
off += snprintf(buf + off, buf_len - off, " & 0x%08x != 0", b->arg);
break;
case COND_BITS:
off += snprintf(buf + off, buf_len - off, " & 0x%08x == 0x%08x", b->arg, b->arg);
break;
default:
off += snprintf(buf + off, buf_len - off,
" ?(0x%x) 0x%08x", cc, b->arg);
break;
}
}
off += snprintf(buf + off, buf_len - off, ") ");
switch (op) {
case OP_ABORT:
off += snprintf(buf + off, buf_len - off, "return no-match;");
break;
case OP_MATCH:
off += snprintf(buf + off, buf_len - off, "return match;");
break;
case OP_GOTO:
off += snprintf(buf + off, buf_len - off, "goto L.%u;", b->arg);
break;
case OP_SET:
off += snprintf(buf + off, buf_len - off, "flags |= 0x%02x;", pa);
break;
case OP_CLEAR:
off += snprintf(buf + off, buf_len - off, "flags &= 0x%02x;", ~pa & 0xFF);
break;
}
}