root/drivers/platform/x86/ibm_rtl.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * IBM Real-Time Linux driver
 *
 * Copyright (C) IBM Corporation, 2010
 *
 * Author: Keith Mannthey <kmannth@us.ibm.com>
 *         Vernon Mauery <vernux@us.ibm.com>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/dmi.h>
#include <linux/efi.h>
#include <linux/mutex.h>
#include <asm/bios_ebda.h>

#include <linux/io-64-nonatomic-lo-hi.h>

static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");

static bool debug;
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, "Show debug output");

MODULE_DESCRIPTION("IBM Premium Real Time Mode (PRTM) driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>");
MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>");

#define RTL_ADDR_TYPE_IO    1
#define RTL_ADDR_TYPE_MMIO  2

#define RTL_CMD_ENTER_PRTM  1
#define RTL_CMD_EXIT_PRTM   2

/* The RTL table as presented by the EBDA: */
struct ibm_rtl_table {
        char signature[5]; /* signature should be "_RTL_" */
        u8 version;
        u8 rt_status;
        u8 command;
        u8 command_status;
        u8 cmd_address_type;
        u8 cmd_granularity;
        u8 cmd_offset;
        u16 reserve1;
        u32 cmd_port_address; /* platform dependent address */
        u32 cmd_port_value;   /* platform dependent value */
} __attribute__((packed));

/* to locate "_RTL_" signature do a masked 5-byte integer compare */
#define RTL_SIGNATURE 0x0000005f4c54525fULL
#define RTL_MASK      0x000000ffffffffffULL

#define RTL_DEBUG(fmt, ...)                             \
do {                                                    \
        if (debug)                                      \
                pr_info(fmt, ##__VA_ARGS__);            \
} while (0)

static DEFINE_MUTEX(rtl_lock);
static struct ibm_rtl_table __iomem *rtl_table;
static void __iomem *ebda_map;
static void __iomem *rtl_cmd_addr;
static u8 rtl_cmd_type;
static u8 rtl_cmd_width;

static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len)
{
        if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
                return ioremap(addr, len);
        return ioport_map(addr, len);
}

static void rtl_port_unmap(void __iomem *addr)
{
        if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO)
                iounmap(addr);
        else
                ioport_unmap(addr);
}

static int ibm_rtl_write(u8 value)
{
        int ret = 0, count = 0;
        u32 cmd_port_val;

        RTL_DEBUG("%s(%d)\n", __func__, value);

        value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM;

        mutex_lock(&rtl_lock);

        if (ioread8(&rtl_table->rt_status) != value) {
                iowrite8(value, &rtl_table->command);

                switch (rtl_cmd_width) {
                case 8:
                        cmd_port_val = ioread8(&rtl_table->cmd_port_value);
                        RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
                        iowrite8((u8)cmd_port_val, rtl_cmd_addr);
                        break;
                case 16:
                        cmd_port_val = ioread16(&rtl_table->cmd_port_value);
                        RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
                        iowrite16((u16)cmd_port_val, rtl_cmd_addr);
                        break;
                case 32:
                        cmd_port_val = ioread32(&rtl_table->cmd_port_value);
                        RTL_DEBUG("cmd_port_val = %u\n", cmd_port_val);
                        iowrite32(cmd_port_val, rtl_cmd_addr);
                        break;
                }

                while (ioread8(&rtl_table->command)) {
                        msleep(10);
                        if (count++ > 500) {
                                pr_err("Hardware not responding to "
                                       "mode switch request\n");
                                ret = -EIO;
                                break;
                        }

                }

                if (ioread8(&rtl_table->command_status)) {
                        RTL_DEBUG("command_status reports failed command\n");
                        ret = -EIO;
                }
        }

        mutex_unlock(&rtl_lock);
        return ret;
}

static ssize_t rtl_show_version(struct device *dev,
                                struct device_attribute *attr,
                                char *buf)
{
        return sprintf(buf, "%d\n", (int)ioread8(&rtl_table->version));
}

static ssize_t rtl_show_state(struct device *dev,
                              struct device_attribute *attr,
                              char *buf)
{
        return sprintf(buf, "%d\n", ioread8(&rtl_table->rt_status));
}

static ssize_t rtl_set_state(struct device *dev,
                             struct device_attribute *attr,
                             const char *buf,
                             size_t count)
{
        ssize_t ret;

        if (count < 1 || count > 2)
                return -EINVAL;

        switch (buf[0]) {
        case '0':
                ret = ibm_rtl_write(0);
                break;
        case '1':
                ret = ibm_rtl_write(1);
                break;
        default:
                ret = -EINVAL;
        }
        if (ret >= 0)
                ret = count;

        return ret;
}

