162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * drivers/firmware/qemu_fw_cfg.c
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright 2015 Carnegie Mellon University
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Expose entries from QEMU's firmware configuration (fw_cfg) device in
762306a36Sopenharmony_ci * sysfs (read-only, under "/sys/firmware/qemu_fw_cfg/...").
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * The fw_cfg device may be instantiated via either an ACPI node (on x86
1062306a36Sopenharmony_ci * and select subsets of aarch64), a Device Tree node (on arm), or using
1162306a36Sopenharmony_ci * a kernel module (or command line) parameter with the following syntax:
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci *      [qemu_fw_cfg.]ioport=<size>@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]
1462306a36Sopenharmony_ci * or
1562306a36Sopenharmony_ci *      [qemu_fw_cfg.]mmio=<size>@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]
1662306a36Sopenharmony_ci *
1762306a36Sopenharmony_ci * where:
1862306a36Sopenharmony_ci *      <size>     := size of ioport or mmio range
1962306a36Sopenharmony_ci *      <base>     := physical base address of ioport or mmio range
2062306a36Sopenharmony_ci *      <ctrl_off> := (optional) offset of control register
2162306a36Sopenharmony_ci *      <data_off> := (optional) offset of data register
2262306a36Sopenharmony_ci *      <dma_off> := (optional) offset of dma register
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * e.g.:
2562306a36Sopenharmony_ci *      qemu_fw_cfg.ioport=12@0x510:0:1:4	(the default on x86)
2662306a36Sopenharmony_ci * or
2762306a36Sopenharmony_ci *      qemu_fw_cfg.mmio=16@0x9020000:8:0:16	(the default on arm)
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#include <linux/module.h>
3162306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
3262306a36Sopenharmony_ci#include <linux/platform_device.h>
3362306a36Sopenharmony_ci#include <linux/acpi.h>
3462306a36Sopenharmony_ci#include <linux/slab.h>
3562306a36Sopenharmony_ci#include <linux/io.h>
3662306a36Sopenharmony_ci#include <linux/ioport.h>
3762306a36Sopenharmony_ci#include <uapi/linux/qemu_fw_cfg.h>
3862306a36Sopenharmony_ci#include <linux/delay.h>
3962306a36Sopenharmony_ci#include <linux/crash_dump.h>
4062306a36Sopenharmony_ci#include <linux/crash_core.h>
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ciMODULE_AUTHOR("Gabriel L. Somlo <somlo@cmu.edu>");
4362306a36Sopenharmony_ciMODULE_DESCRIPTION("QEMU fw_cfg sysfs support");
4462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci/* fw_cfg revision attribute, in /sys/firmware/qemu_fw_cfg top-level dir. */
4762306a36Sopenharmony_cistatic u32 fw_cfg_rev;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* fw_cfg device i/o register addresses */
5062306a36Sopenharmony_cistatic bool fw_cfg_is_mmio;
5162306a36Sopenharmony_cistatic phys_addr_t fw_cfg_p_base;
5262306a36Sopenharmony_cistatic resource_size_t fw_cfg_p_size;
5362306a36Sopenharmony_cistatic void __iomem *fw_cfg_dev_base;
5462306a36Sopenharmony_cistatic void __iomem *fw_cfg_reg_ctrl;
5562306a36Sopenharmony_cistatic void __iomem *fw_cfg_reg_data;
5662306a36Sopenharmony_cistatic void __iomem *fw_cfg_reg_dma;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/* atomic access to fw_cfg device (potentially slow i/o, so using mutex) */
5962306a36Sopenharmony_cistatic DEFINE_MUTEX(fw_cfg_dev_lock);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci/* pick appropriate endianness for selector key */
6262306a36Sopenharmony_cistatic void fw_cfg_sel_endianness(u16 key)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	if (fw_cfg_is_mmio)
6562306a36Sopenharmony_ci		iowrite16be(key, fw_cfg_reg_ctrl);
6662306a36Sopenharmony_ci	else
6762306a36Sopenharmony_ci		iowrite16(key, fw_cfg_reg_ctrl);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci#ifdef CONFIG_CRASH_CORE
7162306a36Sopenharmony_cistatic inline bool fw_cfg_dma_enabled(void)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	return (fw_cfg_rev & FW_CFG_VERSION_DMA) && fw_cfg_reg_dma;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/* qemu fw_cfg device is sync today, but spec says it may become async */
7762306a36Sopenharmony_cistatic void fw_cfg_wait_for_control(struct fw_cfg_dma_access *d)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	for (;;) {
8062306a36Sopenharmony_ci		u32 ctrl = be32_to_cpu(READ_ONCE(d->control));
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		/* do not reorder the read to d->control */
8362306a36Sopenharmony_ci		rmb();
8462306a36Sopenharmony_ci		if ((ctrl & ~FW_CFG_DMA_CTL_ERROR) == 0)
8562306a36Sopenharmony_ci			return;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci		cpu_relax();
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic ssize_t fw_cfg_dma_transfer(void *address, u32 length, u32 control)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	phys_addr_t dma;
9462306a36Sopenharmony_ci	struct fw_cfg_dma_access *d = NULL;
9562306a36Sopenharmony_ci	ssize_t ret = length;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	d = kmalloc(sizeof(*d), GFP_KERNEL);
9862306a36Sopenharmony_ci	if (!d) {
9962306a36Sopenharmony_ci		ret = -ENOMEM;
10062306a36Sopenharmony_ci		goto end;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	/* fw_cfg device does not need IOMMU protection, so use physical addresses */
10462306a36Sopenharmony_ci	*d = (struct fw_cfg_dma_access) {
10562306a36Sopenharmony_ci		.address = cpu_to_be64(address ? virt_to_phys(address) : 0),
10662306a36Sopenharmony_ci		.length = cpu_to_be32(length),
10762306a36Sopenharmony_ci		.control = cpu_to_be32(control)
10862306a36Sopenharmony_ci	};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	dma = virt_to_phys(d);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	iowrite32be((u64)dma >> 32, fw_cfg_reg_dma);
11362306a36Sopenharmony_ci	/* force memory to sync before notifying device via MMIO */
11462306a36Sopenharmony_ci	wmb();
11562306a36Sopenharmony_ci	iowrite32be(dma, fw_cfg_reg_dma + 4);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	fw_cfg_wait_for_control(d);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (be32_to_cpu(READ_ONCE(d->control)) & FW_CFG_DMA_CTL_ERROR) {
12062306a36Sopenharmony_ci		ret = -EIO;
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ciend:
12462306a36Sopenharmony_ci	kfree(d);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return ret;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci#endif
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci/* read chunk of given fw_cfg blob (caller responsible for sanity-check) */
13162306a36Sopenharmony_cistatic ssize_t fw_cfg_read_blob(u16 key,
13262306a36Sopenharmony_ci				void *buf, loff_t pos, size_t count)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	u32 glk = -1U;
13562306a36Sopenharmony_ci	acpi_status status;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* If we have ACPI, ensure mutual exclusion against any potential
13862306a36Sopenharmony_ci	 * device access by the firmware, e.g. via AML methods:
13962306a36Sopenharmony_ci	 */
14062306a36Sopenharmony_ci	status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
14162306a36Sopenharmony_ci	if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
14262306a36Sopenharmony_ci		/* Should never get here */
14362306a36Sopenharmony_ci		WARN(1, "fw_cfg_read_blob: Failed to lock ACPI!\n");
14462306a36Sopenharmony_ci		memset(buf, 0, count);
14562306a36Sopenharmony_ci		return -EINVAL;
14662306a36Sopenharmony_ci	}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	mutex_lock(&fw_cfg_dev_lock);
14962306a36Sopenharmony_ci	fw_cfg_sel_endianness(key);
15062306a36Sopenharmony_ci	while (pos-- > 0)
15162306a36Sopenharmony_ci		ioread8(fw_cfg_reg_data);
15262306a36Sopenharmony_ci	ioread8_rep(fw_cfg_reg_data, buf, count);
15362306a36Sopenharmony_ci	mutex_unlock(&fw_cfg_dev_lock);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	acpi_release_global_lock(glk);
15662306a36Sopenharmony_ci	return count;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci#ifdef CONFIG_CRASH_CORE
16062306a36Sopenharmony_ci/* write chunk of given fw_cfg blob (caller responsible for sanity-check) */
16162306a36Sopenharmony_cistatic ssize_t fw_cfg_write_blob(u16 key,
16262306a36Sopenharmony_ci				 void *buf, loff_t pos, size_t count)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	u32 glk = -1U;
16562306a36Sopenharmony_ci	acpi_status status;
16662306a36Sopenharmony_ci	ssize_t ret = count;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	/* If we have ACPI, ensure mutual exclusion against any potential
16962306a36Sopenharmony_ci	 * device access by the firmware, e.g. via AML methods:
17062306a36Sopenharmony_ci	 */
17162306a36Sopenharmony_ci	status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
17262306a36Sopenharmony_ci	if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
17362306a36Sopenharmony_ci		/* Should never get here */
17462306a36Sopenharmony_ci		WARN(1, "%s: Failed to lock ACPI!\n", __func__);
17562306a36Sopenharmony_ci		return -EINVAL;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	mutex_lock(&fw_cfg_dev_lock);
17962306a36Sopenharmony_ci	if (pos == 0) {
18062306a36Sopenharmony_ci		ret = fw_cfg_dma_transfer(buf, count, key << 16
18162306a36Sopenharmony_ci					  | FW_CFG_DMA_CTL_SELECT
18262306a36Sopenharmony_ci					  | FW_CFG_DMA_CTL_WRITE);
18362306a36Sopenharmony_ci	} else {
18462306a36Sopenharmony_ci		fw_cfg_sel_endianness(key);
18562306a36Sopenharmony_ci		ret = fw_cfg_dma_transfer(NULL, pos, FW_CFG_DMA_CTL_SKIP);
18662306a36Sopenharmony_ci		if (ret < 0)
18762306a36Sopenharmony_ci			goto end;
18862306a36Sopenharmony_ci		ret = fw_cfg_dma_transfer(buf, count, FW_CFG_DMA_CTL_WRITE);
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ciend:
19262306a36Sopenharmony_ci	mutex_unlock(&fw_cfg_dev_lock);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	acpi_release_global_lock(glk);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	return ret;
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci#endif /* CONFIG_CRASH_CORE */
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci/* clean up fw_cfg device i/o */
20162306a36Sopenharmony_cistatic void fw_cfg_io_cleanup(void)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	if (fw_cfg_is_mmio) {
20462306a36Sopenharmony_ci		iounmap(fw_cfg_dev_base);
20562306a36Sopenharmony_ci		release_mem_region(fw_cfg_p_base, fw_cfg_p_size);
20662306a36Sopenharmony_ci	} else {
20762306a36Sopenharmony_ci		ioport_unmap(fw_cfg_dev_base);
20862306a36Sopenharmony_ci		release_region(fw_cfg_p_base, fw_cfg_p_size);
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci/* arch-specific ctrl & data register offsets are not available in ACPI, DT */
21362306a36Sopenharmony_ci#if !(defined(FW_CFG_CTRL_OFF) && defined(FW_CFG_DATA_OFF))
21462306a36Sopenharmony_ci# if (defined(CONFIG_ARM) || defined(CONFIG_ARM64))
21562306a36Sopenharmony_ci#  define FW_CFG_CTRL_OFF 0x08
21662306a36Sopenharmony_ci#  define FW_CFG_DATA_OFF 0x00
21762306a36Sopenharmony_ci#  define FW_CFG_DMA_OFF 0x10
21862306a36Sopenharmony_ci# elif defined(CONFIG_PARISC)	/* parisc */
21962306a36Sopenharmony_ci#  define FW_CFG_CTRL_OFF 0x00
22062306a36Sopenharmony_ci#  define FW_CFG_DATA_OFF 0x04
22162306a36Sopenharmony_ci# elif (defined(CONFIG_PPC_PMAC) || defined(CONFIG_SPARC32)) /* ppc/mac,sun4m */
22262306a36Sopenharmony_ci#  define FW_CFG_CTRL_OFF 0x00
22362306a36Sopenharmony_ci#  define FW_CFG_DATA_OFF 0x02
22462306a36Sopenharmony_ci# elif (defined(CONFIG_X86) || defined(CONFIG_SPARC64)) /* x86, sun4u */
22562306a36Sopenharmony_ci#  define FW_CFG_CTRL_OFF 0x00
22662306a36Sopenharmony_ci#  define FW_CFG_DATA_OFF 0x01
22762306a36Sopenharmony_ci#  define FW_CFG_DMA_OFF 0x04
22862306a36Sopenharmony_ci# else
22962306a36Sopenharmony_ci#  error "QEMU FW_CFG not available on this architecture!"
23062306a36Sopenharmony_ci# endif
23162306a36Sopenharmony_ci#endif
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci/* initialize fw_cfg device i/o from platform data */
23462306a36Sopenharmony_cistatic int fw_cfg_do_platform_probe(struct platform_device *pdev)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	char sig[FW_CFG_SIG_SIZE];
23762306a36Sopenharmony_ci	struct resource *range, *ctrl, *data, *dma;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	/* acquire i/o range details */
24062306a36Sopenharmony_ci	fw_cfg_is_mmio = false;
24162306a36Sopenharmony_ci	range = platform_get_resource(pdev, IORESOURCE_IO, 0);
24262306a36Sopenharmony_ci	if (!range) {
24362306a36Sopenharmony_ci		fw_cfg_is_mmio = true;
24462306a36Sopenharmony_ci		range = platform_get_resource(pdev, IORESOURCE_MEM, 0);
24562306a36Sopenharmony_ci		if (!range)
24662306a36Sopenharmony_ci			return -EINVAL;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci	fw_cfg_p_base = range->start;
24962306a36Sopenharmony_ci	fw_cfg_p_size = resource_size(range);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	if (fw_cfg_is_mmio) {
25262306a36Sopenharmony_ci		if (!request_mem_region(fw_cfg_p_base,
25362306a36Sopenharmony_ci					fw_cfg_p_size, "fw_cfg_mem"))
25462306a36Sopenharmony_ci			return -EBUSY;
25562306a36Sopenharmony_ci		fw_cfg_dev_base = ioremap(fw_cfg_p_base, fw_cfg_p_size);
25662306a36Sopenharmony_ci		if (!fw_cfg_dev_base) {
25762306a36Sopenharmony_ci			release_mem_region(fw_cfg_p_base, fw_cfg_p_size);
25862306a36Sopenharmony_ci			return -EFAULT;
25962306a36Sopenharmony_ci		}
26062306a36Sopenharmony_ci	} else {
26162306a36Sopenharmony_ci		if (!request_region(fw_cfg_p_base,
26262306a36Sopenharmony_ci				    fw_cfg_p_size, "fw_cfg_io"))
26362306a36Sopenharmony_ci			return -EBUSY;
26462306a36Sopenharmony_ci		fw_cfg_dev_base = ioport_map(fw_cfg_p_base, fw_cfg_p_size);
26562306a36Sopenharmony_ci		if (!fw_cfg_dev_base) {
26662306a36Sopenharmony_ci			release_region(fw_cfg_p_base, fw_cfg_p_size);
26762306a36Sopenharmony_ci			return -EFAULT;
26862306a36Sopenharmony_ci		}
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	/* were custom register offsets provided (e.g. on the command line)? */
27262306a36Sopenharmony_ci	ctrl = platform_get_resource_byname(pdev, IORESOURCE_REG, "ctrl");
27362306a36Sopenharmony_ci	data = platform_get_resource_byname(pdev, IORESOURCE_REG, "data");
27462306a36Sopenharmony_ci	dma = platform_get_resource_byname(pdev, IORESOURCE_REG, "dma");
27562306a36Sopenharmony_ci	if (ctrl && data) {
27662306a36Sopenharmony_ci		fw_cfg_reg_ctrl = fw_cfg_dev_base + ctrl->start;
27762306a36Sopenharmony_ci		fw_cfg_reg_data = fw_cfg_dev_base + data->start;
27862306a36Sopenharmony_ci	} else {
27962306a36Sopenharmony_ci		/* use architecture-specific offsets */
28062306a36Sopenharmony_ci		fw_cfg_reg_ctrl = fw_cfg_dev_base + FW_CFG_CTRL_OFF;
28162306a36Sopenharmony_ci		fw_cfg_reg_data = fw_cfg_dev_base + FW_CFG_DATA_OFF;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	if (dma)
28562306a36Sopenharmony_ci		fw_cfg_reg_dma = fw_cfg_dev_base + dma->start;
28662306a36Sopenharmony_ci#ifdef FW_CFG_DMA_OFF
28762306a36Sopenharmony_ci	else
28862306a36Sopenharmony_ci		fw_cfg_reg_dma = fw_cfg_dev_base + FW_CFG_DMA_OFF;
28962306a36Sopenharmony_ci#endif
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	/* verify fw_cfg device signature */
29262306a36Sopenharmony_ci	if (fw_cfg_read_blob(FW_CFG_SIGNATURE, sig,
29362306a36Sopenharmony_ci				0, FW_CFG_SIG_SIZE) < 0 ||
29462306a36Sopenharmony_ci		memcmp(sig, "QEMU", FW_CFG_SIG_SIZE) != 0) {
29562306a36Sopenharmony_ci		fw_cfg_io_cleanup();
29662306a36Sopenharmony_ci		return -ENODEV;
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	return 0;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic ssize_t fw_cfg_showrev(struct kobject *k, struct kobj_attribute *a,
30362306a36Sopenharmony_ci			      char *buf)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	return sprintf(buf, "%u\n", fw_cfg_rev);
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic const struct kobj_attribute fw_cfg_rev_attr = {
30962306a36Sopenharmony_ci	.attr = { .name = "rev", .mode = S_IRUSR },
31062306a36Sopenharmony_ci	.show = fw_cfg_showrev,
31162306a36Sopenharmony_ci};
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci/* fw_cfg_sysfs_entry type */
31462306a36Sopenharmony_cistruct fw_cfg_sysfs_entry {
31562306a36Sopenharmony_ci	struct kobject kobj;
31662306a36Sopenharmony_ci	u32 size;
31762306a36Sopenharmony_ci	u16 select;
31862306a36Sopenharmony_ci	char name[FW_CFG_MAX_FILE_PATH];
31962306a36Sopenharmony_ci	struct list_head list;
32062306a36Sopenharmony_ci};
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci#ifdef CONFIG_CRASH_CORE
32362306a36Sopenharmony_cistatic ssize_t fw_cfg_write_vmcoreinfo(const struct fw_cfg_file *f)
32462306a36Sopenharmony_ci{
32562306a36Sopenharmony_ci	static struct fw_cfg_vmcoreinfo *data;
32662306a36Sopenharmony_ci	ssize_t ret;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	data = kmalloc(sizeof(struct fw_cfg_vmcoreinfo), GFP_KERNEL);
32962306a36Sopenharmony_ci	if (!data)
33062306a36Sopenharmony_ci		return -ENOMEM;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	*data = (struct fw_cfg_vmcoreinfo) {
33362306a36Sopenharmony_ci		.guest_format = cpu_to_le16(FW_CFG_VMCOREINFO_FORMAT_ELF),
33462306a36Sopenharmony_ci		.size = cpu_to_le32(VMCOREINFO_NOTE_SIZE),
33562306a36Sopenharmony_ci		.paddr = cpu_to_le64(paddr_vmcoreinfo_note())
33662306a36Sopenharmony_ci	};
33762306a36Sopenharmony_ci	/* spare ourself reading host format support for now since we
33862306a36Sopenharmony_ci	 * don't know what else to format - host may ignore ours
33962306a36Sopenharmony_ci	 */
34062306a36Sopenharmony_ci	ret = fw_cfg_write_blob(be16_to_cpu(f->select), data,
34162306a36Sopenharmony_ci				0, sizeof(struct fw_cfg_vmcoreinfo));
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	kfree(data);
34462306a36Sopenharmony_ci	return ret;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci#endif /* CONFIG_CRASH_CORE */
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci/* get fw_cfg_sysfs_entry from kobject member */
34962306a36Sopenharmony_cistatic inline struct fw_cfg_sysfs_entry *to_entry(struct kobject *kobj)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	return container_of(kobj, struct fw_cfg_sysfs_entry, kobj);
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci/* fw_cfg_sysfs_attribute type */
35562306a36Sopenharmony_cistruct fw_cfg_sysfs_attribute {
35662306a36Sopenharmony_ci	struct attribute attr;
35762306a36Sopenharmony_ci	ssize_t (*show)(struct fw_cfg_sysfs_entry *entry, char *buf);
35862306a36Sopenharmony_ci};
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci/* get fw_cfg_sysfs_attribute from attribute member */
36162306a36Sopenharmony_cistatic inline struct fw_cfg_sysfs_attribute *to_attr(struct attribute *attr)
36262306a36Sopenharmony_ci{
36362306a36Sopenharmony_ci	return container_of(attr, struct fw_cfg_sysfs_attribute, attr);
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci/* global cache of fw_cfg_sysfs_entry objects */
36762306a36Sopenharmony_cistatic LIST_HEAD(fw_cfg_entry_cache);
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci/* kobjects removed lazily by kernel, mutual exclusion needed */
37062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(fw_cfg_cache_lock);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_cistatic inline void fw_cfg_sysfs_cache_enlist(struct fw_cfg_sysfs_entry *entry)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	spin_lock(&fw_cfg_cache_lock);
37562306a36Sopenharmony_ci	list_add_tail(&entry->list, &fw_cfg_entry_cache);
37662306a36Sopenharmony_ci	spin_unlock(&fw_cfg_cache_lock);
37762306a36Sopenharmony_ci}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_cistatic inline void fw_cfg_sysfs_cache_delist(struct fw_cfg_sysfs_entry *entry)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	spin_lock(&fw_cfg_cache_lock);
38262306a36Sopenharmony_ci	list_del(&entry->list);
38362306a36Sopenharmony_ci	spin_unlock(&fw_cfg_cache_lock);
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cistatic void fw_cfg_sysfs_cache_cleanup(void)
38762306a36Sopenharmony_ci{
38862306a36Sopenharmony_ci	struct fw_cfg_sysfs_entry *entry, *next;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	list_for_each_entry_safe(entry, next, &fw_cfg_entry_cache, list) {
39162306a36Sopenharmony_ci		fw_cfg_sysfs_cache_delist(entry);
39262306a36Sopenharmony_ci		kobject_del(&entry->kobj);
39362306a36Sopenharmony_ci		kobject_put(&entry->kobj);
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci/* per-entry attributes and show methods */
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci#define FW_CFG_SYSFS_ATTR(_attr) \
40062306a36Sopenharmony_cistruct fw_cfg_sysfs_attribute fw_cfg_sysfs_attr_##_attr = { \
40162306a36Sopenharmony_ci	.attr = { .name = __stringify(_attr), .mode = S_IRUSR }, \
40262306a36Sopenharmony_ci	.show = fw_cfg_sysfs_show_##_attr, \
40362306a36Sopenharmony_ci}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_cistatic ssize_t fw_cfg_sysfs_show_size(struct fw_cfg_sysfs_entry *e, char *buf)
40662306a36Sopenharmony_ci{
40762306a36Sopenharmony_ci	return sprintf(buf, "%u\n", e->size);
40862306a36Sopenharmony_ci}
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_cistatic ssize_t fw_cfg_sysfs_show_key(struct fw_cfg_sysfs_entry *e, char *buf)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	return sprintf(buf, "%u\n", e->select);
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic ssize_t fw_cfg_sysfs_show_name(struct fw_cfg_sysfs_entry *e, char *buf)
41662306a36Sopenharmony_ci{
41762306a36Sopenharmony_ci	return sprintf(buf, "%s\n", e->name);
41862306a36Sopenharmony_ci}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_cistatic FW_CFG_SYSFS_ATTR(size);
42162306a36Sopenharmony_cistatic FW_CFG_SYSFS_ATTR(key);
42262306a36Sopenharmony_cistatic FW_CFG_SYSFS_ATTR(name);
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic struct attribute *fw_cfg_sysfs_entry_attrs[] = {
42562306a36Sopenharmony_ci	&fw_cfg_sysfs_attr_size.attr,
42662306a36Sopenharmony_ci	&fw_cfg_sysfs_attr_key.attr,
42762306a36Sopenharmony_ci	&fw_cfg_sysfs_attr_name.attr,
42862306a36Sopenharmony_ci	NULL,
42962306a36Sopenharmony_ci};
43062306a36Sopenharmony_ciATTRIBUTE_GROUPS(fw_cfg_sysfs_entry);
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci/* sysfs_ops: find fw_cfg_[entry, attribute] and call appropriate show method */
43362306a36Sopenharmony_cistatic ssize_t fw_cfg_sysfs_attr_show(struct kobject *kobj, struct attribute *a,
43462306a36Sopenharmony_ci				      char *buf)
43562306a36Sopenharmony_ci{
43662306a36Sopenharmony_ci	struct fw_cfg_sysfs_entry *entry = to_entry(kobj);
43762306a36Sopenharmony_ci	struct fw_cfg_sysfs_attribute *attr = to_attr(a);
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	return attr->show(entry, buf);
44062306a36Sopenharmony_ci}
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_cistatic const struct sysfs_ops fw_cfg_sysfs_attr_ops = {
44362306a36Sopenharmony_ci	.show = fw_cfg_sysfs_attr_show,
44462306a36Sopenharmony_ci};
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci/* release: destructor, to be called via kobject_put() */
44762306a36Sopenharmony_cistatic void fw_cfg_sysfs_release_entry(struct kobject *kobj)
44862306a36Sopenharmony_ci{
44962306a36Sopenharmony_ci	struct fw_cfg_sysfs_entry *entry = to_entry(kobj);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	kfree(entry);
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci/* kobj_type: ties together all properties required to register an entry */
45562306a36Sopenharmony_cistatic struct kobj_type fw_cfg_sysfs_entry_ktype = {
45662306a36Sopenharmony_ci	.default_groups = fw_cfg_sysfs_entry_groups,
45762306a36Sopenharmony_ci	.sysfs_ops = &fw_cfg_sysfs_attr_ops,
45862306a36Sopenharmony_ci	.release = fw_cfg_sysfs_release_entry,
45962306a36Sopenharmony_ci};
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci/* raw-read method and attribute */
46262306a36Sopenharmony_cistatic ssize_t fw_cfg_sysfs_read_raw(struct file *filp, struct kobject *kobj,
46362306a36Sopenharmony_ci				     struct bin_attribute *bin_attr,
46462306a36Sopenharmony_ci				     char *buf, loff_t pos, size_t count)
46562306a36Sopenharmony_ci{
46662306a36Sopenharmony_ci	struct fw_cfg_sysfs_entry *entry = to_entry(kobj);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	if (pos > entry->size)
46962306a36Sopenharmony_ci		return -EINVAL;
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	if (count > entry->size - pos)
47262306a36Sopenharmony_ci		count = entry->size - pos;
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	return fw_cfg_read_blob(entry->select, buf, pos, count);
47562306a36Sopenharmony_ci}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_cistatic struct bin_attribute fw_cfg_sysfs_attr_raw = {
47862306a36Sopenharmony_ci	.attr = { .name = "raw", .mode = S_IRUSR },
47962306a36Sopenharmony_ci	.read = fw_cfg_sysfs_read_raw,
48062306a36Sopenharmony_ci};
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci/*
48362306a36Sopenharmony_ci * Create a kset subdirectory matching each '/' delimited dirname token
48462306a36Sopenharmony_ci * in 'name', starting with sysfs kset/folder 'dir'; At the end, create
48562306a36Sopenharmony_ci * a symlink directed at the given 'target'.
48662306a36Sopenharmony_ci * NOTE: We do this on a best-effort basis, since 'name' is not guaranteed
48762306a36Sopenharmony_ci * to be a well-behaved path name. Whenever a symlink vs. kset directory
48862306a36Sopenharmony_ci * name collision occurs, the kernel will issue big scary warnings while
48962306a36Sopenharmony_ci * refusing to add the offending link or directory. We follow up with our
49062306a36Sopenharmony_ci * own, slightly less scary error messages explaining the situation :)
49162306a36Sopenharmony_ci */
49262306a36Sopenharmony_cistatic int fw_cfg_build_symlink(struct kset *dir,
49362306a36Sopenharmony_ci				struct kobject *target, const char *name)
49462306a36Sopenharmony_ci{
49562306a36Sopenharmony_ci	int ret;
49662306a36Sopenharmony_ci	struct kset *subdir;
49762306a36Sopenharmony_ci	struct kobject *ko;
49862306a36Sopenharmony_ci	char *name_copy, *p, *tok;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	if (!dir || !target || !name || !*name)
50162306a36Sopenharmony_ci		return -EINVAL;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	/* clone a copy of name for parsing */
50462306a36Sopenharmony_ci	name_copy = p = kstrdup(name, GFP_KERNEL);
50562306a36Sopenharmony_ci	if (!name_copy)
50662306a36Sopenharmony_ci		return -ENOMEM;
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	/* create folders for each dirname token, then symlink for basename */
50962306a36Sopenharmony_ci	while ((tok = strsep(&p, "/")) && *tok) {
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci		/* last (basename) token? If so, add symlink here */
51262306a36Sopenharmony_ci		if (!p || !*p) {
51362306a36Sopenharmony_ci			ret = sysfs_create_link(&dir->kobj, target, tok);
51462306a36Sopenharmony_ci			break;
51562306a36Sopenharmony_ci		}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci		/* does the current dir contain an item named after tok ? */
51862306a36Sopenharmony_ci		ko = kset_find_obj(dir, tok);
51962306a36Sopenharmony_ci		if (ko) {
52062306a36Sopenharmony_ci			/* drop reference added by kset_find_obj */
52162306a36Sopenharmony_ci			kobject_put(ko);
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci			/* ko MUST be a kset - we're about to use it as one ! */
52462306a36Sopenharmony_ci			if (ko->ktype != dir->kobj.ktype) {
52562306a36Sopenharmony_ci				ret = -EINVAL;
52662306a36Sopenharmony_ci				break;
52762306a36Sopenharmony_ci			}
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci			/* descend into already existing subdirectory */
53062306a36Sopenharmony_ci			dir = to_kset(ko);
53162306a36Sopenharmony_ci		} else {
53262306a36Sopenharmony_ci			/* create new subdirectory kset */
53362306a36Sopenharmony_ci			subdir = kzalloc(sizeof(struct kset), GFP_KERNEL);
53462306a36Sopenharmony_ci			if (!subdir) {
53562306a36Sopenharmony_ci				ret = -ENOMEM;
53662306a36Sopenharmony_ci				break;
53762306a36Sopenharmony_ci			}
53862306a36Sopenharmony_ci			subdir->kobj.kset = dir;
53962306a36Sopenharmony_ci			subdir->kobj.ktype = dir->kobj.ktype;
54062306a36Sopenharmony_ci			ret = kobject_set_name(&subdir->kobj, "%s", tok);
54162306a36Sopenharmony_ci			if (ret) {
54262306a36Sopenharmony_ci				kfree(subdir);
54362306a36Sopenharmony_ci				break;
54462306a36Sopenharmony_ci			}
54562306a36Sopenharmony_ci			ret = kset_register(subdir);
54662306a36Sopenharmony_ci			if (ret) {
54762306a36Sopenharmony_ci				kfree(subdir);
54862306a36Sopenharmony_ci				break;
54962306a36Sopenharmony_ci			}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci			/* descend into newly created subdirectory */
55262306a36Sopenharmony_ci			dir = subdir;
55362306a36Sopenharmony_ci		}
55462306a36Sopenharmony_ci	}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/* we're done with cloned copy of name */
55762306a36Sopenharmony_ci	kfree(name_copy);
55862306a36Sopenharmony_ci	return ret;
55962306a36Sopenharmony_ci}
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci/* recursively unregister fw_cfg/by_name/ kset directory tree */
56262306a36Sopenharmony_cistatic void fw_cfg_kset_unregister_recursive(struct kset *kset)
56362306a36Sopenharmony_ci{
56462306a36Sopenharmony_ci	struct kobject *k, *next;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	list_for_each_entry_safe(k, next, &kset->list, entry)
56762306a36Sopenharmony_ci		/* all set members are ksets too, but check just in case... */
56862306a36Sopenharmony_ci		if (k->ktype == kset->kobj.ktype)
56962306a36Sopenharmony_ci			fw_cfg_kset_unregister_recursive(to_kset(k));
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	/* symlinks are cleanly and automatically removed with the directory */
57262306a36Sopenharmony_ci	kset_unregister(kset);
57362306a36Sopenharmony_ci}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci/* kobjects & kset representing top-level, by_key, and by_name folders */
57662306a36Sopenharmony_cistatic struct kobject *fw_cfg_top_ko;
57762306a36Sopenharmony_cistatic struct kobject *fw_cfg_sel_ko;
57862306a36Sopenharmony_cistatic struct kset *fw_cfg_fname_kset;
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci/* register an individual fw_cfg file */
58162306a36Sopenharmony_cistatic int fw_cfg_register_file(const struct fw_cfg_file *f)
58262306a36Sopenharmony_ci{
58362306a36Sopenharmony_ci	int err;
58462306a36Sopenharmony_ci	struct fw_cfg_sysfs_entry *entry;
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci#ifdef CONFIG_CRASH_CORE
58762306a36Sopenharmony_ci	if (fw_cfg_dma_enabled() &&
58862306a36Sopenharmony_ci		strcmp(f->name, FW_CFG_VMCOREINFO_FILENAME) == 0 &&
58962306a36Sopenharmony_ci		!is_kdump_kernel()) {
59062306a36Sopenharmony_ci		if (fw_cfg_write_vmcoreinfo(f) < 0)
59162306a36Sopenharmony_ci			pr_warn("fw_cfg: failed to write vmcoreinfo");
59262306a36Sopenharmony_ci	}
59362306a36Sopenharmony_ci#endif
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	/* allocate new entry */
59662306a36Sopenharmony_ci	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
59762306a36Sopenharmony_ci	if (!entry)
59862306a36Sopenharmony_ci		return -ENOMEM;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	/* set file entry information */
60162306a36Sopenharmony_ci	entry->size = be32_to_cpu(f->size);
60262306a36Sopenharmony_ci	entry->select = be16_to_cpu(f->select);
60362306a36Sopenharmony_ci	strscpy(entry->name, f->name, FW_CFG_MAX_FILE_PATH);
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	/* register entry under "/sys/firmware/qemu_fw_cfg/by_key/" */
60662306a36Sopenharmony_ci	err = kobject_init_and_add(&entry->kobj, &fw_cfg_sysfs_entry_ktype,
60762306a36Sopenharmony_ci				   fw_cfg_sel_ko, "%d", entry->select);
60862306a36Sopenharmony_ci	if (err)
60962306a36Sopenharmony_ci		goto err_put_entry;
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	/* add raw binary content access */
61262306a36Sopenharmony_ci	err = sysfs_create_bin_file(&entry->kobj, &fw_cfg_sysfs_attr_raw);
61362306a36Sopenharmony_ci	if (err)
61462306a36Sopenharmony_ci		goto err_del_entry;
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	/* try adding "/sys/firmware/qemu_fw_cfg/by_name/" symlink */
61762306a36Sopenharmony_ci	fw_cfg_build_symlink(fw_cfg_fname_kset, &entry->kobj, entry->name);
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci	/* success, add entry to global cache */
62062306a36Sopenharmony_ci	fw_cfg_sysfs_cache_enlist(entry);
62162306a36Sopenharmony_ci	return 0;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_cierr_del_entry:
62462306a36Sopenharmony_ci	kobject_del(&entry->kobj);
62562306a36Sopenharmony_cierr_put_entry:
62662306a36Sopenharmony_ci	kobject_put(&entry->kobj);
62762306a36Sopenharmony_ci	return err;
62862306a36Sopenharmony_ci}
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci/* iterate over all fw_cfg directory entries, registering each one */
63162306a36Sopenharmony_cistatic int fw_cfg_register_dir_entries(void)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	int ret = 0;
63462306a36Sopenharmony_ci	__be32 files_count;
63562306a36Sopenharmony_ci	u32 count, i;
63662306a36Sopenharmony_ci	struct fw_cfg_file *dir;
63762306a36Sopenharmony_ci	size_t dir_size;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	ret = fw_cfg_read_blob(FW_CFG_FILE_DIR, &files_count,
64062306a36Sopenharmony_ci			0, sizeof(files_count));
64162306a36Sopenharmony_ci	if (ret < 0)
64262306a36Sopenharmony_ci		return ret;
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	count = be32_to_cpu(files_count);
64562306a36Sopenharmony_ci	dir_size = count * sizeof(struct fw_cfg_file);
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	dir = kmalloc(dir_size, GFP_KERNEL);
64862306a36Sopenharmony_ci	if (!dir)
64962306a36Sopenharmony_ci		return -ENOMEM;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	ret = fw_cfg_read_blob(FW_CFG_FILE_DIR, dir,
65262306a36Sopenharmony_ci			sizeof(files_count), dir_size);
65362306a36Sopenharmony_ci	if (ret < 0)
65462306a36Sopenharmony_ci		goto end;
65562306a36Sopenharmony_ci
65662306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
65762306a36Sopenharmony_ci		ret = fw_cfg_register_file(&dir[i]);
65862306a36Sopenharmony_ci		if (ret)
65962306a36Sopenharmony_ci			break;
66062306a36Sopenharmony_ci	}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ciend:
66362306a36Sopenharmony_ci	kfree(dir);
66462306a36Sopenharmony_ci	return ret;
66562306a36Sopenharmony_ci}
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci/* unregister top-level or by_key folder */
66862306a36Sopenharmony_cistatic inline void fw_cfg_kobj_cleanup(struct kobject *kobj)
66962306a36Sopenharmony_ci{
67062306a36Sopenharmony_ci	kobject_del(kobj);
67162306a36Sopenharmony_ci	kobject_put(kobj);
67262306a36Sopenharmony_ci}
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_cistatic int fw_cfg_sysfs_probe(struct platform_device *pdev)
67562306a36Sopenharmony_ci{
67662306a36Sopenharmony_ci	int err;
67762306a36Sopenharmony_ci	__le32 rev;
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	/* NOTE: If we supported multiple fw_cfg devices, we'd first create
68062306a36Sopenharmony_ci	 * a subdirectory named after e.g. pdev->id, then hang per-device
68162306a36Sopenharmony_ci	 * by_key (and by_name) subdirectories underneath it. However, only
68262306a36Sopenharmony_ci	 * one fw_cfg device exist system-wide, so if one was already found
68362306a36Sopenharmony_ci	 * earlier, we might as well stop here.
68462306a36Sopenharmony_ci	 */
68562306a36Sopenharmony_ci	if (fw_cfg_sel_ko)
68662306a36Sopenharmony_ci		return -EBUSY;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	/* create by_key and by_name subdirs of /sys/firmware/qemu_fw_cfg/ */
68962306a36Sopenharmony_ci	err = -ENOMEM;
69062306a36Sopenharmony_ci	fw_cfg_sel_ko = kobject_create_and_add("by_key", fw_cfg_top_ko);
69162306a36Sopenharmony_ci	if (!fw_cfg_sel_ko)
69262306a36Sopenharmony_ci		goto err_sel;
69362306a36Sopenharmony_ci	fw_cfg_fname_kset = kset_create_and_add("by_name", NULL, fw_cfg_top_ko);
69462306a36Sopenharmony_ci	if (!fw_cfg_fname_kset)
69562306a36Sopenharmony_ci		goto err_name;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	/* initialize fw_cfg device i/o from platform data */
69862306a36Sopenharmony_ci	err = fw_cfg_do_platform_probe(pdev);
69962306a36Sopenharmony_ci	if (err)
70062306a36Sopenharmony_ci		goto err_probe;
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	/* get revision number, add matching top-level attribute */
70362306a36Sopenharmony_ci	err = fw_cfg_read_blob(FW_CFG_ID, &rev, 0, sizeof(rev));
70462306a36Sopenharmony_ci	if (err < 0)
70562306a36Sopenharmony_ci		goto err_probe;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	fw_cfg_rev = le32_to_cpu(rev);
70862306a36Sopenharmony_ci	err = sysfs_create_file(fw_cfg_top_ko, &fw_cfg_rev_attr.attr);
70962306a36Sopenharmony_ci	if (err)
71062306a36Sopenharmony_ci		goto err_rev;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	/* process fw_cfg file directory entry, registering each file */
71362306a36Sopenharmony_ci	err = fw_cfg_register_dir_entries();
71462306a36Sopenharmony_ci	if (err)
71562306a36Sopenharmony_ci		goto err_dir;
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	/* success */
71862306a36Sopenharmony_ci	pr_debug("fw_cfg: loaded.\n");
71962306a36Sopenharmony_ci	return 0;
72062306a36Sopenharmony_ci
72162306a36Sopenharmony_cierr_dir:
72262306a36Sopenharmony_ci	fw_cfg_sysfs_cache_cleanup();
72362306a36Sopenharmony_ci	sysfs_remove_file(fw_cfg_top_ko, &fw_cfg_rev_attr.attr);
72462306a36Sopenharmony_cierr_rev:
72562306a36Sopenharmony_ci	fw_cfg_io_cleanup();
72662306a36Sopenharmony_cierr_probe:
72762306a36Sopenharmony_ci	fw_cfg_kset_unregister_recursive(fw_cfg_fname_kset);
72862306a36Sopenharmony_cierr_name:
72962306a36Sopenharmony_ci	fw_cfg_kobj_cleanup(fw_cfg_sel_ko);
73062306a36Sopenharmony_cierr_sel:
73162306a36Sopenharmony_ci	return err;
73262306a36Sopenharmony_ci}
73362306a36Sopenharmony_ci
73462306a36Sopenharmony_cistatic int fw_cfg_sysfs_remove(struct platform_device *pdev)
73562306a36Sopenharmony_ci{
73662306a36Sopenharmony_ci	pr_debug("fw_cfg: unloading.\n");
73762306a36Sopenharmony_ci	fw_cfg_sysfs_cache_cleanup();
73862306a36Sopenharmony_ci	sysfs_remove_file(fw_cfg_top_ko, &fw_cfg_rev_attr.attr);
73962306a36Sopenharmony_ci	fw_cfg_io_cleanup();
74062306a36Sopenharmony_ci	fw_cfg_kset_unregister_recursive(fw_cfg_fname_kset);
74162306a36Sopenharmony_ci	fw_cfg_kobj_cleanup(fw_cfg_sel_ko);
74262306a36Sopenharmony_ci	return 0;
74362306a36Sopenharmony_ci}
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_cistatic const struct of_device_id fw_cfg_sysfs_mmio_match[] = {
74662306a36Sopenharmony_ci	{ .compatible = "qemu,fw-cfg-mmio", },
74762306a36Sopenharmony_ci	{},
74862306a36Sopenharmony_ci};
74962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, fw_cfg_sysfs_mmio_match);
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci#ifdef CONFIG_ACPI
75262306a36Sopenharmony_cistatic const struct acpi_device_id fw_cfg_sysfs_acpi_match[] = {
75362306a36Sopenharmony_ci	{ FW_CFG_ACPI_DEVICE_ID, },
75462306a36Sopenharmony_ci	{},
75562306a36Sopenharmony_ci};
75662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, fw_cfg_sysfs_acpi_match);
75762306a36Sopenharmony_ci#endif
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_cistatic struct platform_driver fw_cfg_sysfs_driver = {
76062306a36Sopenharmony_ci	.probe = fw_cfg_sysfs_probe,
76162306a36Sopenharmony_ci	.remove = fw_cfg_sysfs_remove,
76262306a36Sopenharmony_ci	.driver = {
76362306a36Sopenharmony_ci		.name = "fw_cfg",
76462306a36Sopenharmony_ci		.of_match_table = fw_cfg_sysfs_mmio_match,
76562306a36Sopenharmony_ci		.acpi_match_table = ACPI_PTR(fw_cfg_sysfs_acpi_match),
76662306a36Sopenharmony_ci	},
76762306a36Sopenharmony_ci};
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci#ifdef CONFIG_FW_CFG_SYSFS_CMDLINE
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_cistatic struct platform_device *fw_cfg_cmdline_dev;
77262306a36Sopenharmony_ci
77362306a36Sopenharmony_ci/* this probably belongs in e.g. include/linux/types.h,
77462306a36Sopenharmony_ci * but right now we are the only ones doing it...
77562306a36Sopenharmony_ci */
77662306a36Sopenharmony_ci#ifdef CONFIG_PHYS_ADDR_T_64BIT
77762306a36Sopenharmony_ci#define __PHYS_ADDR_PREFIX "ll"
77862306a36Sopenharmony_ci#else
77962306a36Sopenharmony_ci#define __PHYS_ADDR_PREFIX ""
78062306a36Sopenharmony_ci#endif
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci/* use special scanf/printf modifier for phys_addr_t, resource_size_t */
78362306a36Sopenharmony_ci#define PH_ADDR_SCAN_FMT "@%" __PHYS_ADDR_PREFIX "i%n" \
78462306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "i" \
78562306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "i%n" \
78662306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "i%n"
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci#define PH_ADDR_PR_1_FMT "0x%" __PHYS_ADDR_PREFIX "x@" \
78962306a36Sopenharmony_ci			 "0x%" __PHYS_ADDR_PREFIX "x"
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci#define PH_ADDR_PR_3_FMT PH_ADDR_PR_1_FMT \
79262306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "u" \
79362306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "u"
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci#define PH_ADDR_PR_4_FMT PH_ADDR_PR_3_FMT \
79662306a36Sopenharmony_ci			 ":%" __PHYS_ADDR_PREFIX "u"
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_cistatic int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
79962306a36Sopenharmony_ci{
80062306a36Sopenharmony_ci	struct resource res[4] = {};
80162306a36Sopenharmony_ci	char *str;
80262306a36Sopenharmony_ci	phys_addr_t base;
80362306a36Sopenharmony_ci	resource_size_t size, ctrl_off, data_off, dma_off;
80462306a36Sopenharmony_ci	int processed, consumed = 0;
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci	/* only one fw_cfg device can exist system-wide, so if one
80762306a36Sopenharmony_ci	 * was processed on the command line already, we might as
80862306a36Sopenharmony_ci	 * well stop here.
80962306a36Sopenharmony_ci	 */
81062306a36Sopenharmony_ci	if (fw_cfg_cmdline_dev) {
81162306a36Sopenharmony_ci		/* avoid leaking previously registered device */
81262306a36Sopenharmony_ci		platform_device_unregister(fw_cfg_cmdline_dev);
81362306a36Sopenharmony_ci		return -EINVAL;
81462306a36Sopenharmony_ci	}
81562306a36Sopenharmony_ci
81662306a36Sopenharmony_ci	/* consume "<size>" portion of command line argument */
81762306a36Sopenharmony_ci	size = memparse(arg, &str);
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	/* get "@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]" chunks */
82062306a36Sopenharmony_ci	processed = sscanf(str, PH_ADDR_SCAN_FMT,
82162306a36Sopenharmony_ci			   &base, &consumed,
82262306a36Sopenharmony_ci			   &ctrl_off, &data_off, &consumed,
82362306a36Sopenharmony_ci			   &dma_off, &consumed);
82462306a36Sopenharmony_ci
82562306a36Sopenharmony_ci	/* sscanf() must process precisely 1, 3 or 4 chunks:
82662306a36Sopenharmony_ci	 * <base> is mandatory, optionally followed by <ctrl_off>
82762306a36Sopenharmony_ci	 * and <data_off>, and <dma_off>;
82862306a36Sopenharmony_ci	 * there must be no extra characters after the last chunk,
82962306a36Sopenharmony_ci	 * so str[consumed] must be '\0'.
83062306a36Sopenharmony_ci	 */
83162306a36Sopenharmony_ci	if (str[consumed] ||
83262306a36Sopenharmony_ci	    (processed != 1 && processed != 3 && processed != 4))
83362306a36Sopenharmony_ci		return -EINVAL;
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	res[0].start = base;
83662306a36Sopenharmony_ci	res[0].end = base + size - 1;
83762306a36Sopenharmony_ci	res[0].flags = !strcmp(kp->name, "mmio") ? IORESOURCE_MEM :
83862306a36Sopenharmony_ci						   IORESOURCE_IO;
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	/* insert register offsets, if provided */
84162306a36Sopenharmony_ci	if (processed > 1) {
84262306a36Sopenharmony_ci		res[1].name = "ctrl";
84362306a36Sopenharmony_ci		res[1].start = ctrl_off;
84462306a36Sopenharmony_ci		res[1].flags = IORESOURCE_REG;
84562306a36Sopenharmony_ci		res[2].name = "data";
84662306a36Sopenharmony_ci		res[2].start = data_off;
84762306a36Sopenharmony_ci		res[2].flags = IORESOURCE_REG;
84862306a36Sopenharmony_ci	}
84962306a36Sopenharmony_ci	if (processed > 3) {
85062306a36Sopenharmony_ci		res[3].name = "dma";
85162306a36Sopenharmony_ci		res[3].start = dma_off;
85262306a36Sopenharmony_ci		res[3].flags = IORESOURCE_REG;
85362306a36Sopenharmony_ci	}
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	/* "processed" happens to nicely match the number of resources
85662306a36Sopenharmony_ci	 * we need to pass in to this platform device.
85762306a36Sopenharmony_ci	 */
85862306a36Sopenharmony_ci	fw_cfg_cmdline_dev = platform_device_register_simple("fw_cfg",
85962306a36Sopenharmony_ci					PLATFORM_DEVID_NONE, res, processed);
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(fw_cfg_cmdline_dev);
86262306a36Sopenharmony_ci}
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_cistatic int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
86562306a36Sopenharmony_ci{
86662306a36Sopenharmony_ci	/* stay silent if device was not configured via the command
86762306a36Sopenharmony_ci	 * line, or if the parameter name (ioport/mmio) doesn't match
86862306a36Sopenharmony_ci	 * the device setting
86962306a36Sopenharmony_ci	 */
87062306a36Sopenharmony_ci	if (!fw_cfg_cmdline_dev ||
87162306a36Sopenharmony_ci	    (!strcmp(kp->name, "mmio") ^
87262306a36Sopenharmony_ci	     (fw_cfg_cmdline_dev->resource[0].flags == IORESOURCE_MEM)))
87362306a36Sopenharmony_ci		return 0;
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci	switch (fw_cfg_cmdline_dev->num_resources) {
87662306a36Sopenharmony_ci	case 1:
87762306a36Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_1_FMT,
87862306a36Sopenharmony_ci				resource_size(&fw_cfg_cmdline_dev->resource[0]),
87962306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[0].start);
88062306a36Sopenharmony_ci	case 3:
88162306a36Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_3_FMT,
88262306a36Sopenharmony_ci				resource_size(&fw_cfg_cmdline_dev->resource[0]),
88362306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[0].start,
88462306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[1].start,
88562306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[2].start);
88662306a36Sopenharmony_ci	case 4:
88762306a36Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_4_FMT,
88862306a36Sopenharmony_ci				resource_size(&fw_cfg_cmdline_dev->resource[0]),
88962306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[0].start,
89062306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[1].start,
89162306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[2].start,
89262306a36Sopenharmony_ci				fw_cfg_cmdline_dev->resource[3].start);
89362306a36Sopenharmony_ci	}
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	/* Should never get here */
89662306a36Sopenharmony_ci	WARN(1, "Unexpected number of resources: %d\n",
89762306a36Sopenharmony_ci		fw_cfg_cmdline_dev->num_resources);
89862306a36Sopenharmony_ci	return 0;
89962306a36Sopenharmony_ci}
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_cistatic const struct kernel_param_ops fw_cfg_cmdline_param_ops = {
90262306a36Sopenharmony_ci	.set = fw_cfg_cmdline_set,
90362306a36Sopenharmony_ci	.get = fw_cfg_cmdline_get,
90462306a36Sopenharmony_ci};
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_cidevice_param_cb(ioport, &fw_cfg_cmdline_param_ops, NULL, S_IRUSR);
90762306a36Sopenharmony_cidevice_param_cb(mmio, &fw_cfg_cmdline_param_ops, NULL, S_IRUSR);
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci#endif /* CONFIG_FW_CFG_SYSFS_CMDLINE */
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_cistatic int __init fw_cfg_sysfs_init(void)
91262306a36Sopenharmony_ci{
91362306a36Sopenharmony_ci	int ret;
91462306a36Sopenharmony_ci
91562306a36Sopenharmony_ci	/* create /sys/firmware/qemu_fw_cfg/ top level directory */
91662306a36Sopenharmony_ci	fw_cfg_top_ko = kobject_create_and_add("qemu_fw_cfg", firmware_kobj);
91762306a36Sopenharmony_ci	if (!fw_cfg_top_ko)
91862306a36Sopenharmony_ci		return -ENOMEM;
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	ret = platform_driver_register(&fw_cfg_sysfs_driver);
92162306a36Sopenharmony_ci	if (ret)
92262306a36Sopenharmony_ci		fw_cfg_kobj_cleanup(fw_cfg_top_ko);
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_ci	return ret;
92562306a36Sopenharmony_ci}
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_cistatic void __exit fw_cfg_sysfs_exit(void)
92862306a36Sopenharmony_ci{
92962306a36Sopenharmony_ci	platform_driver_unregister(&fw_cfg_sysfs_driver);
93062306a36Sopenharmony_ci
93162306a36Sopenharmony_ci#ifdef CONFIG_FW_CFG_SYSFS_CMDLINE
93262306a36Sopenharmony_ci	platform_device_unregister(fw_cfg_cmdline_dev);
93362306a36Sopenharmony_ci#endif
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci	/* clean up /sys/firmware/qemu_fw_cfg/ */
93662306a36Sopenharmony_ci	fw_cfg_kobj_cleanup(fw_cfg_top_ko);
93762306a36Sopenharmony_ci}
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_cimodule_init(fw_cfg_sysfs_init);
94062306a36Sopenharmony_cimodule_exit(fw_cfg_sysfs_exit);
941