diff --git a/share/man/man4/efidev.4 b/share/man/man4/efidev.4 --- a/share/man/man4/efidev.4 +++ b/share/man/man4/efidev.4 @@ -26,7 +26,7 @@ .\" .\" $FreeBSD$ .\" -.Dd August 12, 2018 +.Dd June 18, 2021 .Dt EFIDEV 4 .Os .Sh NAME @@ -71,11 +71,28 @@ .In sys/efi.h : .Bl -tag -width indent .It Dv EFIIOC_GET_TABLE Pq Vt "struct efi_get_table_ioc" -Get a table by uuid from the UEFI system table. +Copy the UEFI table specified by the +.Va uuid +field of the +.Vt struct efi_get_table_ioc +into the +.Va buf +field. +The memory size for the buf field can be queried by passing +.Dv NULL +pointer as a buf value. +The required size will be stored in the +.Va table_len +field. +The size of the allocated memory must be specified in the +.Va buf_len +field. .Bd -literal -offset indent struct efi_get_table_ioc { + void *buf; struct uuid uuid; - void *ptr; + size_t table_len; + size_t buf_len; }; .Ed .It Dv EFIIOC_GET_TIME Pq Vt "struct efi_tm" diff --git a/sys/dev/efidev/efidev.c b/sys/dev/efidev/efidev.c --- a/sys/dev/efidev/efidev.c +++ b/sys/dev/efidev/efidev.c @@ -53,6 +53,29 @@ int error; switch (cmd) { + case EFIIOC_GET_TABLE: + { + struct efi_get_table_ioc *egtioc = + (struct efi_get_table_ioc *)addr; + void *buf = NULL; + + error = efi_copy_table(&egtioc->uuid, egtioc->buf ? &buf : NULL, + egtioc->buf_len, &egtioc->table_len); + + if (error != 0 || egtioc->buf == NULL) + break; + + if (egtioc->buf_len < egtioc->table_len) { + error = EINVAL; + free(buf, M_TEMP); + break; + } + + error = copyout(buf, egtioc->buf, egtioc->buf_len); + free(buf, M_TEMP); + + break; + } case EFIIOC_GET_TIME: { struct efi_tm *tm = (struct efi_tm *)addr; diff --git a/sys/dev/efidev/efirt.c b/sys/dev/efidev/efirt.c --- a/sys/dev/efidev/efirt.c +++ b/sys/dev/efidev/efirt.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include @@ -58,6 +60,8 @@ #include #include +#define EFI_TABLE_ALLOC_MAX 0x800000 + static struct efi_systbl *efi_systbl; static eventhandler_tag efi_shutdown_tag; /* @@ -96,6 +100,11 @@ EPROTO /* EFI_PROTOCOL_ERROR */ }; +enum efi_table_type { + TYPE_ESRT = 0, + TYPE_PROP +}; + static int efi_enter(void); static void efi_leave(void); @@ -336,6 +345,124 @@ return (ENOENT); } +static int +get_table_length(enum efi_table_type type, size_t *table_len, void **taddr) +{ + switch (type) { + case TYPE_ESRT: + { + struct efi_esrt_table *esrt = NULL; + struct uuid uuid = EFI_TABLE_ESRT; + uint32_t fw_resource_count = 0; + size_t len = sizeof(*esrt); + int error; + void *buf; + + error = efi_get_table(&uuid, (void **)&esrt); + if (error != 0) + return (error); + + buf = malloc(len, M_TEMP, M_WAITOK); + error = physcopyout((vm_paddr_t)esrt, buf, len); + if (error != 0) { + free(buf, M_TEMP); + return (error); + } + + /* Check ESRT version */ + if (((struct efi_esrt_table *)buf)->fw_resource_version != + ESRT_FIRMWARE_RESOURCE_VERSION) { + free(buf, M_TEMP); + return (ENODEV); + } + + fw_resource_count = ((struct efi_esrt_table *)buf)-> + fw_resource_count; + if (fw_resource_count > EFI_TABLE_ALLOC_MAX / + sizeof(struct efi_esrt_entry_v1)) { + free(buf, M_TEMP); + return (ENOMEM); + } + + len += fw_resource_count * sizeof(struct efi_esrt_entry_v1); + *table_len = len; + + if (taddr != NULL) + *taddr = esrt; + free(buf, M_TEMP); + return (0); + } + case TYPE_PROP: + { + struct uuid uuid = EFI_PROPERTIES_TABLE; + struct efi_prop_table *prop; + size_t len = sizeof(*prop); + uint32_t prop_len; + int error; + void *buf; + + error = efi_get_table(&uuid, (void **)&prop); + if (error != 0) + return (error); + + buf = malloc(len, M_TEMP, M_WAITOK); + error = physcopyout((vm_paddr_t)prop, buf, len); + if (error != 0) { + free(buf, M_TEMP); + return (error); + } + + prop_len = ((struct efi_prop_table *)buf)->length; + if (prop_len > EFI_TABLE_ALLOC_MAX) { + free(buf, M_TEMP); + return (ENOMEM); + } + *table_len = prop_len; + + if (taddr != NULL) + *taddr = prop; + free(buf, M_TEMP); + return (0); + } + } + return (ENOENT); +} + +static int +copy_table(struct uuid *uuid, void **buf, size_t buf_len, size_t *table_len) +{ + static const struct known_table { + struct uuid uuid; + enum efi_table_type type; + } tables[] = { + { EFI_TABLE_ESRT, TYPE_ESRT }, + { EFI_PROPERTIES_TABLE, TYPE_PROP } + }; + size_t table_idx; + void *taddr; + int rc; + + for (table_idx = 0; table_idx < nitems(tables); table_idx++) { + if (!bcmp(&tables[table_idx].uuid, uuid, sizeof(*uuid))) + break; + } + + if (table_idx == nitems(tables)) + return (EINVAL); + + rc = get_table_length(tables[table_idx].type, table_len, &taddr); + if (rc != 0) + return rc; + + /* return table length to userspace */ + if (buf == NULL) + return (0); + + *buf = malloc(*table_len, M_TEMP, M_WAITOK); + rc = physcopyout((vm_paddr_t)taddr, *buf, *table_len); + return (rc); +} + static int efi_rt_handle_faults = EFI_RT_HANDLE_FAULTS_DEFAULT; SYSCTL_INT(_machdep, OID_AUTO, efi_rt_handle_faults, CTLFLAG_RWTUN, &efi_rt_handle_faults, 0, @@ -568,6 +695,7 @@ const static struct efi_ops efi_ops = { .rt_ok = rt_ok, .get_table = get_table, + .copy_table = copy_table, .get_time = get_time, .get_time_capabilities = get_time_capabilities, .reset_system = reset_system, diff --git a/sys/sys/efi.h b/sys/sys/efi.h --- a/sys/sys/efi.h +++ b/sys/sys/efi.h @@ -40,6 +40,10 @@ {0xeb9d2d31,0x2d88,0x11d3,0x9a,0x16,{0x00,0x90,0x27,0x3f,0xc1,0x4d}} #define EFI_TABLE_SMBIOS3 \ {0xf2fd1544,0x9794,0x4a2c,0x99,0x2e,{0xe5,0xbb,0xcf,0x20,0xe3,0x94}} +#define EFI_TABLE_ESRT \ + {0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}} +#define EFI_PROPERTIES_TABLE \ + {0x880aaca3,0x4adc,0x4a04,0x90,0x79,{0xb7,0x47,0x34,0x08,0x25,0xe5}} enum efi_reset { EFI_RESET_COLD = 0, @@ -123,6 +127,31 @@ uint32_t __res; }; +#define ESRT_FIRMWARE_RESOURCE_VERSION 1 + +struct efi_esrt_table { + uint32_t fw_resource_count; + uint32_t fw_resource_count_max; + uint64_t fw_resource_version; + uint8_t entries[]; +}; + +struct efi_esrt_entry_v1 { + struct uuid fw_class; + uint32_t fw_type; + uint32_t fw_version; + uint32_t lowest_supported_fw_version; + uint32_t capsule_flags; + uint32_t last_attempt_version; + uint32_t last_attempt_status; +}; + +struct efi_prop_table { + uint32_t version; + uint32_t length; + uint64_t memory_protection_attribute; +}; + #ifdef _KERNEL #ifdef EFIABI_ATTR @@ -188,6 +217,7 @@ */ int (*rt_ok)(void); int (*get_table)(struct uuid *, void **); + int (*copy_table)(struct uuid *, void **, size_t, size_t *); int (*get_time)(struct efi_tm *); int (*get_time_capabilities)(struct efi_tmcap *); int (*reset_system)(enum efi_reset); @@ -216,6 +246,15 @@ return (active_efi_ops->get_table(uuid, ptr)); } +static inline int efi_copy_table(struct uuid *uuid, void **buf, + size_t buf_len, size_t *table_len) +{ + + if (active_efi_ops->copy_table == NULL) + return (ENXIO); + return (active_efi_ops->copy_table(uuid, buf, buf_len, table_len)); +} + static inline int efi_get_time(struct efi_tm *tm) { diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h --- a/sys/sys/efiio.h +++ b/sys/sys/efiio.h @@ -32,6 +32,14 @@ #include #include +struct efi_get_table_ioc +{ + void *buf; /* Pointer to userspace buffer */ + struct uuid uuid; /* UUID to look up */ + size_t table_len; /* Table size */ + size_t buf_len; /* Size of the buffer */ +}; + struct efi_var_ioc { efi_char *name; /* User pointer to name, in wide chars */ @@ -42,6 +50,7 @@ size_t datasize; /* Number of *bytes* in the data */ }; +#define EFIIOC_GET_TABLE _IOWR('E', 1, struct efi_get_table_ioc) #define EFIIOC_GET_TIME _IOR('E', 2, struct efi_tm) #define EFIIOC_SET_TIME _IOW('E', 3, struct efi_tm) #define EFIIOC_VAR_GET _IOWR('E', 4, struct efi_var_ioc)