Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5039e316 authored by Dave Young's avatar Dave Young Committed by Matt Fleming
Browse files

x86: Export x86 boot_params to sysfs



kexec-tools use boot_params for getting the 1st kernel hardware_subarch,
the kexec kernel EFI runtime support also needs to read the old efi_info
from boot_params. Currently it exists in debugfs which is not a good
place for such infomation. Per HPA, we should avoid "sploit debugfs".

In this patch /sys/kernel/boot_params are exported, also the setup_data is
exported as a subdirectory. kexec-tools is using debugfs for hardware_subarch
for a long time now so we're not removing it yet.

Structure is like below:

/sys/kernel/boot_params
|__ data                /* boot_params in binary*/
|__ setup_data
|   |__ 0               /* the first setup_data node */
|   |   |__ data        /* setup_data node 0 in binary*/
|   |   |__ type        /* setup_data type of setup_data node 0, hex string */
[snip]
|__ version             /* boot protocal version (in hex, "0x" prefixed)*/

Signed-off-by: default avatarDave Young <dyoung@redhat.com>
Acked-by: default avatarBorislav Petkov <bp@suse.de>
Tested-by: default avatarToshi Kani <toshi.kani@hp.com>
Signed-off-by: default avatarMatt Fleming <matt.fleming@intel.com>
parent 456a29dd
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
What:		/sys/kernel/boot_params
Date:		December 2013
Contact:	Dave Young <dyoung@redhat.com>
Description:	The /sys/kernel/boot_params directory contains two
		files: "data" and "version" and one subdirectory "setup_data".
		It is used to export the kernel boot parameters of an x86
		platform to userspace for kexec and debugging purpose.

		If there's no setup_data in boot_params the subdirectory will
		not be created.

		"data" file is the binary representation of struct boot_params.

		"version" file is the string representation of boot
		protocol version.

		"setup_data" subdirectory contains the setup_data data
		structure in boot_params. setup_data is maintained in kernel
		as a link list. In "setup_data" subdirectory there's one
		subdirectory for each link list node named with the number
		of the list nodes. The list node subdirectory contains two
		files "type" and "data". "type" file is the string
		representation of setup_data type. "data" file is the binary
		representation of setup_data payload.

		The whole boot_params directory structure is like below:
		/sys/kernel/boot_params
		|__ data
		|__ setup_data
		|   |__ 0
		|   |   |__ data
		|   |   |__ type
		|   |__ 1
		|       |__ data
		|       |__ type
		|__ version

Users:		Kexec
+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ obj-$(CONFIG_X86_64) += sys_x86_64.o x8664_ksyms_64.o
obj-y			+= syscall_$(BITS).o
obj-$(CONFIG_X86_64)	+= vsyscall_64.o
obj-$(CONFIG_X86_64)	+= vsyscall_emu_64.o
obj-$(CONFIG_SYSFS)	+= ksysfs.o
obj-y			+= bootflag.o e820.o
obj-y			+= pci-dma.o quirks.o topology.o kdebugfs.o
obj-y			+= alternative.o i8253.o pci-nommu.o hw_breakpoint.o
+339 −0
Original line number Diff line number Diff line
/*
 * Architecture specific sysfs attributes in /sys/kernel
 *
 * Copyright (C) 2007, Intel Corp.
 *      Huang Ying <ying.huang@intel.com>
 * Copyright (C) 2013, 2013 Red Hat, Inc.
 *      Dave Young <dyoung@redhat.com>
 *
 * This file is released under the GPLv2
 */

#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/init.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/mm.h>

#include <asm/setup.h>

static ssize_t version_show(struct kobject *kobj,
			    struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "0x%04x\n", boot_params.hdr.version);
}

static struct kobj_attribute boot_params_version_attr = __ATTR_RO(version);

static ssize_t boot_params_data_read(struct file *fp, struct kobject *kobj,
				     struct bin_attribute *bin_attr,
				     char *buf, loff_t off, size_t count)
{
	memcpy(buf, (void *)&boot_params + off, count);
	return count;
}

static struct bin_attribute boot_params_data_attr = {
	.attr = {
		.name = "data",
		.mode = S_IRUGO,
	},
	.read = boot_params_data_read,
	.size = sizeof(boot_params),
};

static struct attribute *boot_params_version_attrs[] = {
	&boot_params_version_attr.attr,
	NULL,
};

static struct bin_attribute *boot_params_data_attrs[] = {
	&boot_params_data_attr,
	NULL,
};

static struct attribute_group boot_params_attr_group = {
	.attrs = boot_params_version_attrs,
	.bin_attrs = boot_params_data_attrs,
};

static int kobj_to_setup_data_nr(struct kobject *kobj, int *nr)
{
	const char *name;

	name = kobject_name(kobj);
	return kstrtoint(name, 10, nr);
}

static int get_setup_data_paddr(int nr, u64 *paddr)
{
	int i = 0;
	struct setup_data *data;
	u64 pa_data = boot_params.hdr.setup_data;

	while (pa_data) {
		if (nr == i) {
			*paddr = pa_data;
			return 0;
		}
		data = ioremap_cache(pa_data, sizeof(*data));
		if (!data)
			return -ENOMEM;

		pa_data = data->next;
		iounmap(data);
		i++;
	}
	return -EINVAL;
}

static int __init get_setup_data_size(int nr, size_t *size)
{
	int i = 0;
	struct setup_data *data;
	u64 pa_data = boot_params.hdr.setup_data;

	while (pa_data) {
		data = ioremap_cache(pa_data, sizeof(*data));
		if (!data)
			return -ENOMEM;
		if (nr == i) {
			*size = data->len;
			iounmap(data);
			return 0;
		}

		pa_data = data->next;
		iounmap(data);
		i++;
	}
	return -EINVAL;
}

