esrt.c 10.6 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
/*
 * esrt.c
 *
 * This module exports EFI System Resource Table (ESRT) entries into userspace
 * through the sysfs file system. The ESRT provides a read-only catalog of
 * system components for which the system accepts firmware upgrades via UEFI's
 * "Capsule Update" feature. This module allows userland utilities to evaluate
 * what firmware updates can be applied to this system, and potentially arrange
 * for those updates to occur.
 *
 * Data is currently found below /sys/firmware/efi/esrt/...
 */
#define pr_fmt(fmt) "esrt: " fmt

#include <linux/capability.h>
#include <linux/device.h>
#include <linux/efi.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/list.h>
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/types.h>

#include <asm/io.h>
#include <asm/early_ioremap.h>

struct efi_system_resource_entry_v1 {
	efi_guid_t	fw_class;
	u32		fw_type;
	u32		fw_version;
	u32		lowest_supported_fw_version;
	u32		capsule_flags;
	u32		last_attempt_version;
	u32		last_attempt_status;
};

/*
 * _count and _version are what they seem like.  _max is actually just
 * accounting info for the firmware when creating the table; it should never
 * have been exposed to us.  To wit, the spec says:
 * The maximum number of resource array entries that can be within the
 * table without reallocating the table, must not be zero.
 * Since there's no guidance about what that means in terms of memory layout,
 * it means nothing to us.
 */
struct efi_system_resource_table {
	u32	fw_resource_count;
	u32	fw_resource_count_max;
	u64	fw_resource_version;
	u8	entries[];
};

static phys_addr_t esrt_data;
static size_t esrt_data_size;

static struct efi_system_resource_table *esrt;

struct esre_entry {
	union {
		struct efi_system_resource_entry_v1 *esre1;
	} esre;

	struct kobject kobj;
	struct list_head list;
};

/* global list of esre_entry. */
static LIST_HEAD(entry_list);

/* entry attribute */
struct esre_attribute {
	struct attribute attr;
	ssize_t (*show)(struct esre_entry *entry, char *buf);
	ssize_t (*store)(struct esre_entry *entry,
			 const char *buf, size_t count);
};

static struct esre_entry *to_entry(struct kobject *kobj)
{
	return container_of(kobj, struct esre_entry, kobj);
}

static struct esre_attribute *to_attr(struct attribute *attr)
{
	return container_of(attr, struct esre_attribute, attr);
}

static ssize_t esre_attr_show(struct kobject *kobj,
			      struct attribute *_attr, char *buf)
{
	struct esre_entry *entry = to_entry(kobj);
	struct esre_attribute *attr = to_attr(_attr);

	/* Don't tell normal users what firmware versions we've got... */
	if (!capable(CAP_SYS_ADMIN))
		return -EACCES;

	return attr->show(entry, buf);
}

static const struct sysfs_ops esre_attr_ops = {
	.show = esre_attr_show,
};

/* Generic ESRT Entry ("ESRE") support. */
static ssize_t fw_class_show(struct esre_entry *entry, char *buf)
{
	char *str = buf;

	efi_guid_to_str(&entry->esre.esre1->fw_class, str);
	str += strlen(str);
	str += sprintf(str, "\n");

	return str - buf;
}

static struct esre_attribute esre_fw_class = __ATTR_RO_MODE(fw_class, 0400);

