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