diff options
Diffstat (limited to 'drivers/soc/apple')
| -rw-r--r-- | drivers/soc/apple/Kconfig | 43 | ||||
| -rw-r--r-- | drivers/soc/apple/Makefile | 10 | ||||
| -rw-r--r-- | drivers/soc/apple/mailbox.c | 463 | ||||
| -rw-r--r-- | drivers/soc/apple/mailbox.h | 48 | ||||
| -rw-r--r-- | drivers/soc/apple/rtkit-crashlog.c | 247 | ||||
| -rw-r--r-- | drivers/soc/apple/rtkit-internal.h | 59 | ||||
| -rw-r--r-- | drivers/soc/apple/rtkit.c | 963 | ||||
| -rw-r--r-- | drivers/soc/apple/sart.c | 378 |
8 files changed, 2211 insertions, 0 deletions
diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig new file mode 100644 index 000000000000..ad6736889231 --- /dev/null +++ b/drivers/soc/apple/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only + +if ARCH_APPLE || COMPILE_TEST + +menu "Apple SoC drivers" + +config APPLE_MAILBOX + tristate "Apple SoC mailboxes" + depends on PM + depends on ARCH_APPLE || (64BIT && COMPILE_TEST) + help + Apple SoCs have various co-processors required for certain + peripherals to work (NVMe, display controller, etc.). This + driver adds support for the mailbox controller used to + communicate with those. + + Say Y here if you have an Apple SoC. + +config APPLE_RTKIT + tristate "Apple RTKit co-processor IPC protocol" + depends on APPLE_MAILBOX + depends on ARCH_APPLE || COMPILE_TEST + help + Apple SoCs such as the M1 come with various co-processors running + their proprietary RTKit operating system. This option enables support + for the protocol library used to communicate with those. It is used + by various client drivers. + + Say 'y' here if you have an Apple SoC. + +config APPLE_SART + tristate "Apple SART DMA address filter" + depends on ARCH_APPLE || COMPILE_TEST + help + Apple SART is a simple DMA address filter used on Apple SoCs such + as the M1. It is usually required for the NVMe coprocessor which does + not use a proper IOMMU. + + Say 'y' here if you have an Apple SoC. + +endmenu + +endif diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile new file mode 100644 index 000000000000..4d9ab8f3037b --- /dev/null +++ b/drivers/soc/apple/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o +apple-mailbox-y = mailbox.o + +obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o +apple-rtkit-y = rtkit.o rtkit-crashlog.o + +obj-$(CONFIG_APPLE_SART) += apple-sart.o +apple-sart-y = sart.o diff --git a/drivers/soc/apple/mailbox.c b/drivers/soc/apple/mailbox.c new file mode 100644 index 000000000000..5c48455185c9 --- /dev/null +++ b/drivers/soc/apple/mailbox.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple mailbox driver + * + * Copyright The Asahi Linux Contributors + * + * This driver adds support for two mailbox variants (called ASC and M3 by + * Apple) found in Apple SoCs such as the M1. It consists of two FIFOs used to + * exchange 64+32 bit messages between the main CPU and a co-processor. + * Various coprocessors implement different IPC protocols based on these simple + * messages and shared memory buffers. + * + * Both the main CPU and the co-processor see the same set of registers but + * the first FIFO (A2I) is always used to transfer messages from the application + * processor (us) to the I/O processor and the second one (I2A) for the + * other direction. + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include "mailbox.h" + +#define APPLE_ASC_MBOX_CONTROL_FULL BIT(16) +#define APPLE_ASC_MBOX_CONTROL_EMPTY BIT(17) + +#define APPLE_ASC_MBOX_A2I_CONTROL 0x110 +#define APPLE_ASC_MBOX_A2I_SEND0 0x800 +#define APPLE_ASC_MBOX_A2I_SEND1 0x808 +#define APPLE_ASC_MBOX_A2I_RECV0 0x810 +#define APPLE_ASC_MBOX_A2I_RECV1 0x818 + +#define APPLE_ASC_MBOX_I2A_CONTROL 0x114 +#define APPLE_ASC_MBOX_I2A_SEND0 0x820 +#define APPLE_ASC_MBOX_I2A_SEND1 0x828 +#define APPLE_ASC_MBOX_I2A_RECV0 0x830 +#define APPLE_ASC_MBOX_I2A_RECV1 0x838 + +#define APPLE_T8015_MBOX_A2I_CONTROL 0x108 +#define APPLE_T8015_MBOX_I2A_CONTROL 0x10c + +#define APPLE_M3_MBOX_CONTROL_FULL BIT(16) +#define APPLE_M3_MBOX_CONTROL_EMPTY BIT(17) + +#define APPLE_M3_MBOX_A2I_CONTROL 0x50 +#define APPLE_M3_MBOX_A2I_SEND0 0x60 +#define APPLE_M3_MBOX_A2I_SEND1 0x68 +#define APPLE_M3_MBOX_A2I_RECV0 0x70 +#define APPLE_M3_MBOX_A2I_RECV1 0x78 + +#define APPLE_M3_MBOX_I2A_CONTROL 0x80 +#define APPLE_M3_MBOX_I2A_SEND0 0x90 +#define APPLE_M3_MBOX_I2A_SEND1 0x98 +#define APPLE_M3_MBOX_I2A_RECV0 0xa0 +#define APPLE_M3_MBOX_I2A_RECV1 0xa8 + +#define APPLE_M3_MBOX_IRQ_ENABLE 0x48 +#define APPLE_M3_MBOX_IRQ_ACK 0x4c +#define APPLE_M3_MBOX_IRQ_A2I_EMPTY BIT(0) +#define APPLE_M3_MBOX_IRQ_A2I_NOT_EMPTY BIT(1) +#define APPLE_M3_MBOX_IRQ_I2A_EMPTY BIT(2) +#define APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY BIT(3) + +#define APPLE_MBOX_MSG1_OUTCNT GENMASK(56, 52) +#define APPLE_MBOX_MSG1_INCNT GENMASK(51, 48) +#define APPLE_MBOX_MSG1_OUTPTR GENMASK(47, 44) +#define APPLE_MBOX_MSG1_INPTR GENMASK(43, 40) +#define APPLE_MBOX_MSG1_MSG GENMASK(31, 0) + +#define APPLE_MBOX_TX_TIMEOUT 500 + +struct apple_mbox_hw { + unsigned int control_full; + unsigned int control_empty; + + unsigned int a2i_control; + unsigned int a2i_send0; + unsigned int a2i_send1; + + unsigned int i2a_control; + unsigned int i2a_recv0; + unsigned int i2a_recv1; + + bool has_irq_controls; + unsigned int irq_enable; + unsigned int irq_ack; + unsigned int irq_bit_recv_not_empty; + unsigned int irq_bit_send_empty; +}; + +int apple_mbox_send(struct apple_mbox *mbox, const struct apple_mbox_msg msg, + bool atomic) +{ + unsigned long flags; + int ret; + u32 mbox_ctrl; + long t; + + spin_lock_irqsave(&mbox->tx_lock, flags); + mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->a2i_control); + + while (mbox_ctrl & mbox->hw->control_full) { + if (atomic) { + ret = readl_poll_timeout_atomic( + mbox->regs + mbox->hw->a2i_control, mbox_ctrl, + !(mbox_ctrl & mbox->hw->control_full), 100, + APPLE_MBOX_TX_TIMEOUT * 1000); + + if (ret) { + spin_unlock_irqrestore(&mbox->tx_lock, flags); + return ret; + } + + break; + } + /* + * The interrupt is level triggered and will keep firing as long as the + * FIFO is empty. It will also keep firing if the FIFO was empty + * at any point in the past until it has been acknowledged at the + * mailbox level. By acknowledging it here we can ensure that we will + * only get the interrupt once the FIFO has been cleared again. + * If the FIFO is already empty before the ack it will fire again + * immediately after the ack. + */ + if (mbox->hw->has_irq_controls) { + writel_relaxed(mbox->hw->irq_bit_send_empty, + mbox->regs + mbox->hw->irq_ack); + } + enable_irq(mbox->irq_send_empty); + reinit_completion(&mbox->tx_empty); + spin_unlock_irqrestore(&mbox->tx_lock, flags); + + t = wait_for_completion_interruptible_timeout( + &mbox->tx_empty, + msecs_to_jiffies(APPLE_MBOX_TX_TIMEOUT)); + if (t < 0) + return t; + else if (t == 0) + return -ETIMEDOUT; + + spin_lock_irqsave(&mbox->tx_lock, flags); + mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->a2i_control); + } + + writeq_relaxed(msg.msg0, mbox->regs + mbox->hw->a2i_send0); + writeq_relaxed(FIELD_PREP(APPLE_MBOX_MSG1_MSG, msg.msg1), + mbox->regs + mbox->hw->a2i_send1); + + spin_unlock_irqrestore(&mbox->tx_lock, flags); + + return 0; +} +EXPORT_SYMBOL(apple_mbox_send); + +static irqreturn_t apple_mbox_send_empty_irq(int irq, void *data) +{ + struct apple_mbox *mbox = data; + + /* + * We don't need to acknowledge the interrupt at the mailbox level + * here even if supported by the hardware. It will keep firing but that + * doesn't matter since it's disabled at the main interrupt controller. + * apple_mbox_send will acknowledge it before enabling + * it at the main controller again. + */ + spin_lock(&mbox->tx_lock); + disable_irq_nosync(mbox->irq_send_empty); + complete(&mbox->tx_empty); + spin_unlock(&mbox->tx_lock); + + return IRQ_HANDLED; +} + +static int apple_mbox_poll_locked(struct apple_mbox *mbox) +{ + struct apple_mbox_msg msg; + int ret = 0; + + u32 mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->i2a_control); + + while (!(mbox_ctrl & mbox->hw->control_empty)) { + msg.msg0 = readq_relaxed(mbox->regs + mbox->hw->i2a_recv0); + msg.msg1 = FIELD_GET( + APPLE_MBOX_MSG1_MSG, + readq_relaxed(mbox->regs + mbox->hw->i2a_recv1)); + + mbox->rx(mbox, msg, mbox->cookie); + ret++; + mbox_ctrl = readl_relaxed(mbox->regs + mbox->hw->i2a_control); + } + + /* + * The interrupt will keep firing even if there are no more messages + * unless we also acknowledge it at the mailbox level here. + * There's no race if a message comes in between the check in the while + * loop above and the ack below: If a new messages arrives inbetween + * those two the interrupt will just fire again immediately after the + * ack since it's level triggered. + */ + if (mbox->hw->has_irq_controls) { + writel_relaxed(mbox->hw->irq_bit_recv_not_empty, + mbox->regs + mbox->hw->irq_ack); + } + + return ret; +} + +static irqreturn_t apple_mbox_recv_irq(int irq, void *data) +{ + struct apple_mbox *mbox = data; + + spin_lock(&mbox->rx_lock); + apple_mbox_poll_locked(mbox); + spin_unlock(&mbox->rx_lock); + + return IRQ_HANDLED; +} + +int apple_mbox_poll(struct apple_mbox *mbox) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&mbox->rx_lock, flags); + ret = apple_mbox_poll_locked(mbox); + spin_unlock_irqrestore(&mbox->rx_lock, flags); + + return ret; +} +EXPORT_SYMBOL(apple_mbox_poll); + +int apple_mbox_start(struct apple_mbox *mbox) +{ + int ret; + + if (mbox->active) + return 0; + + ret = pm_runtime_resume_and_get(mbox->dev); + if (ret) + return ret; + + /* + * Only some variants of this mailbox HW provide interrupt control + * at the mailbox level. We therefore need to handle enabling/disabling + * interrupts at the main interrupt controller anyway for hardware that + * doesn't. Just always keep the interrupts we care about enabled at + * the mailbox level so that both hardware revisions behave almost + * the same. + */ + if (mbox->hw->has_irq_controls) { + writel_relaxed(mbox->hw->irq_bit_recv_not_empty | + mbox->hw->irq_bit_send_empty, + mbox->regs + mbox->hw->irq_enable); + } + + enable_irq(mbox->irq_recv_not_empty); + mbox->active = true; + return 0; +} +EXPORT_SYMBOL(apple_mbox_start); + +void apple_mbox_stop(struct apple_mbox *mbox) +{ + if (!mbox->active) + return; + + mbox->active = false; + disable_irq(mbox->irq_recv_not_empty); + pm_runtime_mark_last_busy(mbox->dev); + pm_runtime_put_autosuspend(mbox->dev); +} +EXPORT_SYMBOL(apple_mbox_stop); + +struct apple_mbox *apple_mbox_get(struct device *dev, int index) +{ + struct of_phandle_args args; + struct platform_device *pdev; + struct apple_mbox *mbox; + int ret; + + ret = of_parse_phandle_with_args(dev->of_node, "mboxes", "#mbox-cells", + index, &args); + if (ret || !args.np) + return ERR_PTR(ret); + + pdev = of_find_device_by_node(args.np); + of_node_put(args.np); + + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + mbox = platform_get_drvdata(pdev); + if (!mbox) { + mbox = ERR_PTR(-EPROBE_DEFER); + goto out_put_pdev; + } + + if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) { + mbox = ERR_PTR(-ENODEV); + goto out_put_pdev; + } + +out_put_pdev: + put_device(&pdev->dev); + + return mbox; +} +EXPORT_SYMBOL(apple_mbox_get); + +struct apple_mbox *apple_mbox_get_byname(struct device *dev, const char *name) +{ + int index; + + index = of_property_match_string(dev->of_node, "mbox-names", name); + if (index < 0) + return ERR_PTR(index); + + return apple_mbox_get(dev, index); +} +EXPORT_SYMBOL(apple_mbox_get_byname); + +static int apple_mbox_probe(struct platform_device *pdev) +{ + int ret; + char *irqname; + struct apple_mbox *mbox; + struct device *dev = &pdev->dev; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + mbox->dev = &pdev->dev; + mbox->hw = of_device_get_match_data(dev); + if (!mbox->hw) + return -EINVAL; + + mbox->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mbox->regs)) + return PTR_ERR(mbox->regs); + + mbox->irq_recv_not_empty = + platform_get_irq_byname(pdev, "recv-not-empty"); + if (mbox->irq_recv_not_empty < 0) + return -ENODEV; + + mbox->irq_send_empty = platform_get_irq_byname(pdev, "send-empty"); + if (mbox->irq_send_empty < 0) + return -ENODEV; + + spin_lock_init(&mbox->rx_lock); + spin_lock_init(&mbox->tx_lock); + init_completion(&mbox->tx_empty); + + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-recv", dev_name(dev)); + if (!irqname) + return -ENOMEM; + + ret = devm_request_irq(dev, mbox->irq_recv_not_empty, + apple_mbox_recv_irq, + IRQF_NO_AUTOEN | IRQF_NO_SUSPEND, irqname, mbox); + if (ret) + return ret; + + irqname = devm_kasprintf(dev, GFP_KERNEL, "%s-send", dev_name(dev)); + if (!irqname) + return -ENOMEM; + + ret = devm_request_irq(dev, mbox->irq_send_empty, + apple_mbox_send_empty_irq, + IRQF_NO_AUTOEN | IRQF_NO_SUSPEND, irqname, mbox); + if (ret) + return ret; + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + platform_set_drvdata(pdev, mbox); + return 0; +} + +static const struct apple_mbox_hw apple_mbox_t8015_hw = { + .control_full = APPLE_ASC_MBOX_CONTROL_FULL, + .control_empty = APPLE_ASC_MBOX_CONTROL_EMPTY, + + .a2i_control = APPLE_T8015_MBOX_A2I_CONTROL, + .a2i_send0 = APPLE_ASC_MBOX_A2I_SEND0, + .a2i_send1 = APPLE_ASC_MBOX_A2I_SEND1, + + .i2a_control = APPLE_T8015_MBOX_I2A_CONTROL, + .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0, + .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1, + + .has_irq_controls = false, +}; + +static const struct apple_mbox_hw apple_mbox_asc_hw = { + .control_full = APPLE_ASC_MBOX_CONTROL_FULL, + .control_empty = APPLE_ASC_MBOX_CONTROL_EMPTY, + + .a2i_control = APPLE_ASC_MBOX_A2I_CONTROL, + .a2i_send0 = APPLE_ASC_MBOX_A2I_SEND0, + .a2i_send1 = APPLE_ASC_MBOX_A2I_SEND1, + + .i2a_control = APPLE_ASC_MBOX_I2A_CONTROL, + .i2a_recv0 = APPLE_ASC_MBOX_I2A_RECV0, + .i2a_recv1 = APPLE_ASC_MBOX_I2A_RECV1, + + .has_irq_controls = false, +}; + +static const struct apple_mbox_hw apple_mbox_m3_hw = { + .control_full = APPLE_M3_MBOX_CONTROL_FULL, + .control_empty = APPLE_M3_MBOX_CONTROL_EMPTY, + + .a2i_control = APPLE_M3_MBOX_A2I_CONTROL, + .a2i_send0 = APPLE_M3_MBOX_A2I_SEND0, + .a2i_send1 = APPLE_M3_MBOX_A2I_SEND1, + + .i2a_control = APPLE_M3_MBOX_I2A_CONTROL, + .i2a_recv0 = APPLE_M3_MBOX_I2A_RECV0, + .i2a_recv1 = APPLE_M3_MBOX_I2A_RECV1, + + .has_irq_controls = true, + .irq_enable = APPLE_M3_MBOX_IRQ_ENABLE, + .irq_ack = APPLE_M3_MBOX_IRQ_ACK, + .irq_bit_recv_not_empty = APPLE_M3_MBOX_IRQ_I2A_NOT_EMPTY, + .irq_bit_send_empty = APPLE_M3_MBOX_IRQ_A2I_EMPTY, +}; + +static const struct of_device_id apple_mbox_of_match[] = { + { .compatible = "apple,asc-mailbox-v4", .data = &apple_mbox_asc_hw }, + { .compatible = "apple,t8015-asc-mailbox", .data = &apple_mbox_t8015_hw }, + { .compatible = "apple,m3-mailbox-v2", .data = &apple_mbox_m3_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_mbox_of_match); + +static struct platform_driver apple_mbox_driver = { + .driver = { + .name = "apple-mailbox", + .of_match_table = apple_mbox_of_match, + }, + .probe = apple_mbox_probe, +}; +module_platform_driver(apple_mbox_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Apple Mailbox driver"); diff --git a/drivers/soc/apple/mailbox.h b/drivers/soc/apple/mailbox.h new file mode 100644 index 000000000000..f73a8913da95 --- /dev/null +++ b/drivers/soc/apple/mailbox.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple mailbox message format + * + * Copyright The Asahi Linux Contributors + */ + +#ifndef _APPLE_MAILBOX_H_ +#define _APPLE_MAILBOX_H_ + +#include <linux/device.h> +#include <linux/types.h> + +/* encodes a single 96bit message sent over the single channel */ +struct apple_mbox_msg { + u64 msg0; + u32 msg1; +}; + +struct apple_mbox { + struct device *dev; + void __iomem *regs; + const struct apple_mbox_hw *hw; + bool active; + + int irq_recv_not_empty; + int irq_send_empty; + + spinlock_t rx_lock; + spinlock_t tx_lock; + + struct completion tx_empty; + + /** Receive callback for incoming messages */ + void (*rx)(struct apple_mbox *mbox, struct apple_mbox_msg msg, void *cookie); + void *cookie; +}; + +struct apple_mbox *apple_mbox_get(struct device *dev, int index); +struct apple_mbox *apple_mbox_get_byname(struct device *dev, const char *name); + +int apple_mbox_start(struct apple_mbox *mbox); +void apple_mbox_stop(struct apple_mbox *mbox); +int apple_mbox_poll(struct apple_mbox *mbox); +int apple_mbox_send(struct apple_mbox *mbox, struct apple_mbox_msg msg, + bool atomic); + +#endif diff --git a/drivers/soc/apple/rtkit-crashlog.c b/drivers/soc/apple/rtkit-crashlog.c new file mode 100644 index 000000000000..8319e365110b --- /dev/null +++ b/drivers/soc/apple/rtkit-crashlog.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple RTKit IPC library + * Copyright (C) The Asahi Linux Contributors + */ +#include "rtkit-internal.h" + +#define FOURCC(a, b, c, d) \ + (((u32)(a) << 24) | ((u32)(b) << 16) | ((u32)(c) << 8) | ((u32)(d))) + +#define APPLE_RTKIT_CRASHLOG_HEADER FOURCC('C', 'L', 'H', 'E') +#define APPLE_RTKIT_CRASHLOG_STR FOURCC('C', 's', 't', 'r') +#define APPLE_RTKIT_CRASHLOG_VERSION FOURCC('C', 'v', 'e', 'r') +#define APPLE_RTKIT_CRASHLOG_MBOX FOURCC('C', 'm', 'b', 'x') +#define APPLE_RTKIT_CRASHLOG_TIME FOURCC('C', 't', 'i', 'm') +#define APPLE_RTKIT_CRASHLOG_REGS FOURCC('C', 'r', 'g', '8') + +/* For COMPILE_TEST on non-ARM64 architectures */ +#ifndef PSR_MODE_EL0t +#define PSR_MODE_EL0t 0x00000000 +#define PSR_MODE_EL1t 0x00000004 +#define PSR_MODE_EL1h 0x00000005 +#define PSR_MODE_EL2t 0x00000008 +#define PSR_MODE_EL2h 0x00000009 +#define PSR_MODE_MASK 0x0000000f +#endif + +struct apple_rtkit_crashlog_header { + u32 fourcc; + u32 version; + u32 size; + u32 flags; + u8 _unk[16]; +}; +static_assert(sizeof(struct apple_rtkit_crashlog_header) == 0x20); + +struct apple_rtkit_crashlog_mbox_entry { + u64 msg0; + u64 msg1; + u32 timestamp; + u8 _unk[4]; +}; +static_assert(sizeof(struct apple_rtkit_crashlog_mbox_entry) == 0x18); + +struct apple_rtkit_crashlog_regs { + u32 unk_0; + u32 unk_4; + u64 regs[31]; + u64 sp; + u64 pc; + u64 psr; + u64 cpacr; + u64 fpsr; + u64 fpcr; + u64 unk[64]; + u64 far; + u64 unk_X; + u64 esr; + u64 unk_Z; +} __packed; +static_assert(sizeof(struct apple_rtkit_crashlog_regs) == 0x350); + +static void apple_rtkit_crashlog_dump_str(struct apple_rtkit *rtk, u8 *bfr, + size_t size) +{ + u32 idx; + u8 *ptr, *end; + + memcpy(&idx, bfr, 4); + + ptr = bfr + 4; + end = bfr + size; + while (ptr < end) { + u8 *newline = memchr(ptr, '\n', end - ptr); + + if (newline) { + u8 tmp = *newline; + *newline = '\0'; + dev_warn(rtk->dev, "RTKit: Message (id=%x): %s\n", idx, + ptr); + *newline = tmp; + ptr = newline + 1; + } else { + dev_warn(rtk->dev, "RTKit: Message (id=%x): %s", idx, + ptr); + break; + } + } +} + +static void apple_rtkit_crashlog_dump_version(struct apple_rtkit *rtk, u8 *bfr, + size_t size) +{ + dev_warn(rtk->dev, "RTKit: Version: %s", bfr + 16); +} + +static void apple_rtkit_crashlog_dump_time(struct apple_rtkit *rtk, u8 *bfr, + size_t size) +{ + u64 crash_time; + + memcpy(&crash_time, bfr, 8); + dev_warn(rtk->dev, "RTKit: Crash time: %lld", crash_time); +} + +static void apple_rtkit_crashlog_dump_mailbox(struct apple_rtkit *rtk, u8 *bfr, + size_t size) +{ + u32 type, index, i; + size_t n_messages; + struct apple_rtkit_crashlog_mbox_entry entry; + + memcpy(&type, bfr + 16, 4); + memcpy(&index, bfr + 24, 4); + n_messages = (size - 28) / sizeof(entry); + + dev_warn(rtk->dev, "RTKit: Mailbox history (type = %d, index = %d)", + type, index); + for (i = 0; i < n_messages; ++i) { + memcpy(&entry, bfr + 28 + i * sizeof(entry), sizeof(entry)); + dev_warn(rtk->dev, "RTKit: #%03d@%08x: %016llx %016llx", i, + entry.timestamp, entry.msg0, entry.msg1); + } +} + +static void apple_rtkit_crashlog_dump_regs(struct apple_rtkit *rtk, u8 *bfr, + size_t size) +{ + struct apple_rtkit_crashlog_regs *regs; + const char *el; + int i; + + if (size < sizeof(*regs)) { + dev_warn(rtk->dev, "RTKit: Regs section too small: 0x%zx", size); + return; + } + + regs = (struct apple_rtkit_crashlog_regs *)bfr; + + switch (regs->psr & PSR_MODE_MASK) { + case PSR_MODE_EL0t: + el = "EL0t"; + break; + case PSR_MODE_EL1t: + el = "EL1t"; + break; + case PSR_MODE_EL1h: + el = "EL1h"; + break; + case PSR_MODE_EL2t: + el = "EL2t"; + break; + case PSR_MODE_EL2h: + el = "EL2h"; + break; + default: + el = "unknown"; + break; + } + + dev_warn(rtk->dev, "RTKit: Exception dump:"); + dev_warn(rtk->dev, " == Exception taken from %s ==", el); + dev_warn(rtk->dev, " PSR = 0x%llx", regs->psr); + dev_warn(rtk->dev, " PC = 0x%llx\n", regs->pc); + dev_warn(rtk->dev, " ESR = 0x%llx\n", regs->esr); + dev_warn(rtk->dev, " FAR = 0x%llx\n", regs->far); + dev_warn(rtk->dev, " SP = 0x%llx\n", regs->sp); + dev_warn(rtk->dev, "\n"); + + for (i = 0; i < 31; i += 4) { + if (i < 28) + dev_warn(rtk->dev, + " x%02d-x%02d = %016llx %016llx %016llx %016llx\n", + i, i + 3, + regs->regs[i], regs->regs[i + 1], + regs->regs[i + 2], regs->regs[i + 3]); + else + dev_warn(rtk->dev, + " x%02d-x%02d = %016llx %016llx %016llx\n", i, i + 3, + regs->regs[i], regs->regs[i + 1], regs->regs[i + 2]); + } + + dev_warn(rtk->dev, "\n"); +} + +void apple_rtkit_crashlog_dump(struct apple_rtkit *rtk, u8 *bfr, size_t size) +{ + size_t offset; + u32 section_fourcc, section_size; + struct apple_rtkit_crashlog_header header; + + memcpy(&header, bfr, sizeof(header)); + if (header.fourcc != APPLE_RTKIT_CRASHLOG_HEADER) { + dev_warn(rtk->dev, "RTKit: Expected crashlog header but got %x", + header.fourcc); + return; + } + + if (header.size > size) { + dev_warn(rtk->dev, "RTKit: Crashlog size (%x) is too large", + header.size); + return; + } + + size = header.size; + offset = sizeof(header); + + while (offset < size) { + memcpy(§ion_fourcc, bfr + offset, 4); + memcpy(§ion_size, bfr + offset + 12, 4); + + switch (section_fourcc) { + case APPLE_RTKIT_CRASHLOG_HEADER: + dev_dbg(rtk->dev, "RTKit: End of crashlog reached"); + return; + case APPLE_RTKIT_CRASHLOG_STR: + apple_rtkit_crashlog_dump_str(rtk, bfr + offset + 16, + section_size); + break; + case APPLE_RTKIT_CRASHLOG_VERSION: + apple_rtkit_crashlog_dump_version( + rtk, bfr + offset + 16, section_size); + break; + case APPLE_RTKIT_CRASHLOG_MBOX: + apple_rtkit_crashlog_dump_mailbox( + rtk, bfr + offset + 16, section_size); + break; + case APPLE_RTKIT_CRASHLOG_TIME: + apple_rtkit_crashlog_dump_time(rtk, bfr + offset + 16, + section_size); + break; + case APPLE_RTKIT_CRASHLOG_REGS: + apple_rtkit_crashlog_dump_regs(rtk, bfr + offset + 16, + section_size); + break; + default: + dev_warn(rtk->dev, + "RTKit: Unknown crashlog section: %x", + section_fourcc); + } + + offset += section_size; + } + + dev_warn(rtk->dev, + "RTKit: End of crashlog reached but no footer present"); +} diff --git a/drivers/soc/apple/rtkit-internal.h b/drivers/soc/apple/rtkit-internal.h new file mode 100644 index 000000000000..b8d5244678f0 --- /dev/null +++ b/drivers/soc/apple/rtkit-internal.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple RTKit IPC library + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _APPLE_RTKIT_INTERAL_H +#define _APPLE_RTKIT_INTERAL_H + +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/completion.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soc/apple/rtkit.h> +#include <linux/workqueue.h> +#include "mailbox.h" + +#define APPLE_RTKIT_APP_ENDPOINT_START 0x20 +#define APPLE_RTKIT_MAX_ENDPOINTS 0x100 + +struct apple_rtkit { + void *cookie; + const struct apple_rtkit_ops *ops; + struct device *dev; + + struct apple_mbox *mbox; + + struct completion epmap_completion; + struct completion iop_pwr_ack_completion; + struct completion ap_pwr_ack_completion; + + int boot_result; + int version; + + unsigned int iop_power_state; + unsigned int ap_power_state; + bool crashed; + + DECLARE_BITMAP(endpoints, APPLE_RTKIT_MAX_ENDPOINTS); + + struct apple_rtkit_shmem ioreport_buffer; + struct apple_rtkit_shmem crashlog_buffer; + struct apple_rtkit_shmem oslog_buffer; + + struct apple_rtkit_shmem syslog_buffer; + char *syslog_msg_buffer; + size_t syslog_n_entries; + size_t syslog_msg_size; + + struct workqueue_struct *wq; +}; + +void apple_rtkit_crashlog_dump(struct apple_rtkit *rtk, u8 *bfr, size_t size); + +#endif diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c new file mode 100644 index 000000000000..b8d4da147d23 --- /dev/null +++ b/drivers/soc/apple/rtkit.c @@ -0,0 +1,963 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple RTKit IPC library + * Copyright (C) The Asahi Linux Contributors + */ + +#include "rtkit-internal.h" + +enum { + APPLE_RTKIT_PWR_STATE_OFF = 0x00, /* power off, cannot be restarted */ + APPLE_RTKIT_PWR_STATE_SLEEP = 0x01, /* sleeping, can be restarted */ + APPLE_RTKIT_PWR_STATE_IDLE = 0x201, /* sleeping, retain state */ + APPLE_RTKIT_PWR_STATE_QUIESCED = 0x10, /* running but no communication */ + APPLE_RTKIT_PWR_STATE_ON = 0x20, /* normal operating state */ + APPLE_RTKIT_PWR_STATE_INIT = 0x220, /* init after starting the coproc */ +}; + +enum { + APPLE_RTKIT_EP_MGMT = 0, + APPLE_RTKIT_EP_CRASHLOG = 1, + APPLE_RTKIT_EP_SYSLOG = 2, + APPLE_RTKIT_EP_DEBUG = 3, + APPLE_RTKIT_EP_IOREPORT = 4, + APPLE_RTKIT_EP_OSLOG = 8, +}; + +#define APPLE_RTKIT_MGMT_TYPE GENMASK_ULL(59, 52) + +enum { + APPLE_RTKIT_MGMT_HELLO = 1, + APPLE_RTKIT_MGMT_HELLO_REPLY = 2, + APPLE_RTKIT_MGMT_STARTEP = 5, + APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE = 6, + APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE_ACK = 7, + APPLE_RTKIT_MGMT_EPMAP = 8, + APPLE_RTKIT_MGMT_EPMAP_REPLY = 8, + APPLE_RTKIT_MGMT_SET_AP_PWR_STATE = 0xb, + APPLE_RTKIT_MGMT_SET_AP_PWR_STATE_ACK = 0xb, +}; + +#define APPLE_RTKIT_MGMT_HELLO_MINVER GENMASK_ULL(15, 0) +#define APPLE_RTKIT_MGMT_HELLO_MAXVER GENMASK_ULL(31, 16) + +#define APPLE_RTKIT_MGMT_EPMAP_LAST BIT_ULL(51) +#define APPLE_RTKIT_MGMT_EPMAP_BASE GENMASK_ULL(34, 32) +#define APPLE_RTKIT_MGMT_EPMAP_BITMAP GENMASK_ULL(31, 0) + +#define APPLE_RTKIT_MGMT_EPMAP_REPLY_MORE BIT_ULL(0) + +#define APPLE_RTKIT_MGMT_STARTEP_EP GENMASK_ULL(39, 32) +#define APPLE_RTKIT_MGMT_STARTEP_FLAG BIT_ULL(1) + +#define APPLE_RTKIT_MGMT_PWR_STATE GENMASK_ULL(15, 0) + +#define APPLE_RTKIT_CRASHLOG_CRASH 1 + +#define APPLE_RTKIT_BUFFER_REQUEST 1 +#define APPLE_RTKIT_BUFFER_REQUEST_SIZE GENMASK_ULL(51, 44) +#define APPLE_RTKIT_BUFFER_REQUEST_IOVA GENMASK_ULL(43, 0) + +#define APPLE_RTKIT_SYSLOG_TYPE GENMASK_ULL(59, 52) + +#define APPLE_RTKIT_SYSLOG_LOG 5 + +#define APPLE_RTKIT_SYSLOG_INIT 8 +#define APPLE_RTKIT_SYSLOG_N_ENTRIES GENMASK_ULL(7, 0) +#define APPLE_RTKIT_SYSLOG_MSG_SIZE GENMASK_ULL(31, 24) + +#define APPLE_RTKIT_OSLOG_TYPE GENMASK_ULL(63, 56) +#define APPLE_RTKIT_OSLOG_BUFFER_REQUEST 1 +#define APPLE_RTKIT_OSLOG_SIZE GENMASK_ULL(55, 36) +#define APPLE_RTKIT_OSLOG_IOVA GENMASK_ULL(35, 0) + +#define APPLE_RTKIT_MIN_SUPPORTED_VERSION 11 +#define APPLE_RTKIT_MAX_SUPPORTED_VERSION 12 + +struct apple_rtkit_rx_work { + struct apple_rtkit *rtk; + u8 ep; + u64 msg; + struct work_struct work; +}; + +bool apple_rtkit_is_running(struct apple_rtkit *rtk) +{ + if (rtk->crashed) + return false; + if ((rtk->iop_power_state & 0xff) != APPLE_RTKIT_PWR_STATE_ON) + return false; + if ((rtk->ap_power_state & 0xff) != APPLE_RTKIT_PWR_STATE_ON) + return false; + return true; +} +EXPORT_SYMBOL_GPL(apple_rtkit_is_running); + +bool apple_rtkit_is_crashed(struct apple_rtkit *rtk) +{ + return rtk->crashed; +} +EXPORT_SYMBOL_GPL(apple_rtkit_is_crashed); + +static int apple_rtkit_management_send(struct apple_rtkit *rtk, u8 type, + u64 msg) +{ + int ret; + + msg &= ~APPLE_RTKIT_MGMT_TYPE; + msg |= FIELD_PREP(APPLE_RTKIT_MGMT_TYPE, type); + ret = apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_MGMT, msg, NULL, false); + + if (ret) + dev_err(rtk->dev, "RTKit: Failed to send management message: %d\n", ret); + + return ret; +} + +static void apple_rtkit_management_rx_hello(struct apple_rtkit *rtk, u64 msg) +{ + u64 reply; + + int min_ver = FIELD_GET(APPLE_RTKIT_MGMT_HELLO_MINVER, msg); + int max_ver = FIELD_GET(APPLE_RTKIT_MGMT_HELLO_MAXVER, msg); + int want_ver = min(APPLE_RTKIT_MAX_SUPPORTED_VERSION, max_ver); + + dev_dbg(rtk->dev, "RTKit: Min ver %d, max ver %d\n", min_ver, max_ver); + + if (min_ver > APPLE_RTKIT_MAX_SUPPORTED_VERSION) { + dev_err(rtk->dev, "RTKit: Firmware min version %d is too new\n", + min_ver); + goto abort_boot; + } + + if (max_ver < APPLE_RTKIT_MIN_SUPPORTED_VERSION) { + dev_err(rtk->dev, "RTKit: Firmware max version %d is too old\n", + max_ver); + goto abort_boot; + } + + dev_info(rtk->dev, "RTKit: Initializing (protocol version %d)\n", + want_ver); + rtk->version = want_ver; + + reply = FIELD_PREP(APPLE_RTKIT_MGMT_HELLO_MINVER, want_ver); + reply |= FIELD_PREP(APPLE_RTKIT_MGMT_HELLO_MAXVER, want_ver); + apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_HELLO_REPLY, reply); + + return; + +abort_boot: + rtk->boot_result = -EINVAL; + complete_all(&rtk->epmap_completion); +} + +static void apple_rtkit_management_rx_epmap(struct apple_rtkit *rtk, u64 msg) +{ + int i, ep; + u64 reply; + unsigned long bitmap = FIELD_GET(APPLE_RTKIT_MGMT_EPMAP_BITMAP, msg); + u32 base = FIELD_GET(APPLE_RTKIT_MGMT_EPMAP_BASE, msg); + + dev_dbg(rtk->dev, + "RTKit: received endpoint bitmap 0x%lx with base 0x%x\n", + bitmap, base); + + for_each_set_bit(i, &bitmap, 32) { + ep = 32 * base + i; + dev_dbg(rtk->dev, "RTKit: Discovered endpoint 0x%02x\n", ep); + set_bit(ep, rtk->endpoints); + } + + reply = FIELD_PREP(APPLE_RTKIT_MGMT_EPMAP_BASE, base); + if (msg & APPLE_RTKIT_MGMT_EPMAP_LAST) + reply |= APPLE_RTKIT_MGMT_EPMAP_LAST; + else + reply |= APPLE_RTKIT_MGMT_EPMAP_REPLY_MORE; + + apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_EPMAP_REPLY, reply); + + if (!(msg & APPLE_RTKIT_MGMT_EPMAP_LAST)) + return; + + for_each_set_bit(ep, rtk->endpoints, APPLE_RTKIT_APP_ENDPOINT_START) { + switch (ep) { + /* the management endpoint is started by default */ + case APPLE_RTKIT_EP_MGMT: + break; + + /* without starting these RTKit refuses to boot */ + case APPLE_RTKIT_EP_SYSLOG: + case APPLE_RTKIT_EP_CRASHLOG: + case APPLE_RTKIT_EP_DEBUG: + case APPLE_RTKIT_EP_IOREPORT: + case APPLE_RTKIT_EP_OSLOG: + dev_dbg(rtk->dev, + "RTKit: Starting system endpoint 0x%02x\n", ep); + apple_rtkit_start_ep(rtk, ep); + break; + + default: + dev_warn(rtk->dev, + "RTKit: Unknown system endpoint: 0x%02x\n", + ep); + } + } + + rtk->boot_result = 0; + complete_all(&rtk->epmap_completion); +} + +static void apple_rtkit_management_rx_iop_pwr_ack(struct apple_rtkit *rtk, + u64 msg) +{ + unsigned int new_state = FIELD_GET(APPLE_RTKIT_MGMT_PWR_STATE, msg); + + dev_dbg(rtk->dev, "RTKit: IOP power state transition: 0x%x -> 0x%x\n", + rtk->iop_power_state, new_state); + rtk->iop_power_state = new_state; + + complete_all(&rtk->iop_pwr_ack_completion); +} + +static void apple_rtkit_management_rx_ap_pwr_ack(struct apple_rtkit *rtk, + u64 msg) +{ + unsigned int new_state = FIELD_GET(APPLE_RTKIT_MGMT_PWR_STATE, msg); + + dev_dbg(rtk->dev, "RTKit: AP power state transition: 0x%x -> 0x%x\n", + rtk->ap_power_state, new_state); + rtk->ap_power_state = new_state; + + complete_all(&rtk->ap_pwr_ack_completion); +} + +static void apple_rtkit_management_rx(struct apple_rtkit *rtk, u64 msg) +{ + u8 type = FIELD_GET(APPLE_RTKIT_MGMT_TYPE, msg); + + switch (type) { + case APPLE_RTKIT_MGMT_HELLO: + apple_rtkit_management_rx_hello(rtk, msg); + break; + case APPLE_RTKIT_MGMT_EPMAP: + apple_rtkit_management_rx_epmap(rtk, msg); + break; + case APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE_ACK: + apple_rtkit_management_rx_iop_pwr_ack(rtk, msg); + break; + case APPLE_RTKIT_MGMT_SET_AP_PWR_STATE_ACK: + apple_rtkit_management_rx_ap_pwr_ack(rtk, msg); + break; + default: + dev_warn( + rtk->dev, + "RTKit: unknown management message: 0x%llx (type: 0x%02x)\n", + msg, type); + } +} + +static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk, + struct apple_rtkit_shmem *buffer, + u8 ep, u64 msg) +{ + u64 reply; + int err; + + /* The different size vs. IOVA shifts look odd but are indeed correct this way */ + if (ep == APPLE_RTKIT_EP_OSLOG) { + buffer->size = FIELD_GET(APPLE_RTKIT_OSLOG_SIZE, msg); + buffer->iova = FIELD_GET(APPLE_RTKIT_OSLOG_IOVA, msg) << 12; + } else { + buffer->size = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_SIZE, msg) << 12; + buffer->iova = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_IOVA, msg); + } + + buffer->buffer = NULL; + buffer->iomem = NULL; + buffer->is_mapped = false; + + dev_dbg(rtk->dev, "RTKit: buffer request for 0x%zx bytes at %pad\n", + buffer->size, &buffer->iova); + + if (buffer->iova && !rtk->ops->shmem_setup) { + err = -EINVAL; + goto error; + } + + if (rtk->ops->shmem_setup) { + err = rtk->ops->shmem_setup(rtk->cookie, buffer); + if (err) + goto error; + } else { + buffer->buffer = dma_alloc_coherent(rtk->dev, buffer->size, + &buffer->iova, GFP_KERNEL); + if (!buffer->buffer) { + err = -ENOMEM; + goto error; + } + } + + if (!buffer->is_mapped) { + /* oslog uses different fields and needs a shifted IOVA instead of size */ + if (ep == APPLE_RTKIT_EP_OSLOG) { + reply = FIELD_PREP(APPLE_RTKIT_OSLOG_TYPE, + APPLE_RTKIT_OSLOG_BUFFER_REQUEST); + reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_SIZE, buffer->size); + reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_IOVA, + buffer->iova >> 12); + } else { + reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE, + APPLE_RTKIT_BUFFER_REQUEST); + reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE, + buffer->size >> 12); + reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA, + buffer->iova); + } + apple_rtkit_send_message(rtk, ep, reply, NULL, false); + } + + return 0; + +error: + dev_err(rtk->dev, "RTKit: failed buffer request for 0x%zx bytes (%d)\n", + buffer->size, err); + + buffer->buffer = NULL; + buffer->iomem = NULL; + buffer->iova = 0; + buffer->size = 0; + buffer->is_mapped = false; + return err; +} + +static void apple_rtkit_free_buffer(struct apple_rtkit *rtk, + struct apple_rtkit_shmem *bfr) +{ + if (bfr->size == 0) + return; + + if (rtk->ops->shmem_destroy) + rtk->ops->shmem_destroy(rtk->cookie, bfr); + else if (bfr->buffer) + dma_free_coherent(rtk->dev, bfr->size, bfr->buffer, bfr->iova); + + bfr->buffer = NULL; + bfr->iomem = NULL; + bfr->iova = 0; + bfr->size = 0; + bfr->is_mapped = false; +} + +static void apple_rtkit_memcpy(struct apple_rtkit *rtk, void *dst, + struct apple_rtkit_shmem *bfr, size_t offset, + size_t len) +{ + if (bfr->iomem) + memcpy_fromio(dst, bfr->iomem + offset, len); + else + memcpy(dst, bfr->buffer + offset, len); +} + +static void apple_rtkit_crashlog_rx(struct apple_rtkit *rtk, u64 msg) +{ + u8 type = FIELD_GET(APPLE_RTKIT_SYSLOG_TYPE, msg); + u8 *bfr; + + if (type != APPLE_RTKIT_CRASHLOG_CRASH) { + dev_warn(rtk->dev, "RTKit: Unknown crashlog message: %llx\n", + msg); + return; + } + + if (!rtk->crashlog_buffer.size) { + apple_rtkit_common_rx_get_buffer(rtk, &rtk->crashlog_buffer, + APPLE_RTKIT_EP_CRASHLOG, msg); + return; + } + + dev_err(rtk->dev, "RTKit: co-processor has crashed\n"); + + /* + * create a shadow copy here to make sure the co-processor isn't able + * to change the log while we're dumping it. this also ensures + * the buffer is in normal memory and not iomem for e.g. the SMC + */ + bfr = kzalloc(rtk->crashlog_buffer.size, GFP_KERNEL); + if (bfr) { + apple_rtkit_memcpy(rtk, bfr, &rtk->crashlog_buffer, 0, + rtk->crashlog_buffer.size); + apple_rtkit_crashlog_dump(rtk, bfr, rtk->crashlog_buffer.size); + } else { + dev_err(rtk->dev, + "RTKit: Couldn't allocate crashlog shadow buffer\n"); + } + + rtk->crashed = true; + if (rtk->ops->crashed) + rtk->ops->crashed(rtk->cookie, bfr, rtk->crashlog_buffer.size); + + kfree(bfr); +} + +static void apple_rtkit_ioreport_rx(struct apple_rtkit *rtk, u64 msg) +{ + u8 type = FIELD_GET(APPLE_RTKIT_SYSLOG_TYPE, msg); + + switch (type) { + case APPLE_RTKIT_BUFFER_REQUEST: + apple_rtkit_common_rx_get_buffer(rtk, &rtk->ioreport_buffer, + APPLE_RTKIT_EP_IOREPORT, msg); + break; + /* unknown, must be ACKed or the co-processor will hang */ + case 0x8: + case 0xc: + apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_IOREPORT, msg, + NULL, false); + break; + default: + dev_warn(rtk->dev, "RTKit: Unknown ioreport message: %llx\n", + msg); + } +} + +static void apple_rtkit_syslog_rx_init(struct apple_rtkit *rtk, u64 msg) +{ + rtk->syslog_n_entries = FIELD_GET(APPLE_RTKIT_SYSLOG_N_ENTRIES, msg); + rtk->syslog_msg_size = FIELD_GET(APPLE_RTKIT_SYSLOG_MSG_SIZE, msg); + + rtk->syslog_msg_buffer = kzalloc(rtk->syslog_msg_size, GFP_KERNEL); + + dev_dbg(rtk->dev, + "RTKit: syslog initialized: entries: %zd, msg_size: %zd\n", + rtk->syslog_n_entries, rtk->syslog_msg_size); +} + +static bool should_crop_syslog_char(char c) +{ + return c == '\n' || c == '\r' || c == ' ' || c == '\0'; +} + +static void apple_rtkit_syslog_rx_log(struct apple_rtkit *rtk, u64 msg) +{ + u8 idx = msg & 0xff; + char log_context[24]; + size_t entry_size = 0x20 + rtk->syslog_msg_size; + int msglen; + + if (!rtk->syslog_msg_buffer) { + dev_warn( + rtk->dev, + "RTKit: received syslog message but no syslog_msg_buffer\n"); + goto done; + } + if (!rtk->syslog_buffer.size) { + dev_warn( + rtk->dev, + "RTKit: received syslog message but syslog_buffer.size is zero\n"); + goto done; + } + if (!rtk->syslog_buffer.buffer && !rtk->syslog_buffer.iomem) { + dev_warn( + rtk->dev, + "RTKit: received syslog message but no syslog_buffer.buffer or syslog_buffer.iomem\n"); + goto done; + } + if (idx > rtk->syslog_n_entries) { + dev_warn(rtk->dev, "RTKit: syslog index %d out of range\n", + idx); + goto done; + } + + apple_rtkit_memcpy(rtk, log_context, &rtk->syslog_buffer, + idx * entry_size + 8, sizeof(log_context)); + apple_rtkit_memcpy(rtk, rtk->syslog_msg_buffer, &rtk->syslog_buffer, + idx * entry_size + 8 + sizeof(log_context), + rtk->syslog_msg_size); + + log_context[sizeof(log_context) - 1] = 0; + + msglen = strnlen(rtk->syslog_msg_buffer, rtk->syslog_msg_size - 1); + while (msglen > 0 && + should_crop_syslog_char(rtk->syslog_msg_buffer[msglen - 1])) + msglen--; + + rtk->syslog_msg_buffer[msglen] = 0; + dev_info(rtk->dev, "RTKit: syslog message: %s: %s\n", log_context, + rtk->syslog_msg_buffer); + +done: + apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_SYSLOG, msg, NULL, false); +} + +static void apple_rtkit_syslog_rx(struct apple_rtkit *rtk, u64 msg) +{ + u8 type = FIELD_GET(APPLE_RTKIT_SYSLOG_TYPE, msg); + + switch (type) { + case APPLE_RTKIT_BUFFER_REQUEST: + apple_rtkit_common_rx_get_buffer(rtk, &rtk->syslog_buffer, + APPLE_RTKIT_EP_SYSLOG, msg); + break; + case APPLE_RTKIT_SYSLOG_INIT: + apple_rtkit_syslog_rx_init(rtk, msg); + break; + case APPLE_RTKIT_SYSLOG_LOG: + apple_rtkit_syslog_rx_log(rtk, msg); + break; + default: + dev_warn(rtk->dev, "RTKit: Unknown syslog message: %llx\n", + msg); + } +} + +static void apple_rtkit_oslog_rx(struct apple_rtkit *rtk, u64 msg) +{ + u8 type = FIELD_GET(APPLE_RTKIT_OSLOG_TYPE, msg); + + switch (type) { + case APPLE_RTKIT_OSLOG_BUFFER_REQUEST: + apple_rtkit_common_rx_get_buffer(rtk, &rtk->oslog_buffer, + APPLE_RTKIT_EP_OSLOG, msg); + break; + default: + dev_warn(rtk->dev, "RTKit: Unknown oslog message: %llx\n", + msg); + } +} + +static void apple_rtkit_rx_work(struct work_struct *work) +{ + struct apple_rtkit_rx_work *rtk_work = + container_of(work, struct apple_rtkit_rx_work, work); + struct apple_rtkit *rtk = rtk_work->rtk; + + switch (rtk_work->ep) { + case APPLE_RTKIT_EP_MGMT: + apple_rtkit_management_rx(rtk, rtk_work->msg); + break; + case APPLE_RTKIT_EP_CRASHLOG: + apple_rtkit_crashlog_rx(rtk, rtk_work->msg); + break; + case APPLE_RTKIT_EP_SYSLOG: + apple_rtkit_syslog_rx(rtk, rtk_work->msg); + break; + case APPLE_RTKIT_EP_IOREPORT: + apple_rtkit_ioreport_rx(rtk, rtk_work->msg); + break; + case APPLE_RTKIT_EP_OSLOG: + apple_rtkit_oslog_rx(rtk, rtk_work->msg); + break; + case APPLE_RTKIT_APP_ENDPOINT_START ... 0xff: + if (rtk->ops->recv_message) + rtk->ops->recv_message(rtk->cookie, rtk_work->ep, + rtk_work->msg); + else + dev_warn( + rtk->dev, + "Received unexpected message to EP%02d: %llx\n", + rtk_work->ep, rtk_work->msg); + break; + default: + dev_warn(rtk->dev, + "RTKit: message to unknown endpoint %02x: %llx\n", + rtk_work->ep, rtk_work->msg); + } + + kfree(rtk_work); +} + +static void apple_rtkit_rx(struct apple_mbox *mbox, struct apple_mbox_msg msg, + void *cookie) +{ + struct apple_rtkit *rtk = cookie; + struct apple_rtkit_rx_work *work; + u8 ep = msg.msg1; + + /* + * The message was read from a MMIO FIFO and we have to make + * sure all reads from buffers sent with that message happen + * afterwards. + */ + dma_rmb(); + + if (!test_bit(ep, rtk->endpoints)) + dev_warn(rtk->dev, + "RTKit: Message to undiscovered endpoint 0x%02x\n", + ep); + + if (ep >= APPLE_RTKIT_APP_ENDPOINT_START && + rtk->ops->recv_message_early && + rtk->ops->recv_message_early(rtk->cookie, ep, msg.msg0)) + return; + + work = kzalloc(sizeof(*work), GFP_ATOMIC); + if (!work) + return; + + work->rtk = rtk; + work->ep = ep; + work->msg = msg.msg0; + INIT_WORK(&work->work, apple_rtkit_rx_work); + queue_work(rtk->wq, &work->work); +} + +int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message, + struct completion *completion, bool atomic) +{ + struct apple_mbox_msg msg = { + .msg0 = message, + .msg1 = ep, + }; + + if (rtk->crashed) { + dev_warn(rtk->dev, + "RTKit: Device is crashed, cannot send message\n"); + return -EINVAL; + } + + if (ep >= APPLE_RTKIT_APP_ENDPOINT_START && + !apple_rtkit_is_running(rtk)) { + dev_warn(rtk->dev, + "RTKit: Endpoint 0x%02x is not running, cannot send message\n", ep); + return -EINVAL; + } + + /* + * The message will be sent with a MMIO write. We need the barrier + * here to ensure any previous writes to buffers are visible to the + * device before that MMIO write happens. + */ + dma_wmb(); + + return apple_mbox_send(rtk->mbox, msg, atomic); +} +EXPORT_SYMBOL_GPL(apple_rtkit_send_message); + +int apple_rtkit_poll(struct apple_rtkit *rtk) +{ + return apple_mbox_poll(rtk->mbox); +} +EXPORT_SYMBOL_GPL(apple_rtkit_poll); + +int apple_rtkit_start_ep(struct apple_rtkit *rtk, u8 endpoint) +{ + u64 msg; + + if (!test_bit(endpoint, rtk->endpoints)) + return -EINVAL; + if (endpoint >= APPLE_RTKIT_APP_ENDPOINT_START && + !apple_rtkit_is_running(rtk)) + return -EINVAL; + + msg = FIELD_PREP(APPLE_RTKIT_MGMT_STARTEP_EP, endpoint); + msg |= APPLE_RTKIT_MGMT_STARTEP_FLAG; + apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_STARTEP, msg); + + return 0; +} +EXPORT_SYMBOL_GPL(apple_rtkit_start_ep); + +struct apple_rtkit *apple_rtkit_init(struct device *dev, void *cookie, + const char *mbox_name, int mbox_idx, + const struct apple_rtkit_ops *ops) +{ + struct apple_rtkit *rtk; + int ret; + + if (!ops) + return ERR_PTR(-EINVAL); + + rtk = kzalloc(sizeof(*rtk), GFP_KERNEL); + if (!rtk) + return ERR_PTR(-ENOMEM); + + rtk->dev = dev; + rtk->cookie = cookie; + rtk->ops = ops; + + init_completion(&rtk->epmap_completion); + init_completion(&rtk->iop_pwr_ack_completion); + init_completion(&rtk->ap_pwr_ack_completion); + + bitmap_zero(rtk->endpoints, APPLE_RTKIT_MAX_ENDPOINTS); + set_bit(APPLE_RTKIT_EP_MGMT, rtk->endpoints); + + if (mbox_name) + rtk->mbox = apple_mbox_get_byname(dev, mbox_name); + else + rtk->mbox = apple_mbox_get(dev, mbox_idx); + + if (IS_ERR(rtk->mbox)) { + ret = PTR_ERR(rtk->mbox); + goto free_rtk; + } + + rtk->mbox->rx = apple_rtkit_rx; + rtk->mbox->cookie = rtk; + + rtk->wq = alloc_ordered_workqueue("rtkit-%s", WQ_HIGHPRI | WQ_MEM_RECLAIM, + dev_name(rtk->dev)); + if (!rtk->wq) { + ret = -ENOMEM; + goto free_rtk; + } + + ret = apple_mbox_start(rtk->mbox); + if (ret) + goto destroy_wq; + + return rtk; + +destroy_wq: + destroy_workqueue(rtk->wq); +free_rtk: + kfree(rtk); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(apple_rtkit_init); + +static int apple_rtkit_wait_for_completion(struct completion *c) +{ + long t; + + t = wait_for_completion_interruptible_timeout(c, + msecs_to_jiffies(1000)); + if (t < 0) + return t; + else if (t == 0) + return -ETIME; + else + return 0; +} + +int apple_rtkit_reinit(struct apple_rtkit *rtk) +{ + /* make sure we don't handle any messages while reinitializing */ + apple_mbox_stop(rtk->mbox); + flush_workqueue(rtk->wq); + + apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer); + apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer); + + kfree(rtk->syslog_msg_buffer); + + rtk->syslog_msg_buffer = NULL; + rtk->syslog_n_entries = 0; + rtk->syslog_msg_size = 0; + + bitmap_zero(rtk->endpoints, APPLE_RTKIT_MAX_ENDPOINTS); + set_bit(APPLE_RTKIT_EP_MGMT, rtk->endpoints); + + reinit_completion(&rtk->epmap_completion); + reinit_completion(&rtk->iop_pwr_ack_completion); + reinit_completion(&rtk->ap_pwr_ack_completion); + + rtk->crashed = false; + rtk->iop_power_state = APPLE_RTKIT_PWR_STATE_OFF; + rtk->ap_power_state = APPLE_RTKIT_PWR_STATE_OFF; + + return apple_mbox_start(rtk->mbox); +} +EXPORT_SYMBOL_GPL(apple_rtkit_reinit); + +static int apple_rtkit_set_ap_power_state(struct apple_rtkit *rtk, + unsigned int state) +{ + u64 msg; + int ret; + + reinit_completion(&rtk->ap_pwr_ack_completion); + + msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state); + ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_AP_PWR_STATE, + msg); + if (ret) + return ret; + + ret = apple_rtkit_wait_for_completion(&rtk->ap_pwr_ack_completion); + if (ret) + return ret; + + if (rtk->ap_power_state != state) + return -EINVAL; + return 0; +} + +static int apple_rtkit_set_iop_power_state(struct apple_rtkit *rtk, + unsigned int state) +{ + u64 msg; + int ret; + + reinit_completion(&rtk->iop_pwr_ack_completion); + + msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state); + ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE, + msg); + if (ret) + return ret; + + ret = apple_rtkit_wait_for_completion(&rtk->iop_pwr_ack_completion); + if (ret) + return ret; + + if (rtk->iop_power_state != state) + return -EINVAL; + return 0; +} + +int apple_rtkit_boot(struct apple_rtkit *rtk) +{ + int ret; + + if (apple_rtkit_is_running(rtk)) + return 0; + if (rtk->crashed) + return -EINVAL; + + dev_dbg(rtk->dev, "RTKit: waiting for boot to finish\n"); + ret = apple_rtkit_wait_for_completion(&rtk->epmap_completion); + if (ret) + return ret; + if (rtk->boot_result) + return rtk->boot_result; + + dev_dbg(rtk->dev, "RTKit: waiting for IOP power state ACK\n"); + ret = apple_rtkit_wait_for_completion(&rtk->iop_pwr_ack_completion); + if (ret) + return ret; + + return apple_rtkit_set_ap_power_state(rtk, APPLE_RTKIT_PWR_STATE_ON); +} +EXPORT_SYMBOL_GPL(apple_rtkit_boot); + +int apple_rtkit_shutdown(struct apple_rtkit *rtk) +{ + int ret; + + /* if OFF is used here the co-processor will not wake up again */ + ret = apple_rtkit_set_ap_power_state(rtk, + APPLE_RTKIT_PWR_STATE_QUIESCED); + if (ret) + return ret; + + ret = apple_rtkit_set_iop_power_state(rtk, APPLE_RTKIT_PWR_STATE_SLEEP); + if (ret) + return ret; + + return apple_rtkit_reinit(rtk); +} +EXPORT_SYMBOL_GPL(apple_rtkit_shutdown); + +int apple_rtkit_idle(struct apple_rtkit *rtk) +{ + int ret; + + /* if OFF is used here the co-processor will not wake up again */ + ret = apple_rtkit_set_ap_power_state(rtk, + APPLE_RTKIT_PWR_STATE_IDLE); + if (ret) + return ret; + + ret = apple_rtkit_set_iop_power_state(rtk, APPLE_RTKIT_PWR_STATE_IDLE); + if (ret) + return ret; + + rtk->iop_power_state = APPLE_RTKIT_PWR_STATE_IDLE; + rtk->ap_power_state = APPLE_RTKIT_PWR_STATE_IDLE; + return 0; +} +EXPORT_SYMBOL_GPL(apple_rtkit_idle); + +int apple_rtkit_quiesce(struct apple_rtkit *rtk) +{ + int ret; + + ret = apple_rtkit_set_ap_power_state(rtk, + APPLE_RTKIT_PWR_STATE_QUIESCED); + if (ret) + return ret; + + ret = apple_rtkit_set_iop_power_state(rtk, + APPLE_RTKIT_PWR_STATE_QUIESCED); + if (ret) + return ret; + + ret = apple_rtkit_reinit(rtk); + if (ret) + return ret; + + rtk->iop_power_state = APPLE_RTKIT_PWR_STATE_QUIESCED; + rtk->ap_power_state = APPLE_RTKIT_PWR_STATE_QUIESCED; + return 0; +} +EXPORT_SYMBOL_GPL(apple_rtkit_quiesce); + +int apple_rtkit_wake(struct apple_rtkit *rtk) +{ + u64 msg; + int ret; + + if (apple_rtkit_is_running(rtk)) + return -EINVAL; + + reinit_completion(&rtk->iop_pwr_ack_completion); + + /* + * Use open-coded apple_rtkit_set_iop_power_state since apple_rtkit_boot + * will wait for the completion anyway. + */ + msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_INIT); + ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE, + msg); + if (ret) + return ret; + + return apple_rtkit_boot(rtk); +} +EXPORT_SYMBOL_GPL(apple_rtkit_wake); + +void apple_rtkit_free(struct apple_rtkit *rtk) +{ + apple_mbox_stop(rtk->mbox); + destroy_workqueue(rtk->wq); + + apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer); + apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer); + apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer); + + kfree(rtk->syslog_msg_buffer); + kfree(rtk); +} +EXPORT_SYMBOL_GPL(apple_rtkit_free); + +static void apple_rtkit_free_wrapper(void *data) +{ + apple_rtkit_free(data); +} + +struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie, + const char *mbox_name, int mbox_idx, + const struct apple_rtkit_ops *ops) +{ + struct apple_rtkit *rtk; + int ret; + + rtk = apple_rtkit_init(dev, cookie, mbox_name, mbox_idx, ops); + if (IS_ERR(rtk)) + return rtk; + + ret = devm_add_action_or_reset(dev, apple_rtkit_free_wrapper, rtk); + if (ret) + return ERR_PTR(ret); + + return rtk; +} +EXPORT_SYMBOL_GPL(devm_apple_rtkit_init); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Apple RTKit driver"); diff --git a/drivers/soc/apple/sart.c b/drivers/soc/apple/sart.c new file mode 100644 index 000000000000..9eaf3febb382 --- /dev/null +++ b/drivers/soc/apple/sart.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SART device driver + * Copyright (C) The Asahi Linux Contributors + * + * Apple SART is a simple address filter for some DMA transactions. + * Regions of physical memory must be added to the SART's allow + * list before any DMA can target these. Unlike a proper + * IOMMU no remapping can be done and special support in the + * consumer driver is required since not all DMA transactions of + * a single device are subject to SART filtering. + */ + +#include <linux/soc/apple/sart.h> +#include <linux/atomic.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#define APPLE_SART_MAX_ENTRIES 16 + +/* SARTv0 registers */ +#define APPLE_SART0_CONFIG(idx) (0x00 + 4 * (idx)) +#define APPLE_SART0_CONFIG_FLAGS GENMASK(28, 24) +#define APPLE_SART0_CONFIG_SIZE GENMASK(18, 0) +#define APPLE_SART0_CONFIG_SIZE_SHIFT 12 +#define APPLE_SART0_CONFIG_SIZE_MAX GENMASK(18, 0) + +#define APPLE_SART0_PADDR(idx) (0x40 + 4 * (idx)) +#define APPLE_SART0_PADDR_SHIFT 12 + +#define APPLE_SART0_FLAGS_ALLOW 0xf + +/* SARTv2 registers */ +#define APPLE_SART2_CONFIG(idx) (0x00 + 4 * (idx)) +#define APPLE_SART2_CONFIG_FLAGS GENMASK(31, 24) +#define APPLE_SART2_CONFIG_SIZE GENMASK(23, 0) +#define APPLE_SART2_CONFIG_SIZE_SHIFT 12 +#define APPLE_SART2_CONFIG_SIZE_MAX GENMASK(23, 0) + +#define APPLE_SART2_PADDR(idx) (0x40 + 4 * (idx)) +#define APPLE_SART2_PADDR_SHIFT 12 + +#define APPLE_SART2_FLAGS_ALLOW 0xff + +/* SARTv3 registers */ +#define APPLE_SART3_CONFIG(idx) (0x00 + 4 * (idx)) + +#define APPLE_SART3_PADDR(idx) (0x40 + 4 * (idx)) +#define APPLE_SART3_PADDR_SHIFT 12 + +#define APPLE_SART3_SIZE(idx) (0x80 + 4 * (idx)) +#define APPLE_SART3_SIZE_SHIFT 12 +#define APPLE_SART3_SIZE_MAX GENMASK(29, 0) + +#define APPLE_SART3_FLAGS_ALLOW 0xff + +struct apple_sart_ops { + void (*get_entry)(struct apple_sart *sart, int index, u8 *flags, + phys_addr_t *paddr, size_t *size); + void (*set_entry)(struct apple_sart *sart, int index, u8 flags, + phys_addr_t paddr_shifted, size_t size_shifted); + /* This is probably a bitfield but the exact meaning of each bit is unknown. */ + unsigned int flags_allow; + unsigned int size_shift; + unsigned int paddr_shift; + size_t size_max; +}; + +struct apple_sart { + struct device *dev; + void __iomem *regs; + + const struct apple_sart_ops *ops; + + unsigned long protected_entries; + unsigned long used_entries; +}; + +static void sart0_get_entry(struct apple_sart *sart, int index, u8 *flags, + phys_addr_t *paddr, size_t *size) +{ + u32 cfg = readl(sart->regs + APPLE_SART0_CONFIG(index)); + phys_addr_t paddr_ = readl(sart->regs + APPLE_SART0_PADDR(index)); + size_t size_ = FIELD_GET(APPLE_SART0_CONFIG_SIZE, cfg); + + *flags = FIELD_GET(APPLE_SART0_CONFIG_FLAGS, cfg); + *size = size_ << APPLE_SART0_CONFIG_SIZE_SHIFT; + *paddr = paddr_ << APPLE_SART0_PADDR_SHIFT; +} + +static void sart0_set_entry(struct apple_sart *sart, int index, u8 flags, + phys_addr_t paddr_shifted, size_t size_shifted) +{ + u32 cfg; + + cfg = FIELD_PREP(APPLE_SART0_CONFIG_FLAGS, flags); + cfg |= FIELD_PREP(APPLE_SART0_CONFIG_SIZE, size_shifted); + + writel(paddr_shifted, sart->regs + APPLE_SART0_PADDR(index)); + writel(cfg, sart->regs + APPLE_SART0_CONFIG(index)); +} + +static struct apple_sart_ops sart_ops_v0 = { + .get_entry = sart0_get_entry, + .set_entry = sart0_set_entry, + .flags_allow = APPLE_SART0_FLAGS_ALLOW, + .size_shift = APPLE_SART0_CONFIG_SIZE_SHIFT, + .paddr_shift = APPLE_SART0_PADDR_SHIFT, + .size_max = APPLE_SART0_CONFIG_SIZE_MAX, +}; + +static void sart2_get_entry(struct apple_sart *sart, int index, u8 *flags, + phys_addr_t *paddr, size_t *size) +{ + u32 cfg = readl(sart->regs + APPLE_SART2_CONFIG(index)); + phys_addr_t paddr_ = readl(sart->regs + APPLE_SART2_PADDR(index)); + size_t size_ = FIELD_GET(APPLE_SART2_CONFIG_SIZE, cfg); + + *flags = FIELD_GET(APPLE_SART2_CONFIG_FLAGS, cfg); + *size = size_ << APPLE_SART2_CONFIG_SIZE_SHIFT; + *paddr = paddr_ << APPLE_SART2_PADDR_SHIFT; +} + +static void sart2_set_entry(struct apple_sart *sart, int index, u8 flags, + phys_addr_t paddr_shifted, size_t size_shifted) +{ + u32 cfg; + + cfg = FIELD_PREP(APPLE_SART2_CONFIG_FLAGS, flags); + cfg |= FIELD_PREP(APPLE_SART2_CONFIG_SIZE, size_shifted); + + writel(paddr_shifted, sart->regs + APPLE_SART2_PADDR(index)); + writel(cfg, sart->regs + APPLE_SART2_CONFIG(index)); +} + +static struct apple_sart_ops sart_ops_v2 = { + .get_entry = sart2_get_entry, + .set_entry = sart2_set_entry, + .flags_allow = APPLE_SART2_FLAGS_ALLOW, + .size_shift = APPLE_SART2_CONFIG_SIZE_SHIFT, + .paddr_shift = APPLE_SART2_PADDR_SHIFT, + .size_max = APPLE_SART2_CONFIG_SIZE_MAX, +}; + +static void sart3_get_entry(struct apple_sart *sart, int index, u8 *flags, + phys_addr_t *paddr, size_t *size) +{ + phys_addr_t paddr_ = readl(sart->regs + APPLE_SART3_PADDR(index)); + size_t size_ = readl(sart->regs + APPLE_SART3_SIZE(index)); + + *flags = readl(sart->regs + APPLE_SART3_CONFIG(index)); + *size = size_ << APPLE_SART3_SIZE_SHIFT; + *paddr = paddr_ << APPLE_SART3_PADDR_SHIFT; +} + +static void sart3_set_entry(struct apple_sart *sart, int index, u8 flags, + phys_addr_t paddr_shifted, size_t size_shifted) +{ + writel(paddr_shifted, sart->regs + APPLE_SART3_PADDR(index)); + writel(size_shifted, sart->regs + APPLE_SART3_SIZE(index)); + writel(flags, sart->regs + APPLE_SART3_CONFIG(index)); +} + +static struct apple_sart_ops sart_ops_v3 = { + .get_entry = sart3_get_entry, + .set_entry = sart3_set_entry, + .flags_allow = APPLE_SART3_FLAGS_ALLOW, + .size_shift = APPLE_SART3_SIZE_SHIFT, + .paddr_shift = APPLE_SART3_PADDR_SHIFT, + .size_max = APPLE_SART3_SIZE_MAX, +}; + +static int apple_sart_probe(struct platform_device *pdev) +{ + int i; + struct apple_sart *sart; + struct device *dev = &pdev->dev; + + sart = devm_kzalloc(dev, sizeof(*sart), GFP_KERNEL); + if (!sart) + return -ENOMEM; + + sart->dev = dev; + sart->ops = of_device_get_match_data(dev); + + sart->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sart->regs)) + return PTR_ERR(sart->regs); + + for (i = 0; i < APPLE_SART_MAX_ENTRIES; ++i) { + u8 flags; + size_t size; + phys_addr_t paddr; + + sart->ops->get_entry(sart, i, &flags, &paddr, &size); + + if (!flags) + continue; + + dev_dbg(sart->dev, + "SART bootloader entry: index %02d; flags: 0x%02x; paddr: %pa; size: 0x%zx\n", + i, flags, &paddr, size); + set_bit(i, &sart->protected_entries); + } + + platform_set_drvdata(pdev, sart); + return 0; +} + +struct apple_sart *devm_apple_sart_get(struct device *dev) +{ + struct device_node *sart_node; + struct platform_device *sart_pdev; + struct apple_sart *sart; + + sart_node = of_parse_phandle(dev->of_node, "apple,sart", 0); + if (!sart_node) + return ERR_PTR(-ENODEV); + + sart_pdev = of_find_device_by_node(sart_node); + of_node_put(sart_node); + + if (!sart_pdev) + return ERR_PTR(-ENODEV); + + sart = dev_get_drvdata(&sart_pdev->dev); + if (!sart) { + put_device(&sart_pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + device_link_add(dev, &sart_pdev->dev, + DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER); + + put_device(&sart_pdev->dev); + + return sart; +} +EXPORT_SYMBOL_GPL(devm_apple_sart_get); + +static int sart_set_entry(struct apple_sart *sart, int index, u8 flags, + phys_addr_t paddr, size_t size) +{ + if (size & ((1 << sart->ops->size_shift) - 1)) + return -EINVAL; + if (paddr & ((1 << sart->ops->paddr_shift) - 1)) + return -EINVAL; + + paddr >>= sart->ops->size_shift; + size >>= sart->ops->paddr_shift; + + if (size > sart->ops->size_max) + return -EINVAL; + + sart->ops->set_entry(sart, index, flags, paddr, size); + return 0; +} + +int apple_sart_add_allowed_region(struct apple_sart *sart, phys_addr_t paddr, + size_t size) +{ + int i, ret; + + for (i = 0; i < APPLE_SART_MAX_ENTRIES; ++i) { + if (test_bit(i, &sart->protected_entries)) + continue; + if (test_and_set_bit(i, &sart->used_entries)) + continue; + + ret = sart_set_entry(sart, i, sart->ops->flags_allow, paddr, + size); + if (ret) { + dev_dbg(sart->dev, + "unable to set entry %d to [%pa, 0x%zx]\n", + i, &paddr, size); + clear_bit(i, &sart->used_entries); + return ret; + } + + dev_dbg(sart->dev, "wrote [%pa, 0x%zx] to %d\n", &paddr, size, + i); + return 0; + } + + dev_warn(sart->dev, + "no free entries left to add [paddr: 0x%pa, size: 0x%zx]\n", + &paddr, size); + + return -EBUSY; +} +EXPORT_SYMBOL_GPL(apple_sart_add_allowed_region); + +int apple_sart_remove_allowed_region(struct apple_sart *sart, phys_addr_t paddr, + size_t size) +{ + int i; + + dev_dbg(sart->dev, + "will remove [paddr: %pa, size: 0x%zx] from allowed regions\n", + &paddr, size); + + for (i = 0; i < APPLE_SART_MAX_ENTRIES; ++i) { + u8 eflags; + size_t esize; + phys_addr_t epaddr; + + if (test_bit(i, &sart->protected_entries)) + continue; + + sart->ops->get_entry(sart, i, &eflags, &epaddr, &esize); + + if (epaddr != paddr || esize != size) + continue; + + sart->ops->set_entry(sart, i, 0, 0, 0); + + clear_bit(i, &sart->used_entries); + dev_dbg(sart->dev, "cleared entry %d\n", i); + return 0; + } + + dev_warn(sart->dev, "entry [paddr: 0x%pa, size: 0x%zx] not found\n", + &paddr, size); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(apple_sart_remove_allowed_region); + +static void apple_sart_shutdown(struct platform_device *pdev) +{ + struct apple_sart *sart = dev_get_drvdata(&pdev->dev); + int i; + + for (i = 0; i < APPLE_SART_MAX_ENTRIES; ++i) { + if (test_bit(i, &sart->protected_entries)) + continue; + + sart->ops->set_entry(sart, i, 0, 0, 0); + } +} + +static const struct of_device_id apple_sart_of_match[] = { + { + .compatible = "apple,t6000-sart", + .data = &sart_ops_v3, + }, + { + .compatible = "apple,t8103-sart", + .data = &sart_ops_v2, + }, + { + .compatible = "apple,t8015-sart", + .data = &sart_ops_v0, + }, + {} +}; +MODULE_DEVICE_TABLE(of, apple_sart_of_match); + +static struct platform_driver apple_sart_driver = { + .driver = { + .name = "apple-sart", + .of_match_table = apple_sart_of_match, + }, + .probe = apple_sart_probe, + .shutdown = apple_sart_shutdown, +}; +module_platform_driver(apple_sart_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Apple SART driver"); |