#define esre_attr_decl(name, size, fmt) \
static ssize_t name##_show(struct esre_entry *entry, char *buf) \
{ \
	return sprintf(buf, fmt "\n", \
		       le##size##_to_cpu(entry->esre.esre1->name)); \
} \
\
static struct esre_attribute esre_##name = __ATTR_RO_MODE(name, 0400)

esre_attr_decl(fw_type, 32, "%u");
esre_attr_decl(fw_version, 32, "%u");
esre_attr_decl(lowest_supported_fw_version, 32, "%u");
esre_attr_decl(capsule_flags, 32, "0x%x");
esre_attr_decl(last_attempt_version, 32, "%u");
esre_attr_decl(last_attempt_status, 32, "%u");

static struct attribute *esre1_attrs[] = {
	&esre_fw_class.attr,
	&esre_fw_type.attr,
	&esre_fw_version.attr,
	&esre_lowest_supported_fw_version.attr,
	&esre_capsule_flags.attr,
	&esre_last_attempt_version.attr,
	&esre_last_attempt_status.attr,
	NULL
};
static void esre_release(struct kobject *kobj)
{
	struct esre_entry *entry = to_entry(kobj);

	list_del(&entry->list);
	kfree(entry);
}

static struct kobj_type esre1_ktype = {
	.release = esre_release,
	.sysfs_ops = &esre_attr_ops,
	.default_attrs = esre1_attrs,
};


static struct kobject *esrt_kobj;
static struct kset *esrt_kset;

static int esre_create_sysfs_entry(void *esre, int entry_num)
{
	struct esre_entry *entry;

	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
	if (!entry)
		return -ENOMEM;

	entry->kobj.kset = esrt_kset;

	if (esrt->fw_resource_version == 1) {
		int rc = 0;

		entry->esre.esre1 = esre;
		rc = kobject_init_and_add(&entry->kobj, &esre1_ktype, NULL,
					  "entry%d", entry_num);
		if (rc) {
			kfree(entry);
			return rc;
		}
	}

	list_add_tail(&entry->list, &entry_list);
	return 0;
}

/* support for displaying ESRT fields at the top level */
#define esrt_attr_decl(name, size, fmt) \
static ssize_t name##_show(struct kobject *kobj, \
				  struct kobj_attribute *attr, char *buf)\
{ \
	return sprintf(buf, fmt "\n", le##size##_to_cpu(esrt->name)); \
} \
\
static struct kobj_attribute esrt_##name = __ATTR_RO_MODE(name, 0400)

esrt_attr_decl(fw_resource_count, 32, "%u");
esrt_attr_decl(fw_resource_count_max, 32, "%u");
esrt_attr_decl(fw_resource_version, 64, "%llu");

static struct attribute *esrt_attrs[] = {
	&esrt_fw_resource_count.attr,
	&esrt_fw_resource_count_max.attr,
	&esrt_fw_resource_version.attr,
	NULL,
};

static inline int esrt_table_exists(void)
{
	if (!efi_enabled(EFI_CONFIG_TABLES))
		return 0;
	if (efi.esrt == EFI_INVALID_TABLE_ADDR)
		return 0;
	return 1;
}

static umode_t esrt_attr_is_visible(struct kobject *kobj,
				    struct attribute *attr, int n)
{
	if (!esrt_table_exists())
		return 0;
	return attr->mode;
}

static const struct attribute_group esrt_attr_group = {
	.attrs = esrt_attrs,
	.is_visible = esrt_attr_is_visible,
};

/*
 * remap the table, validate it, mark it reserved and unmap it.
 */
void __init efi_esrt_init(void)
{
	void *va;
	struct efi_system_resource_table tmpesrt;
	struct efi_system_resource_entry_v1 *v1_entries;
	size_t size, max, entry_size, entries_size;
	efi_memory_desc_t md;
	int rc;
	phys_addr_t end;

	pr_debug("esrt-init: loading.\n");
	if (!esrt_table_exists())
		return;

	rc = efi_mem_desc_lookup(efi.esrt, &md);
	if (rc < 0) {
		pr_warn("ESRT header is not in the memory map.\n");
		return;
	}

	max = efi_mem_desc_end(&md);
	if (max < efi.esrt) {
		pr_err("EFI memory descriptor is invalid. (esrt: %p max: %p)\n",
		       (void *)efi.esrt, (void *)max);
		return;
	}

	size = sizeof(*esrt);
	max -= efi.esrt;

	if (max < size) {
		pr_err("ESRT header doesn't fit on single memory map entry. (size: %zu max: %zu)\n",
		       size, max);
		return;
	}

	va = early_memremap(efi.esrt, size);
	if (!va) {
		pr_err("early_memremap(%p, %zu) failed.\n", (void *)efi.esrt,
		       size);
		return;
	}

	memcpy(&tmpesrt, va, sizeof(tmpesrt));

	if (tmpesrt.fw_resource_version == 1) {
		entry_size = sizeof (*v1_entries);
	} else {
		pr_err("Unsupported ESRT version %lld.\n",
		       tmpesrt.fw_resource_version);
		return;
	}

	if (tmpesrt.fw_resource_count > 0 && max - size < entry_size) {
		pr_err("ESRT memory map entry can only hold the header. (max: %zu size: %zu)\n",
		       max - size, entry_size);
		goto err_memunmap;
	}

	/*
	 * The format doesn't really give us any boundary to test here,
	 * so I'm making up 128 as the max number of individually updatable
	 * components we support.
	 * 128 should be pretty excessive, but there's still some chance
	 * somebody will do that someday and we'll need to raise this.
	 */
	if (tmpesrt.fw_resource_count > 128) {
		pr_err("ESRT says fw_resource_count has very large value %d.\n",
		       tmpesrt.fw_resource_count);
		goto err_memunmap;
	}

	/*
	 * We know it can't be larger than N * sizeof() here, and N is limited
	 * by the previous test to a small number, so there's no overflow.
	 */
	entries_size = tmpesrt.fw_resource_count * entry_size;
	if (max < size + entries_size) {
		pr_err("ESRT does not fit on single memory map entry (size: %zu max: %zu)\n",
		       size, max);
		goto err_memunmap;
	}

	/* remap it with our (plausible) new pages */
	early_memunmap(va, size);
	size += entries_size;
	va = early_memremap(efi.esrt, size);
	if (!va) {
		pr_err("early_memremap(%p, %zu) failed.\n", (void *)efi.esrt,
		       size);
		return;
	}

	esrt_data = (phys_addr_t)efi.esrt;
	esrt_data_size = size;

	end = esrt_data + size;
	pr_info("Reserving ESRT space from %pa to %pa.\n", &esrt_data, &end);
	if (md.type == EFI_BOOT_SERVICES_DATA)
		efi_mem_reserve(esrt_data, esrt_data_size);

	pr_debug("esrt-init: loaded.\n");
err_memunmap:
	early_memunmap(va, size);
}

static int __init register_entries(void)
{
	struct efi_system_resource_entry_v1 *v1_entries = (void *)esrt->entries;
	int i, rc;

	if (!esrt_table_exists())
		return 0;

	for (i = 0; i < le32_to_cpu(esrt->fw_resource_count); i++) {
		void *esre = NULL;
		if (esrt->fw_resource_version == 1) {
			esre = &v1_entries[i];
		} else {
			pr_err("Unsupported ESRT version %lld.\n",
			       esrt->fw_resource_version);
			return -EINVAL;
		}

		rc = esre_create_sysfs_entry(esre, i);
		if (rc < 0) {
			pr_err("ESRT entry creation failed with error %d.\n",
			       rc);
			return rc;
		}
	}
	return 0;
}

static void cleanup_entry_list(void)
{
	struct esre_entry *entry, *next;

	list_for_each_entry_safe(entry, next, &entry_list, list) {
		kobject_put(&entry->kobj);
	}
}

static int __init esrt_sysfs_init(void)
{
	int error;

	pr_debug("esrt-sysfs: loading.\n");
	if (!esrt_data || !esrt_data_size)
		return -ENOSYS;

	esrt = memremap(esrt_data, esrt_data_size, MEMREMAP_WB);
	if (!esrt) {
		pr_err("memremap(%pa, %zu) failed.\n", &esrt_data,
		       esrt_data_size);
		return -ENOMEM;
	}

	esrt_kobj = kobject_create_and_add("esrt", efi_kobj);
	if (!esrt_kobj) {
		pr_err("Firmware table registration failed.\n");
		error = -ENOMEM;
		goto err;
	}

	error = sysfs_create_group(esrt_kobj, &esrt_attr_group);
	if (error) {
		pr_err("Sysfs attribute export failed with error %d.\n",
		       error);
		goto err_remove_esrt;
	}

	esrt_kset = kset_create_and_add("entries", NULL, esrt_kobj);
	if (!esrt_kset) {
		pr_err("kset creation failed.\n");
		error = -ENOMEM;
		goto err_remove_group;
	}

	error = register_entries();
	if (error)
		goto err_cleanup_list;

	pr_debug("esrt-sysfs: loaded.\n");

	return 0;
err_cleanup_list:
	cleanup_entry_list();
	kset_unregister(esrt_kset);
err_remove_group:
	sysfs_remove_group(esrt_kobj, &esrt_attr_group);
err_remove_esrt:
	kobject_put(esrt_kobj);
err:
	memunmap(esrt);
	esrt = NULL;
	return error;
}
device_initcall(esrt_sysfs_init);

/*
MODULE_AUTHOR("Peter Jones <pjones@redhat.com>");
MODULE_DESCRIPTION("EFI System Resource Table support");
MODULE_LICENSE("GPL");
*/