|
|
|
@ -10,20 +10,21 @@
|
|
|
|
|
* and select subsets of aarch64), a Device Tree node (on arm), or using
|
|
|
|
|
* a kernel module (or command line) parameter with the following syntax:
|
|
|
|
|
*
|
|
|
|
|
* [qemu_fw_cfg.]ioport=<size>@<base>[:<ctrl_off>:<data_off>]
|
|
|
|
|
* [qemu_fw_cfg.]ioport=<size>@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]
|
|
|
|
|
* or
|
|
|
|
|
* [qemu_fw_cfg.]mmio=<size>@<base>[:<ctrl_off>:<data_off>]
|
|
|
|
|
* [qemu_fw_cfg.]mmio=<size>@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]
|
|
|
|
|
*
|
|
|
|
|
* where:
|
|
|
|
|
* <size> := size of ioport or mmio range
|
|
|
|
|
* <base> := physical base address of ioport or mmio range
|
|
|
|
|
* <ctrl_off> := (optional) offset of control register
|
|
|
|
|
* <data_off> := (optional) offset of data register
|
|
|
|
|
* <dma_off> := (optional) offset of dma register
|
|
|
|
|
*
|
|
|
|
|
* e.g.:
|
|
|
|
|
* qemu_fw_cfg.ioport=2@0x510:0:1 (the default on x86)
|
|
|
|
|
* qemu_fw_cfg.ioport=12@0x510:0:1:4 (the default on x86)
|
|
|
|
|
* or
|
|
|
|
|
* qemu_fw_cfg.mmio=0xA@0x9020000:8:0 (the default on arm)
|
|
|
|
|
* qemu_fw_cfg.mmio=16@0x9020000:8:0:16 (the default on arm)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
@ -32,29 +33,17 @@
|
|
|
|
|
#include <linux/slab.h>
|
|
|
|
|
#include <linux/io.h>
|
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
|
#include <uapi/linux/qemu_fw_cfg.h>
|
|
|
|
|
#include <linux/delay.h>
|
|
|
|
|
#include <linux/crash_dump.h>
|
|
|
|
|
#include <linux/crash_core.h>
|
|
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Gabriel L. Somlo <somlo@cmu.edu>");
|
|
|
|
|
MODULE_DESCRIPTION("QEMU fw_cfg sysfs support");
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
|
|
/* selector key values for "well-known" fw_cfg entries */
|
|
|
|
|
#define FW_CFG_SIGNATURE 0x00
|
|
|
|
|
#define FW_CFG_ID 0x01
|
|
|
|
|
#define FW_CFG_FILE_DIR 0x19
|
|
|
|
|
|
|
|
|
|
/* size in bytes of fw_cfg signature */
|
|
|
|
|
#define FW_CFG_SIG_SIZE 4
|
|
|
|
|
|
|
|
|
|
/* fw_cfg "file name" is up to 56 characters (including terminating nul) */
|
|
|
|
|
#define FW_CFG_MAX_FILE_PATH 56
|
|
|
|
|
|
|
|
|
|
/* fw_cfg file directory entry type */
|
|
|
|
|
struct fw_cfg_file {
|
|
|
|
|
u32 size;
|
|
|
|
|
u16 select;
|
|
|
|
|
u16 reserved;
|
|
|
|
|
char name[FW_CFG_MAX_FILE_PATH];
|
|
|
|
|
};
|
|
|
|
|
/* fw_cfg revision attribute, in /sys/firmware/qemu_fw_cfg top-level dir. */
|
|
|
|
|
static u32 fw_cfg_rev;
|
|
|
|
|
|
|
|
|
|
/* fw_cfg device i/o register addresses */
|
|
|
|
|
static bool fw_cfg_is_mmio;
|
|
|
|
@ -63,19 +52,83 @@ static resource_size_t fw_cfg_p_size;
|
|
|
|
|
static void __iomem *fw_cfg_dev_base;
|
|
|
|
|
static void __iomem *fw_cfg_reg_ctrl;
|
|
|
|
|
static void __iomem *fw_cfg_reg_data;
|
|
|
|
|
static void __iomem *fw_cfg_reg_dma;
|
|
|
|
|
|
|
|
|
|
/* atomic access to fw_cfg device (potentially slow i/o, so using mutex) */
|
|
|
|
|
static DEFINE_MUTEX(fw_cfg_dev_lock);
|
|
|
|
|
|
|
|
|
|
/* pick appropriate endianness for selector key */
|
|
|
|
|
static inline u16 fw_cfg_sel_endianness(u16 key)
|
|
|
|
|
static void fw_cfg_sel_endianness(u16 key)
|
|
|
|
|
{
|
|
|
|
|
return fw_cfg_is_mmio ? cpu_to_be16(key) : cpu_to_le16(key);
|
|
|
|
|
if (fw_cfg_is_mmio)
|
|
|
|
|
iowrite16be(key, fw_cfg_reg_ctrl);
|
|
|
|
|
else
|
|
|
|
|
iowrite16(key, fw_cfg_reg_ctrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_CRASH_CORE
|
|
|
|
|
static inline bool fw_cfg_dma_enabled(void)
|
|
|
|
|
{
|
|
|
|
|
return (fw_cfg_rev & FW_CFG_VERSION_DMA) && fw_cfg_reg_dma;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* qemu fw_cfg device is sync today, but spec says it may become async */
|
|
|
|
|
static void fw_cfg_wait_for_control(struct fw_cfg_dma_access *d)
|
|
|
|
|
{
|
|
|
|
|
for (;;) {
|
|
|
|
|
u32 ctrl = be32_to_cpu(READ_ONCE(d->control));
|
|
|
|
|
|
|
|
|
|
/* do not reorder the read to d->control */
|
|
|
|
|
rmb();
|
|
|
|
|
if ((ctrl & ~FW_CFG_DMA_CTL_ERROR) == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
cpu_relax();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t fw_cfg_dma_transfer(void *address, u32 length, u32 control)
|
|
|
|
|
{
|
|
|
|
|
phys_addr_t dma;
|
|
|
|
|
struct fw_cfg_dma_access *d = NULL;
|
|
|
|
|
ssize_t ret = length;
|
|
|
|
|
|
|
|
|
|
d = kmalloc(sizeof(*d), GFP_KERNEL);
|
|
|
|
|
if (!d) {
|
|
|
|
|
ret = -ENOMEM;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* fw_cfg device does not need IOMMU protection, so use physical addresses */
|
|
|
|
|
*d = (struct fw_cfg_dma_access) {
|
|
|
|
|
.address = cpu_to_be64(address ? virt_to_phys(address) : 0),
|
|
|
|
|
.length = cpu_to_be32(length),
|
|
|
|
|
.control = cpu_to_be32(control)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dma = virt_to_phys(d);
|
|
|
|
|
|
|
|
|
|
iowrite32be((u64)dma >> 32, fw_cfg_reg_dma);
|
|
|
|
|
/* force memory to sync before notifying device via MMIO */
|
|
|
|
|
wmb();
|
|
|
|
|
iowrite32be(dma, fw_cfg_reg_dma + 4);
|
|
|
|
|
|
|
|
|
|
fw_cfg_wait_for_control(d);
|
|
|
|
|
|
|
|
|
|
if (be32_to_cpu(READ_ONCE(d->control)) & FW_CFG_DMA_CTL_ERROR) {
|
|
|
|
|
ret = -EIO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
kfree(d);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* read chunk of given fw_cfg blob (caller responsible for sanity-check) */
|
|
|
|
|
static inline void fw_cfg_read_blob(u16 key,
|
|
|
|
|
void *buf, loff_t pos, size_t count)
|
|
|
|
|
static ssize_t fw_cfg_read_blob(u16 key,
|
|
|
|
|
void *buf, loff_t pos, size_t count)
|
|
|
|
|
{
|
|
|
|
|
u32 glk = -1U;
|
|
|
|
|
acpi_status status;
|
|
|
|
@ -88,19 +141,61 @@ static inline void fw_cfg_read_blob(u16 key,
|
|
|
|
|
/* Should never get here */
|
|
|
|
|
WARN(1, "fw_cfg_read_blob: Failed to lock ACPI!\n");
|
|
|
|
|
memset(buf, 0, count);
|
|
|
|
|
return;
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mutex_lock(&fw_cfg_dev_lock);
|
|
|
|
|
iowrite16(fw_cfg_sel_endianness(key), fw_cfg_reg_ctrl);
|
|
|
|
|
fw_cfg_sel_endianness(key);
|
|
|
|
|
while (pos-- > 0)
|
|
|
|
|
ioread8(fw_cfg_reg_data);
|
|
|
|
|
ioread8_rep(fw_cfg_reg_data, buf, count);
|
|
|
|
|
mutex_unlock(&fw_cfg_dev_lock);
|
|
|
|
|
|
|
|
|
|
acpi_release_global_lock(glk);
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_CRASH_CORE
|
|
|
|
|
/* write chunk of given fw_cfg blob (caller responsible for sanity-check) */
|
|
|
|
|
static ssize_t fw_cfg_write_blob(u16 key,
|
|
|
|
|
void *buf, loff_t pos, size_t count)
|
|
|
|
|
{
|
|
|
|
|
u32 glk = -1U;
|
|
|
|
|
acpi_status status;
|
|
|
|
|
ssize_t ret = count;
|
|
|
|
|
|
|
|
|
|
/* If we have ACPI, ensure mutual exclusion against any potential
|
|
|
|
|
* device access by the firmware, e.g. via AML methods:
|
|
|
|
|
*/
|
|
|
|
|
status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
|
|
|
|
|
if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
|
|
|
|
|
/* Should never get here */
|
|
|
|
|
WARN(1, "%s: Failed to lock ACPI!\n", __func__);
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mutex_lock(&fw_cfg_dev_lock);
|
|
|
|
|
if (pos == 0) {
|
|
|
|
|
ret = fw_cfg_dma_transfer(buf, count, key << 16
|
|
|
|
|
| FW_CFG_DMA_CTL_SELECT
|
|
|
|
|
| FW_CFG_DMA_CTL_WRITE);
|
|
|
|
|
} else {
|
|
|
|
|
fw_cfg_sel_endianness(key);
|
|
|
|
|
ret = fw_cfg_dma_transfer(NULL, pos, FW_CFG_DMA_CTL_SKIP);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
goto end;
|
|
|
|
|
ret = fw_cfg_dma_transfer(buf, count, FW_CFG_DMA_CTL_WRITE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
mutex_unlock(&fw_cfg_dev_lock);
|
|
|
|
|
|
|
|
|
|
acpi_release_global_lock(glk);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
#endif /* CONFIG_CRASH_CORE */
|
|
|
|
|
|
|
|
|
|
/* clean up fw_cfg device i/o */
|
|
|
|
|
static void fw_cfg_io_cleanup(void)
|
|
|
|
|
{
|
|
|
|
@ -118,12 +213,14 @@ static void fw_cfg_io_cleanup(void)
|
|
|
|
|
# if (defined(CONFIG_ARM) || defined(CONFIG_ARM64))
|
|
|
|
|
# define FW_CFG_CTRL_OFF 0x08
|
|
|
|
|
# define FW_CFG_DATA_OFF 0x00
|
|
|
|
|
# define FW_CFG_DMA_OFF 0x10
|
|
|
|
|
# elif (defined(CONFIG_PPC_PMAC) || defined(CONFIG_SPARC32)) /* ppc/mac,sun4m */
|
|
|
|
|
# define FW_CFG_CTRL_OFF 0x00
|
|
|
|
|
# define FW_CFG_DATA_OFF 0x02
|
|
|
|
|
# elif (defined(CONFIG_X86) || defined(CONFIG_SPARC64)) /* x86, sun4u */
|
|
|
|
|
# define FW_CFG_CTRL_OFF 0x00
|
|
|
|
|
# define FW_CFG_DATA_OFF 0x01
|
|
|
|
|
# define FW_CFG_DMA_OFF 0x04
|
|
|
|
|
# else
|
|
|
|
|
# error "QEMU FW_CFG not available on this architecture!"
|
|
|
|
|
# endif
|
|
|
|
@ -133,7 +230,7 @@ static void fw_cfg_io_cleanup(void)
|
|
|
|
|
static int fw_cfg_do_platform_probe(struct platform_device *pdev)
|
|
|
|
|
{
|
|
|
|
|
char sig[FW_CFG_SIG_SIZE];
|
|
|
|
|
struct resource *range, *ctrl, *data;
|
|
|
|
|
struct resource *range, *ctrl, *data, *dma;
|
|
|
|
|
|
|
|
|
|
/* acquire i/o range details */
|
|
|
|
|
fw_cfg_is_mmio = false;
|
|
|
|
@ -170,6 +267,7 @@ static int fw_cfg_do_platform_probe(struct platform_device *pdev)
|
|
|
|
|
/* were custom register offsets provided (e.g. on the command line)? */
|
|
|
|
|
ctrl = platform_get_resource_byname(pdev, IORESOURCE_REG, "ctrl");
|
|
|
|
|
data = platform_get_resource_byname(pdev, IORESOURCE_REG, "data");
|
|
|
|
|
dma = platform_get_resource_byname(pdev, IORESOURCE_REG, "dma");
|
|
|
|
|
if (ctrl && data) {
|
|
|
|
|
fw_cfg_reg_ctrl = fw_cfg_dev_base + ctrl->start;
|
|
|
|
|
fw_cfg_reg_data = fw_cfg_dev_base + data->start;
|
|
|
|
@ -179,9 +277,17 @@ static int fw_cfg_do_platform_probe(struct platform_device *pdev)
|
|
|
|
|
fw_cfg_reg_data = fw_cfg_dev_base + FW_CFG_DATA_OFF;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dma)
|
|
|
|
|
fw_cfg_reg_dma = fw_cfg_dev_base + dma->start;
|
|
|
|
|
#ifdef FW_CFG_DMA_OFF
|
|
|
|
|
else
|
|
|
|
|
fw_cfg_reg_dma = fw_cfg_dev_base + FW_CFG_DMA_OFF;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* verify fw_cfg device signature */
|
|
|
|
|
fw_cfg_read_blob(FW_CFG_SIGNATURE, sig, 0, FW_CFG_SIG_SIZE);
|
|
|
|
|
if (memcmp(sig, "QEMU", FW_CFG_SIG_SIZE) != 0) {
|
|
|
|
|
if (fw_cfg_read_blob(FW_CFG_SIGNATURE, sig,
|
|
|
|
|
0, FW_CFG_SIG_SIZE) < 0 ||
|
|
|
|
|
memcmp(sig, "QEMU", FW_CFG_SIG_SIZE) != 0) {
|
|
|
|
|
fw_cfg_io_cleanup();
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
@ -189,9 +295,6 @@ static int fw_cfg_do_platform_probe(struct platform_device *pdev)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* fw_cfg revision attribute, in /sys/firmware/qemu_fw_cfg top-level dir. */
|
|
|
|
|
static u32 fw_cfg_rev;
|
|
|
|
|
|
|
|
|
|
static ssize_t fw_cfg_showrev(struct kobject *k, struct attribute *a, char *buf)
|
|
|
|
|
{
|
|
|
|
|
return sprintf(buf, "%u\n", fw_cfg_rev);
|
|
|
|
@ -208,10 +311,38 @@ static const struct {
|
|
|
|
|
/* fw_cfg_sysfs_entry type */
|
|
|
|
|
struct fw_cfg_sysfs_entry {
|
|
|
|
|
struct kobject kobj;
|
|
|
|
|
struct fw_cfg_file f;
|
|
|
|
|
u32 size;
|
|
|
|
|
u16 select;
|
|
|
|
|
char name[FW_CFG_MAX_FILE_PATH];
|
|
|
|
|
struct list_head list;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_CRASH_CORE
|
|
|
|
|
static ssize_t fw_cfg_write_vmcoreinfo(const struct fw_cfg_file *f)
|
|
|
|
|
{
|
|
|
|
|
static struct fw_cfg_vmcoreinfo *data;
|
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
|
|
data = kmalloc(sizeof(struct fw_cfg_vmcoreinfo), GFP_KERNEL);
|
|
|
|
|
if (!data)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
*data = (struct fw_cfg_vmcoreinfo) {
|
|
|
|
|
.guest_format = cpu_to_le16(FW_CFG_VMCOREINFO_FORMAT_ELF),
|
|
|
|
|
.size = cpu_to_le32(VMCOREINFO_NOTE_SIZE),
|
|
|
|
|
.paddr = cpu_to_le64(paddr_vmcoreinfo_note())
|
|
|
|
|
};
|
|
|
|
|
/* spare ourself reading host format support for now since we
|
|
|
|
|
* don't know what else to format - host may ignore ours
|
|
|
|
|
*/
|
|
|
|
|
ret = fw_cfg_write_blob(be16_to_cpu(f->select), data,
|
|
|
|
|
0, sizeof(struct fw_cfg_vmcoreinfo));
|
|
|
|
|
|
|
|
|
|
kfree(data);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
#endif /* CONFIG_CRASH_CORE */
|
|
|
|
|
|
|
|
|
|
/* get fw_cfg_sysfs_entry from kobject member */
|
|
|
|
|
static inline struct fw_cfg_sysfs_entry *to_entry(struct kobject *kobj)
|
|
|
|
|
{
|
|
|
|
@ -272,17 +403,17 @@ struct fw_cfg_sysfs_attribute fw_cfg_sysfs_attr_##_attr = { \
|
|
|
|
|
|
|
|
|
|
static ssize_t fw_cfg_sysfs_show_size(struct fw_cfg_sysfs_entry *e, char *buf)
|
|
|
|
|
{
|
|
|
|
|
return sprintf(buf, "%u\n", e->f.size);
|
|
|
|
|
return sprintf(buf, "%u\n", e->size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t fw_cfg_sysfs_show_key(struct fw_cfg_sysfs_entry *e, char *buf)
|
|
|
|
|
{
|
|
|
|
|
return sprintf(buf, "%u\n", e->f.select);
|
|
|
|
|
return sprintf(buf, "%u\n", e->select);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ssize_t fw_cfg_sysfs_show_name(struct fw_cfg_sysfs_entry *e, char *buf)
|
|
|
|
|
{
|
|
|
|
|
return sprintf(buf, "%s\n", e->f.name);
|
|
|
|
|
return sprintf(buf, "%s\n", e->name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FW_CFG_SYSFS_ATTR(size);
|
|
|
|
@ -333,14 +464,13 @@ static ssize_t fw_cfg_sysfs_read_raw(struct file *filp, struct kobject *kobj,
|
|
|
|
|
{
|
|
|
|
|
struct fw_cfg_sysfs_entry *entry = to_entry(kobj);
|
|
|
|
|
|
|
|
|
|
if (pos > entry->f.size)
|
|
|
|
|
if (pos > entry->size)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
if (count > entry->f.size - pos)
|
|
|
|
|
count = entry->f.size - pos;
|
|
|
|
|
if (count > entry->size - pos)
|
|
|
|
|
count = entry->size - pos;
|
|
|
|
|
|
|
|
|
|
fw_cfg_read_blob(entry->f.select, buf, pos, count);
|
|
|
|
|
return count;
|
|
|
|
|
return fw_cfg_read_blob(entry->select, buf, pos, count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct bin_attribute fw_cfg_sysfs_attr_raw = {
|
|
|
|
@ -452,17 +582,28 @@ static int fw_cfg_register_file(const struct fw_cfg_file *f)
|
|
|
|
|
int err;
|
|
|
|
|
struct fw_cfg_sysfs_entry *entry;
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_CRASH_CORE
|
|
|
|
|
if (fw_cfg_dma_enabled() &&
|
|
|
|
|
strcmp(f->name, FW_CFG_VMCOREINFO_FILENAME) == 0 &&
|
|
|
|
|
!is_kdump_kernel()) {
|
|
|
|
|
if (fw_cfg_write_vmcoreinfo(f) < 0)
|
|
|
|
|
pr_warn("fw_cfg: failed to write vmcoreinfo");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* allocate new entry */
|
|
|
|
|
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
|
|
|
if (!entry)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
/* set file entry information */
|
|
|
|
|
memcpy(&entry->f, f, sizeof(struct fw_cfg_file));
|
|
|
|
|
entry->size = be32_to_cpu(f->size);
|
|
|
|
|
entry->select = be16_to_cpu(f->select);
|
|
|
|
|
memcpy(entry->name, f->name, FW_CFG_MAX_FILE_PATH);
|
|
|
|
|
|
|
|
|
|
/* register entry under "/sys/firmware/qemu_fw_cfg/by_key/" */
|
|
|
|
|
err = kobject_init_and_add(&entry->kobj, &fw_cfg_sysfs_entry_ktype,
|
|
|
|
|
fw_cfg_sel_ko, "%d", entry->f.select);
|
|
|
|
|
fw_cfg_sel_ko, "%d", entry->select);
|
|
|
|
|
if (err)
|
|
|
|
|
goto err_register;
|
|
|
|
|
|
|
|
|
@ -472,7 +613,7 @@ static int fw_cfg_register_file(const struct fw_cfg_file *f)
|
|
|
|
|
goto err_add_raw;
|
|
|
|
|
|
|
|
|
|
/* try adding "/sys/firmware/qemu_fw_cfg/by_name/" symlink */
|
|
|
|
|
fw_cfg_build_symlink(fw_cfg_fname_kset, &entry->kobj, entry->f.name);
|
|
|
|
|
fw_cfg_build_symlink(fw_cfg_fname_kset, &entry->kobj, entry->name);
|
|
|
|
|
|
|
|
|
|
/* success, add entry to global cache */
|
|
|
|
|
fw_cfg_sysfs_cache_enlist(entry);
|
|
|
|
@ -489,28 +630,35 @@ err_register:
|
|
|
|
|
static int fw_cfg_register_dir_entries(void)
|
|
|
|
|
{
|
|
|
|
|
int ret = 0;
|
|
|
|
|
__be32 files_count;
|
|
|
|
|
u32 count, i;
|
|
|
|
|
struct fw_cfg_file *dir;
|
|
|
|
|
size_t dir_size;
|
|
|
|
|
|
|
|
|
|
fw_cfg_read_blob(FW_CFG_FILE_DIR, &count, 0, sizeof(count));
|
|
|
|
|
count = be32_to_cpu(count);
|
|
|
|
|
ret = fw_cfg_read_blob(FW_CFG_FILE_DIR, &files_count,
|
|
|
|
|
0, sizeof(files_count));
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
count = be32_to_cpu(files_count);
|
|
|
|
|
dir_size = count * sizeof(struct fw_cfg_file);
|
|
|
|
|
|
|
|
|
|
dir = kmalloc(dir_size, GFP_KERNEL);
|
|
|
|
|
if (!dir)
|
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
|
|
fw_cfg_read_blob(FW_CFG_FILE_DIR, dir, sizeof(count), dir_size);
|
|
|
|
|
ret = fw_cfg_read_blob(FW_CFG_FILE_DIR, dir,
|
|
|
|
|
sizeof(files_count), dir_size);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
|
dir[i].size = be32_to_cpu(dir[i].size);
|
|
|
|
|
dir[i].select = be16_to_cpu(dir[i].select);
|
|
|
|
|
ret = fw_cfg_register_file(&dir[i]);
|
|
|
|
|
if (ret)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
kfree(dir);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
@ -525,6 +673,7 @@ static inline void fw_cfg_kobj_cleanup(struct kobject *kobj)
|
|
|
|
|
static int fw_cfg_sysfs_probe(struct platform_device *pdev)
|
|
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
__le32 rev;
|
|
|
|
|
|
|
|
|
|
/* NOTE: If we supported multiple fw_cfg devices, we'd first create
|
|
|
|
|
* a subdirectory named after e.g. pdev->id, then hang per-device
|
|
|
|
@ -550,8 +699,11 @@ static int fw_cfg_sysfs_probe(struct platform_device *pdev)
|
|
|
|
|
goto err_probe;
|
|
|
|
|
|
|
|
|
|
/* get revision number, add matching top-level attribute */
|
|
|
|
|
fw_cfg_read_blob(FW_CFG_ID, &fw_cfg_rev, 0, sizeof(fw_cfg_rev));
|
|
|
|
|
fw_cfg_rev = le32_to_cpu(fw_cfg_rev);
|
|
|
|
|
err = fw_cfg_read_blob(FW_CFG_ID, &rev, 0, sizeof(rev));
|
|
|
|
|
if (err < 0)
|
|
|
|
|
goto err_probe;
|
|
|
|
|
|
|
|
|
|
fw_cfg_rev = le32_to_cpu(rev);
|
|
|
|
|
err = sysfs_create_file(fw_cfg_top_ko, &fw_cfg_rev_attr.attr);
|
|
|
|
|
if (err)
|
|
|
|
|
goto err_rev;
|
|
|
|
@ -597,7 +749,7 @@ MODULE_DEVICE_TABLE(of, fw_cfg_sysfs_mmio_match);
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_ACPI
|
|
|
|
|
static const struct acpi_device_id fw_cfg_sysfs_acpi_match[] = {
|
|
|
|
|
{ "QEMU0002", },
|
|
|
|
|
{ FW_CFG_ACPI_DEVICE_ID, },
|
|
|
|
|
{},
|
|
|
|
|
};
|
|
|
|
|
MODULE_DEVICE_TABLE(acpi, fw_cfg_sysfs_acpi_match);
|
|
|
|
@ -629,6 +781,7 @@ static struct platform_device *fw_cfg_cmdline_dev;
|
|
|
|
|
/* use special scanf/printf modifier for phys_addr_t, resource_size_t */
|
|
|
|
|
#define PH_ADDR_SCAN_FMT "@%" __PHYS_ADDR_PREFIX "i%n" \
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "i" \
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "i%n" \
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "i%n"
|
|
|
|
|
|
|
|
|
|
#define PH_ADDR_PR_1_FMT "0x%" __PHYS_ADDR_PREFIX "x@" \
|
|
|
|
@ -638,12 +791,15 @@ static struct platform_device *fw_cfg_cmdline_dev;
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "u" \
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "u"
|
|
|
|
|
|
|
|
|
|
#define PH_ADDR_PR_4_FMT PH_ADDR_PR_3_FMT \
|
|
|
|
|
":%" __PHYS_ADDR_PREFIX "u"
|
|
|
|
|
|
|
|
|
|
static int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
|
|
|
|
|
{
|
|
|
|
|
struct resource res[3] = {};
|
|
|
|
|
struct resource res[4] = {};
|
|
|
|
|
char *str;
|
|
|
|
|
phys_addr_t base;
|
|
|
|
|
resource_size_t size, ctrl_off, data_off;
|
|
|
|
|
resource_size_t size, ctrl_off, data_off, dma_off;
|
|
|
|
|
int processed, consumed = 0;
|
|
|
|
|
|
|
|
|
|
/* only one fw_cfg device can exist system-wide, so if one
|
|
|
|
@ -659,19 +815,20 @@ static int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
|
|
|
|
|
/* consume "<size>" portion of command line argument */
|
|
|
|
|
size = memparse(arg, &str);
|
|
|
|
|
|
|
|
|
|
/* get "@<base>[:<ctrl_off>:<data_off>]" chunks */
|
|
|
|
|
/* get "@<base>[:<ctrl_off>:<data_off>[:<dma_off>]]" chunks */
|
|
|
|
|
processed = sscanf(str, PH_ADDR_SCAN_FMT,
|
|
|
|
|
&base, &consumed,
|
|
|
|
|
&ctrl_off, &data_off, &consumed);
|
|
|
|
|
&ctrl_off, &data_off, &consumed,
|
|
|
|
|
&dma_off, &consumed);
|
|
|
|
|
|
|
|
|
|
/* sscanf() must process precisely 1 or 3 chunks:
|
|
|
|
|
/* sscanf() must process precisely 1, 3 or 4 chunks:
|
|
|
|
|
* <base> is mandatory, optionally followed by <ctrl_off>
|
|
|
|
|
* and <data_off>;
|
|
|
|
|
* and <data_off>, and <dma_off>;
|
|
|
|
|
* there must be no extra characters after the last chunk,
|
|
|
|
|
* so str[consumed] must be '\0'.
|
|
|
|
|
*/
|
|
|
|
|
if (str[consumed] ||
|
|
|
|
|
(processed != 1 && processed != 3))
|
|
|
|
|
(processed != 1 && processed != 3 && processed != 4))
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
|
|
res[0].start = base;
|
|
|
|
@ -688,6 +845,11 @@ static int fw_cfg_cmdline_set(const char *arg, const struct kernel_param *kp)
|
|
|
|
|
res[2].start = data_off;
|
|
|
|
|
res[2].flags = IORESOURCE_REG;
|
|
|
|
|
}
|
|
|
|
|
if (processed > 3) {
|
|
|
|
|
res[3].name = "dma";
|
|
|
|
|
res[3].start = dma_off;
|
|
|
|
|
res[3].flags = IORESOURCE_REG;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* "processed" happens to nicely match the number of resources
|
|
|
|
|
* we need to pass in to this platform device.
|
|
|
|
@ -720,6 +882,13 @@ static int fw_cfg_cmdline_get(char *buf, const struct kernel_param *kp)
|
|
|
|
|
fw_cfg_cmdline_dev->resource[0].start,
|
|
|
|
|
fw_cfg_cmdline_dev->resource[1].start,
|
|
|
|
|
fw_cfg_cmdline_dev->resource[2].start);
|
|
|
|
|
case 4:
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, PH_ADDR_PR_4_FMT,
|
|
|
|
|
resource_size(&fw_cfg_cmdline_dev->resource[0]),
|
|
|
|
|
fw_cfg_cmdline_dev->resource[0].start,
|
|
|
|
|
fw_cfg_cmdline_dev->resource[1].start,
|
|
|
|
|
fw_cfg_cmdline_dev->resource[2].start,
|
|
|
|
|
fw_cfg_cmdline_dev->resource[3].start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Should never get here */
|
|
|
|
|