| // 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_obj(*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_obj(*rtk); | |
| 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_poweroff(struct apple_rtkit *rtk) | |
| { | |
| int ret; | |
| ret = apple_rtkit_set_ap_power_state(rtk, APPLE_RTKIT_PWR_STATE_OFF); | |
| if (ret) | |
| return ret; | |
| ret = apple_rtkit_set_iop_power_state(rtk, APPLE_RTKIT_PWR_STATE_OFF); | |
| if (ret) | |
| return ret; | |
| return apple_rtkit_reinit(rtk); | |
| } | |
| EXPORT_SYMBOL_GPL(apple_rtkit_poweroff); | |
| 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"); |