@@ -3,5 +3,6 @@ obj-y += intc-irq.o
obj-y += irq.o
obj-y += memory.o
obj-y += rom.o
+obj-m += rom-sysfs.o
obj-y += scmd.o
obj-y += time.o
new file mode 100644
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PlayStation 2 read-only memory (ROM) sysfs
+ *
+ * Copyright (C) 2019 Fredrik Noring
+ *
+ * FIXME: Is /sys/rom the proper placement?
+ */
+
+///
+// DOC:
+//
+// ROM0 and ROM1 contain simple file systems that can be inspected with this
+// sysfs module. For example, listing all files of ROM0::
+//
+// # cat /sys/rom/rom0/file/*/name | column
+// RESET VBLANK RMRESET MCSERV XSIFCMD
+// ROMDIR IOMAN OSDVER PADMAN XCDVDMAN
+// EXTINFO MODLOAD - CDVDMAN XCDVDFSV
+// ROMVER ROMDRV IOPBOOT CDVDFSV XFILEIO
+// SBIN ADDDRV OSDCNF FILEIO XSIO2MAN
+// LOGO STDIO - CLEARSPU XMTAPMAN
+// IOPBTCONF SIFMAN TBIN UDNL XMCMAN
+// ...
+//
+// Viewing for example the contents of the ROM file ROMVER using /dev/mem::
+//
+// # grep -l ROMVER /sys/rom/rom0/file/*/name
+// /sys/rom/rom0/file/3/name
+// # cd /sys/rom/rom0/file/3
+// # dd if=/dev/mem bs=$(cat size) iflag=skip_bytes
+// skip=$(( $(cat data) )) count=1 status=none
+// 0170EC20030227
+//
+// For convenience, the ROMVER file is also available directly in sysfs::
+//
+// # ls /sys/rom/rom0/version
+// date number region type
+// # cat /sys/rom/rom0/version/*
+// 2003-02-27
+// 0x0170
+// Europe
+// CEX
+//
+// The CEX type indicates that it is a retail machine, as opposed to for
+// example DEX that would be a debug machine.
+//
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+
+#include <asm/page.h>
+#include <asm/uaccess.h>
+
+#include <asm/mach-ps2/rom.h>
+
+static int rom0_sysfs(struct kobject *romn_kobj);
+
+/* FIXME */
+static const struct {
+ const char *name;
+ const struct rom_dir *dir;
+ int (*func)(struct kobject *romn_kobj);
+} rom_dirs[] = {
+ { "rom0", &rom0_dir, rom0_sysfs },
+ { "rom1", &rom1_dir, NULL },
+};
+
+static struct rom_dir rom_dir_from_kobj(const struct kobject *kobj)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(rom_dirs); i++)
+ if (strcmp(rom_dirs[i].name, kobj->parent->name) == 0)
+ return *rom_dirs[i].dir;
+
+ pr_err("%s: ROM dir for \"%s\" does not exist\n", __func__,
+ kobj->parent->name);
+
+ return (struct rom_dir) { };
+}
+
+static struct rom_file rom_file_from_kobj(const struct kobject *kobj)
+{
+ const size_t rom_id = simple_strtoull(kobj->name, NULL, 0);
+ const struct rom_dir dir = rom_dir_from_kobj(kobj->parent);
+ struct rom_file file;
+ size_t id = 0;
+
+ rom_for_each_file(file, dir)
+ if (id++ == rom_id)
+ return file;
+
+ pr_err("%s: ROM id %zu for \"%s\" does not exist\n", __func__,
+ rom_id, kobj->parent->name);
+
+ return (struct rom_file) { .name = "<undefined>" };
+}
+
+static ssize_t rom_version_number_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "0x%04x\n", rom_version().number);
+}
+
+static ssize_t rom_version_region_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ rom_region_name(rom_version().region));
+}
+
+static ssize_t rom_version_type_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ rom_type_name(rom_version().type));
+}
+
+static ssize_t rom_version_date_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ const struct rom_ver v = rom_version();
+
+ return scnprintf(buf, PAGE_SIZE, "%04d-%02d-%02d\n",
+ v.date.year, v.date.month, v.date.day);
+}
+
+static ssize_t rom_file_name_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ rom_file_from_kobj(kobj).name);
+}
+
+static ssize_t rom_file_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "%zu\n",
+ rom_file_from_kobj(kobj).size);
+}
+
+static ssize_t rom_file_data_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE, "0x%lx\n",
+ virt_to_phys(rom_file_from_kobj(kobj).data));
+}
+
+#define DEFINE_ROM_FIELD_ATTR(prefix, field) \
+ static struct kobj_attribute rom_attribute_##prefix##_##field = \
+ __ATTR(field, S_IRUGO, rom_##prefix##_##field##_show, NULL)
+
+DEFINE_ROM_FIELD_ATTR(version, number);
+DEFINE_ROM_FIELD_ATTR(version, region);
+DEFINE_ROM_FIELD_ATTR(version, type);
+DEFINE_ROM_FIELD_ATTR(version, date);
+
+static struct attribute *rom_version_attributes[] = {
+ &rom_attribute_version_number.attr,
+ &rom_attribute_version_region.attr,
+ &rom_attribute_version_type.attr,
+ &rom_attribute_version_date.attr,
+ NULL
+};
+
+static struct attribute_group rom_version_attribute_group = {
+ .attrs = rom_version_attributes
+};
+
+DEFINE_ROM_FIELD_ATTR(file, name);
+DEFINE_ROM_FIELD_ATTR(file, size);
+DEFINE_ROM_FIELD_ATTR(file, data);
+
+static struct attribute *rom_file_attributes[] = {
+ &rom_attribute_file_name.attr,
+ &rom_attribute_file_size.attr,
+ &rom_attribute_file_data.attr,
+ NULL
+};
+
+static struct attribute_group rom_file_attribute_group = {
+ .attrs = rom_file_attributes
+};
+
+static int rom0_sysfs(struct kobject *rom0_kobj)
+{
+ struct kobject *version_kobj;
+
+ version_kobj = kobject_create_and_add("version", rom0_kobj);
+ if (!version_kobj)
+ return -ENOMEM;
+
+ return sysfs_create_group(version_kobj, &rom_version_attribute_group);
+}
+
+static int __init rom_init_file(struct kobject *file_kobj, size_t index,
+ const struct rom_file file)
+{
+ struct kobject *index_kobj;
+ char index_string[20];
+ int err;
+
+ scnprintf(index_string, sizeof(index_string), "%zu", index);
+
+ index_kobj = kobject_create_and_add(index_string, file_kobj);
+ if (!index_kobj)
+ return -ENOMEM;
+
+ return sysfs_create_group(index_kobj, &rom_file_attribute_group);
+}
+
+static int __init rom_init_dir(struct kobject *romn_kobj,
+ const struct rom_dir dir)
+{
+ struct kobject *file_kobj = kobject_create_and_add("file", romn_kobj);
+ struct rom_file file;
+ size_t i = 0;
+
+ if (!file_kobj)
+ return -ENOMEM;
+
+ rom_for_each_file(file, dir) {
+ int err = rom_init_file(file_kobj, i++, file);
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int __init rom_init_rom(struct kobject *rom_kobj,
+ const char *name, const struct rom_dir dir,
+ int (*func)(struct kobject *romn_kobj))
+{
+ struct kobject *romn_kobj = kobject_create_and_add(name, rom_kobj);
+ int err;
+
+ if (!romn_kobj)
+ return -ENOMEM;
+
+ err = rom_init_dir(romn_kobj, dir);
+ if (!err && func)
+ err = func(romn_kobj);
+
+ return err;
+}
+
+static struct kobject *rom_kobj;
+
+static int __init rom_sysfs_init(void)
+{
+ size_t i;
+
+ rom_kobj = kobject_create_and_add("rom", NULL);
+ if (!rom_kobj)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(rom_dirs); i++) {
+ int err = rom_init_rom(rom_kobj, rom_dirs[i].name,
+ *rom_dirs[i].dir, rom_dirs[i].func);
+
+ if (err) {
+ kobject_del(rom_kobj);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static void __exit rom_sysfs_exit(void)
+{
+ kobject_del(rom_kobj);
+}
+
+module_init(rom_sysfs_init);
+module_exit(rom_sysfs_exit);
+
+MODULE_DESCRIPTION("PlayStation 2 read-only memory (ROM) sysfs");
+MODULE_AUTHOR("Fredrik Noring");
+MODULE_LICENSE("GPL");
The ROM sysfs module gives information on the ROM file names, sizes, and addresses. For example, listing all files of ROM0: # cat /sys/rom/rom0/file/*/name | column RESET VBLANK RMRESET MCSERV XSIFCMD ROMDIR IOMAN OSDVER PADMAN XCDVDMAN EXTINFO MODLOAD - CDVDMAN XCDVDFSV ROMVER ROMDRV IOPBOOT CDVDFSV XFILEIO SBIN ADDDRV OSDCNF FILEIO XSIO2MAN LOGO STDIO - CLEARSPU XMTAPMAN IOPBTCONF SIFMAN TBIN UDNL XMCMAN ... Viewing for example the contents of the ROM file ROMVER using /dev/mem: # grep -l ROMVER /sys/rom/rom0/file/*/name /sys/rom/rom0/file/3/name # cd /sys/rom/rom0/file/3 # dd if=/dev/mem bs=$(cat size) iflag=skip_bytes \ skip=$(( $(cat data) )) count=1 status=none 0170EC20030227 For convenience, the ROMVER file is also available directly in sysfs: # ls /sys/rom/rom0/version date number region type # cat /sys/rom/rom0/version/* 2003-02-27 0x0170 Europe CEX The CEX type indicates that it is a retail machine, as opposed to for example DEX that would be a debug machine. Signed-off-by: Fredrik Noring <noring@nocrew.org> --- arch/mips/ps2/Makefile | 1 + arch/mips/ps2/rom-sysfs.c | 288 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 arch/mips/ps2/rom-sysfs.c