static ssize_t type_show(struct kobject *kobj,
			 struct kobj_attribute *attr, char *buf)
{
	int nr, ret;
	u64 paddr;
	struct setup_data *data;

	ret = kobj_to_setup_data_nr(kobj, &nr);
	if (ret)
		return ret;

	ret = get_setup_data_paddr(nr, &paddr);
	if (ret)
		return ret;
	data = ioremap_cache(paddr, sizeof(*data));
	if (!data)
		return -ENOMEM;

	ret = sprintf(buf, "0x%x\n", data->type);
	iounmap(data);
	return ret;
}

static ssize_t setup_data_data_read(struct file *fp,
				    struct kobject *kobj,
				    struct bin_attribute *bin_attr,
				    char *buf,
				    loff_t off, size_t count)
{
	int nr, ret = 0;
	u64 paddr;
	struct setup_data *data;
	void *p;

	ret = kobj_to_setup_data_nr(kobj, &nr);
	if (ret)
		return ret;

	ret = get_setup_data_paddr(nr, &paddr);
	if (ret)
		return ret;
	data = ioremap_cache(paddr, sizeof(*data));
	if (!data)
		return -ENOMEM;

	if (off > data->len) {
		ret = -EINVAL;
		goto out;
	}

	if (count > data->len - off)
		count = data->len - off;

	if (!count)
		goto out;

	ret = count;
	p = ioremap_cache(paddr + sizeof(*data), data->len);
	if (!p) {
		ret = -ENOMEM;
		goto out;
	}
	memcpy(buf, p + off, count);
	iounmap(p);
out:
	iounmap(data);
	return ret;
}

static struct kobj_attribute type_attr = __ATTR_RO(type);

static struct bin_attribute data_attr = {
	.attr = {
		.name = "data",
		.mode = S_IRUGO,
	},
	.read = setup_data_data_read,
};

static struct attribute *setup_data_type_attrs[] = {
	&type_attr.attr,
	NULL,
};

static struct bin_attribute *setup_data_data_attrs[] = {
	&data_attr,
	NULL,
};

static struct attribute_group setup_data_attr_group = {
	.attrs = setup_data_type_attrs,
	.bin_attrs = setup_data_data_attrs,
};

static int __init create_setup_data_node(struct kobject *parent,
					 struct kobject **kobjp, int nr)
{
	int ret = 0;
	size_t size;
	struct kobject *kobj;
	char name[16]; /* should be enough for setup_data nodes numbers */
	snprintf(name, 16, "%d", nr);

	kobj = kobject_create_and_add(name, parent);
	if (!kobj)
		return -ENOMEM;

	ret = get_setup_data_size(nr, &size);
	if (ret)
		goto out_kobj;

	data_attr.size = size;
	ret = sysfs_create_group(kobj, &setup_data_attr_group);
	if (ret)
		goto out_kobj;
	*kobjp = kobj;

	return 0;
out_kobj:
	kobject_put(kobj);
	return ret;
}

static void __init cleanup_setup_data_node(struct kobject *kobj)
{
	sysfs_remove_group(kobj, &setup_data_attr_group);
	kobject_put(kobj);
}

static int __init get_setup_data_total_num(u64 pa_data, int *nr)
{
	int ret = 0;
	struct setup_data *data;

	*nr = 0;
	while (pa_data) {
		*nr += 1;
		data = ioremap_cache(pa_data, sizeof(*data));
		if (!data) {
			ret = -ENOMEM;
			goto out;
		}
		pa_data = data->next;
		iounmap(data);
	}

out:
	return ret;
}

static int __init create_setup_data_nodes(struct kobject *parent)
{
	struct kobject *setup_data_kobj, **kobjp;
	u64 pa_data;
	int i, j, nr, ret = 0;

	pa_data = boot_params.hdr.setup_data;
	if (!pa_data)
		return 0;

	setup_data_kobj = kobject_create_and_add("setup_data", parent);
	if (!setup_data_kobj) {
		ret = -ENOMEM;
		goto out;
	}

	ret = get_setup_data_total_num(pa_data, &nr);
	if (ret)
		goto out_setup_data_kobj;

	kobjp = kmalloc(sizeof(*kobjp) * nr, GFP_KERNEL);
	if (!kobjp) {
		ret = -ENOMEM;
		goto out_setup_data_kobj;
	}

	for (i = 0; i < nr; i++) {
		ret = create_setup_data_node(setup_data_kobj, kobjp + i, i);
		if (ret)
			goto out_clean_nodes;
	}

	kfree(kobjp);
	return 0;

out_clean_nodes:
	for (j = i - 1; j > 0; j--)
		cleanup_setup_data_node(*(kobjp + j));
	kfree(kobjp);
out_setup_data_kobj:
	kobject_put(setup_data_kobj);
out:
	return ret;
}

static int __init boot_params_ksysfs_init(void)
{
	int ret;
	struct kobject *boot_params_kobj;

	boot_params_kobj = kobject_create_and_add("boot_params",
						  kernel_kobj);
	if (!boot_params_kobj) {
		ret = -ENOMEM;
		goto out;
	}

	ret = sysfs_create_group(boot_params_kobj, &boot_params_attr_group);
	if (ret)
		goto out_boot_params_kobj;

	ret = create_setup_data_nodes(boot_params_kobj);
	if (ret)
		goto out_create_group;

	return 0;
out_create_group:
	sysfs_remove_group(boot_params_kobj, &boot_params_attr_group);
out_boot_params_kobj:
	kobject_put(boot_params_kobj);
out:
	return ret;
}

arch_initcall(boot_params_ksysfs_init);