| /* |
| * Architecture specific sysfs attributes in /sys/kernel |
| * |
| * Copyright (C) 2007, Intel Corp. |
| * Huang Ying <ying.huang@intel.com> |
| * Copyright (C) 2013, 2013 Red Hat, Inc. |
| * Dave Young <dyoung@redhat.com> |
| * |
| * This file is released under the GPLv2 |
| */ |
| |
| #include <linux/kobject.h> |
| #include <linux/string.h> |
| #include <linux/sysfs.h> |
| #include <linux/init.h> |
| #include <linux/stat.h> |
| #include <linux/slab.h> |
| #include <linux/mm.h> |
| #include <linux/io.h> |
| |
| #include <asm/setup.h> |
| |
| static ssize_t version_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "0x%04x\n", boot_params.hdr.version); |
| } |
| |
| static struct kobj_attribute boot_params_version_attr = __ATTR_RO(version); |
| |
| static ssize_t boot_params_data_read(struct file *fp, struct kobject *kobj, |
| struct bin_attribute *bin_attr, |
| char *buf, loff_t off, size_t count) |
| { |
| memcpy(buf, (void *)&boot_params + off, count); |
| return count; |
| } |
| |
| static struct bin_attribute boot_params_data_attr = { |
| .attr = { |
| .name = "data", |
| .mode = S_IRUGO, |
| }, |
| .read = boot_params_data_read, |
| .size = sizeof(boot_params), |
| }; |
| |
| static struct attribute *boot_params_version_attrs[] = { |
| &boot_params_version_attr.attr, |
| NULL, |
| }; |
| |
| static struct bin_attribute *boot_params_data_attrs[] = { |
| &boot_params_data_attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group boot_params_attr_group = { |
| .attrs = boot_params_version_attrs, |
| .bin_attrs = boot_params_data_attrs, |
| }; |
| |
| static int kobj_to_setup_data_nr(struct kobject *kobj, int *nr) |
| { |
| const char *name; |
| |
| name = kobject_name(kobj); |
| return kstrtoint(name, 10, nr); |
| } |
| |
| static int get_setup_data_paddr(int nr, u64 *paddr) |
| { |
| int i = 0; |
| struct setup_data *data; |
| u64 pa_data = boot_params.hdr.setup_data; |
| |
| while (pa_data) { |
| if (nr == i) { |
| *paddr = pa_data; |
| return 0; |
| } |
| data = memremap(pa_data, sizeof(*data), MEMREMAP_WB); |
| if (!data) |
| return -ENOMEM; |
| |
| pa_data = data->next; |
| memunmap(data); |
| i++; |
| } |
| return -EINVAL; |
| } |
| |
| static int __init get_setup_data_size(int nr, size_t *size) |
| { |
| int i = 0; |
| struct setup_data *data; |
| u64 pa_data = boot_params.hdr.setup_data; |
| |
| while (pa_data) { |
| data = memremap(pa_data, sizeof(*data), MEMREMAP_WB); |
| if (!data) |
| return -ENOMEM; |
| if (nr == i) { |
| *size = data->len; |
| memunmap(data); |
| return 0; |
| } |
| |
| pa_data = data->next; |
| memunmap(data); |
| i++; |
| } |
| return -EINVAL; |
| } |
| |
| static ssize_t type_show(struct kobject *kobj, |
| struct kobj_attribute *attr, char *buf) |
| { |
| int nr, ret; |
| u64 paddr; |
| struct setup_data *data; |
| |
| ret = kobj_to_setup_data_nr(kobj, &nr); |
| if (ret) |
| return ret; |
| |
| ret = get_setup_data_paddr(nr, &paddr); |
| if (ret) |
| return ret; |
| data = memremap(paddr, sizeof(*data), MEMREMAP_WB); |
| if (!data) |
| return -ENOMEM; |
| |
| ret = sprintf(buf, "0x%x\n", data->type); |
| memunmap(data); |
| return ret; |
| } |
| |
| static ssize_t setup_data_data_read(struct file *fp, |
| struct kobject *kobj, |
| struct bin_attribute *bin_attr, |
| char *buf, |
| loff_t off, size_t count) |
| { |
| int nr, ret = 0; |
| u64 paddr; |
| struct setup_data *data; |
| void *p; |
| |
| ret = kobj_to_setup_data_nr(kobj, &nr); |
| if (ret) |
| return ret; |
| |
| ret = get_setup_data_paddr(nr, &paddr); |
| if (ret) |
| return ret; |
| data = memremap(paddr, sizeof(*data), MEMREMAP_WB); |
| if (!data) |
| return -ENOMEM; |
| |
| if (off > data->len) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (count > data->len - off) |
| count = data->len - off; |
| |
| if (!count) |
| goto out; |
| |
| ret = count; |
| p = memremap(paddr + sizeof(*data), data->len, MEMREMAP_WB); |
| if (!p) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| memcpy(buf, p + off, count); |
| memunmap(p); |
| out: |
| memunmap(data); |
| return ret; |
| } |
| |
| static struct kobj_attribute type_attr = __ATTR_RO(type); |
| |
| static struct bin_attribute data_attr __ro_after_init = { |
| .attr = { |
| .name = "data", |
| .mode = S_IRUGO, |
| }, |
| .read = setup_data_data_read, |
| }; |
| |
| static struct attribute *setup_data_type_attrs[] = { |
| &type_attr.attr, |
| NULL, |
| }; |
| |
| static struct bin_attribute *setup_data_data_attrs[] = { |
| &data_attr, |
| NULL, |
| }; |
| |
| static const struct attribute_group setup_data_attr_group = { |
| .attrs = setup_data_type_attrs, |
| .bin_attrs = setup_data_data_attrs, |
| }; |
| |
| static int __init create_setup_data_node(struct kobject *parent, |
| struct kobject **kobjp, int nr) |
| { |
| int ret = 0; |
| size_t size; |
| struct kobject *kobj; |
| char name[16]; /* should be enough for setup_data nodes numbers */ |
| snprintf(name, 16, "%d", nr); |
| |
| kobj = kobject_create_and_add(name, parent); |
| if (!kobj) |
| return -ENOMEM; |
| |
| ret = get_setup_data_size(nr, &size); |
| if (ret) |
| goto out_kobj; |
| |
| data_attr.size = size; |
| ret = sysfs_create_group(kobj, &setup_data_attr_group); |
| if (ret) |
| goto out_kobj; |
| *kobjp = kobj; |
| |
| return 0; |
| out_kobj: |
| kobject_put(kobj); |
| return ret; |
| } |
| |
| static void __init cleanup_setup_data_node(struct kobject *kobj) |
| { |
| sysfs_remove_group(kobj, &setup_data_attr_group); |
| kobject_put(kobj); |
| } |
| |
| static int __init get_setup_data_total_num(u64 pa_data, int *nr) |
| { |
| int ret = 0; |
| struct setup_data *data; |
| |
| *nr = 0; |
| while (pa_data) { |
| *nr += 1; |
| data = memremap(pa_data, sizeof(*data), MEMREMAP_WB); |
| if (!data) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| pa_data = data->next; |
| memunmap(data); |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static int __init create_setup_data_nodes(struct kobject *parent) |
| { |
| struct kobject *setup_data_kobj, **kobjp; |
| u64 pa_data; |
| int i, j, nr, ret = 0; |
| |
| pa_data = boot_params.hdr.setup_data; |
| if (!pa_data) |
| return 0; |
| |
| setup_data_kobj = kobject_create_and_add("setup_data", parent); |
| if (!setup_data_kobj) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ret = get_setup_data_total_num(pa_data, &nr); |
| if (ret) |
| goto out_setup_data_kobj; |
| |
| kobjp = kmalloc(sizeof(*kobjp) * nr, GFP_KERNEL); |
| if (!kobjp) { |
| ret = -ENOMEM; |
| goto out_setup_data_kobj; |
| } |
| |
| for (i = 0; i < nr; i++) { |
| ret = create_setup_data_node(setup_data_kobj, kobjp + i, i); |
| if (ret) |
| goto out_clean_nodes; |
| } |
| |
| kfree(kobjp); |
| return 0; |
| |
| out_clean_nodes: |
| for (j = i - 1; j > 0; j--) |
| cleanup_setup_data_node(*(kobjp + j)); |
| kfree(kobjp); |
| out_setup_data_kobj: |
| kobject_put(setup_data_kobj); |
| out: |
| return ret; |
| } |
| |
| static int __init boot_params_ksysfs_init(void) |
| { |
| int ret; |
| struct kobject *boot_params_kobj; |
| |
| boot_params_kobj = kobject_create_and_add("boot_params", |
| kernel_kobj); |
| if (!boot_params_kobj) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| ret = sysfs_create_group(boot_params_kobj, &boot_params_attr_group); |
| if (ret) |
| goto out_boot_params_kobj; |
| |
| ret = create_setup_data_nodes(boot_params_kobj); |
| if (ret) |
| goto out_create_group; |
| |
| return 0; |
| out_create_group: |
| sysfs_remove_group(boot_params_kobj, &boot_params_attr_group); |
| out_boot_params_kobj: |
| kobject_put(boot_params_kobj); |
| out: |
| return ret; |
| } |
| |
| arch_initcall(boot_params_ksysfs_init); |