static const struct bus_type rtl_subsys = {
        .name = "ibm_rtl",
        .dev_name = "ibm_rtl",
};

static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL);
static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state);

static struct device_attribute *rtl_attributes[] = {
        &dev_attr_version,
        &dev_attr_state,
        NULL
};


static int rtl_setup_sysfs(void) {
        int ret, i;

        ret = subsys_system_register(&rtl_subsys, NULL);
        if (!ret) {
                struct device *dev_root = bus_get_dev_root(&rtl_subsys);

                if (dev_root) {
                        for (i = 0; rtl_attributes[i]; i ++)
                                device_create_file(dev_root, rtl_attributes[i]);
                        put_device(dev_root);
                }
        }
        return ret;
}

static void rtl_teardown_sysfs(void) {
        struct device *dev_root = bus_get_dev_root(&rtl_subsys);
        int i;

        if (dev_root) {
                for (i = 0; rtl_attributes[i]; i ++)
                        device_remove_file(dev_root, rtl_attributes[i]);
                put_device(dev_root);
        }
        bus_unregister(&rtl_subsys);
}


static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = {
        {                                                  \
                .matches = {                               \
                        DMI_MATCH(DMI_SYS_VENDOR, "IBM"),  \
                },                                         \
        },
        { }
};

static int __init ibm_rtl_init(void) {
        unsigned long ebda_addr, ebda_size;
        unsigned int ebda_kb;
        int ret = -ENODEV, i;

        if (force)
                pr_warn("module loaded by force\n");
        /* first ensure that we are running on IBM HW */
        else if (efi_enabled(EFI_BOOT) || !dmi_check_system(ibm_rtl_dmi_table))
                return -ENODEV;

        /* Get the address for the Extended BIOS Data Area */
        ebda_addr = get_bios_ebda();
        if (!ebda_addr) {
                RTL_DEBUG("no BIOS EBDA found\n");
                return -ENODEV;
        }

        ebda_map = ioremap(ebda_addr, 4);
        if (!ebda_map)
                return -ENOMEM;

        /* First word in the EDBA is the Size in KB */
        ebda_kb = ioread16(ebda_map);
        RTL_DEBUG("EBDA is %d kB\n", ebda_kb);

        if (ebda_kb == 0)
                goto out;

        iounmap(ebda_map);
        ebda_size = ebda_kb*1024;

        /* Remap the whole table */
        ebda_map = ioremap(ebda_addr, ebda_size);
        if (!ebda_map)
                return -ENOMEM;

        /* search for the _RTL_ signature at the start of the table */
        for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) {
                struct ibm_rtl_table __iomem * tmp;
                tmp = (struct ibm_rtl_table __iomem *) (ebda_map + i*sizeof(unsigned int));
                if ((readq(&tmp->signature) & RTL_MASK) == RTL_SIGNATURE) {
                        phys_addr_t addr;
                        unsigned int plen;
                        RTL_DEBUG("found RTL_SIGNATURE at %p\n", tmp);
                        rtl_table = tmp;
                        /* The address, value, width and offset are platform
                         * dependent and found in the ibm_rtl_table */
                        rtl_cmd_width = ioread8(&rtl_table->cmd_granularity);
                        rtl_cmd_type = ioread8(&rtl_table->cmd_address_type);
                        RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n",
                                  rtl_cmd_width, rtl_cmd_type);
                        addr = ioread32(&rtl_table->cmd_port_address);
                        RTL_DEBUG("addr = %#llx\n", (unsigned long long)addr);
                        plen = rtl_cmd_width/sizeof(char);
                        rtl_cmd_addr = rtl_port_map(addr, plen);
                        RTL_DEBUG("rtl_cmd_addr = %p\n", rtl_cmd_addr);
                        if (!rtl_cmd_addr) {
                                ret = -ENOMEM;
                                break;
                        }
                        ret = rtl_setup_sysfs();
                        break;
                }
        }

out:
        if (ret) {
                iounmap(ebda_map);
                rtl_port_unmap(rtl_cmd_addr);
        }

        return ret;
}

static void __exit ibm_rtl_exit(void)
{
        if (rtl_table) {
                RTL_DEBUG("cleaning up");
                /* do not leave the machine in SMI-free mode */
                ibm_rtl_write(0);
                /* unmap, unlink and remove all traces */
                rtl_teardown_sysfs();
                iounmap(ebda_map);
                rtl_port_unmap(rtl_cmd_addr);
        }
}

module_init(ibm_rtl_init);
module_exit(ibm_rtl_exit);