/* * Copyright (C) 2011-2012 Apple Computer, Inc. All rights reserved. * * This document is the property of Apple Computer, Inc. * It is considered confidential and proprietary. * * This document may not be reproduced or transmitted in any form, * in whole or in part, without the express written permission of * Apple Computer, Inc. */ #include #include #include #include #include #include #include #include #include #include #include "csi_private.h" // protocol files #include #include "endpoints/management_ep.h" #include "endpoints/syslog_ep.h" #include "endpoints/console_ep.h" #include "endpoints/crashlog_ep.h" #include #include #include // XXX addresses the need for a common copy of this file #include "queue.h" static const MailboxEndpointConfig endpoint_config[] = MAILBOX_ENDPOINT_INITIALIZERS; #define MAILBOX_ENDPOINT_INITIALIZER_COUNT (int)(sizeof(endpoint_config)/sizeof(*endpoint_config)) #define EP_QUEUE_ADVANCE(_index, _size) ((_index) = ((((_index) + 1) >= _size) ? 0 : ((_index) + 1))) // A simple mechanism to put a governor on potentially high volume log scenarios #define LOG_GOV_PERIOD_USEC 5000000 typedef struct { int occ_count; u_int64_t last_log; } log_governor_t; typedef struct { bool registered; csi_coproc_t coproc; uint32_t index; const char *name; uint32_t rcv_queue_size; msg_payload_t *rcv_queue; uint32_t next_in; uint32_t next_out; uint32_t sm_alignment; struct task_event *rcv_not_empty_event; } endpoint_desc_t; typedef enum { COPROC_STATE_RESET = 0, COPROC_STATE_ERROR_FW, COPROC_STATE_ERROR_STARTING, COPROC_STATE_RUNNING, COPROC_STATE_STOPPED, } coproc_state_t; typedef struct { csi_coproc_t coproc; coproc_state_t state; coproc_feature_t feature; uint32_t send_timeout; uint32_t endpoint_count; endpoint_desc_t endpoints[CSI_MAX_ENDPOINTS]; fw_desc_t fw_desc; struct task_event power_state_event; log_governor_t gov_ep_queue_ovflw; log_governor_t gov_ep_not_reg; } csi_desc_t; static csi_desc_t *csi_descs[CSI_COPROC_MAX] = {NULL}; // // Private functions // static endpoint_desc_t *find_endpoint(csi_coproc_t which_coproc, ipc_endpoint_t which_ep); static int inbox_not_empty_task(void *arg); static void message_dispatcher(endpoint_desc_t *ep, msg_payload_t msg_payload); static void *allocate_shared_memory(uint32_t capacity, uint32_t alignment); static bool check_log_gov(log_governor_t *gov, csi_coproc_t which_coproc); // // csi_init // csi_status_t csi_init (csi_coproc_t which_coproc) { int j; csi_desc_t *csi; csi_status_t status; endpoint_desc_t *next_ep; if (which_coproc >= CSI_COPROC_MAX) { return CSI_STATUS_UNSUPPORTED; } // enforce single instance REQUIRE(csi_descs[which_coproc]==NULL); csi_descs[which_coproc] = malloc(sizeof(csi_desc_t)); REQUIRE(csi_descs[which_coproc]!=NULL); csi = csi_descs[which_coproc]; csi->coproc = which_coproc; csi->state = COPROC_STATE_RESET; csi->send_timeout = CSI_SEND_MESSAGE_TIMEOUT; csi->gov_ep_queue_ovflw.occ_count = 0; csi->gov_ep_not_reg.occ_count = 0; csi->feature = COPROC_FEATURE_NONE; // Create all endpoint structures - all are inactive until registered. csi->endpoint_count = 0; next_ep = csi->endpoints; for (j=CSI_MAX_ENDPOINTS; --j>=0;) { next_ep->registered = false; next_ep->coproc = csi->coproc; next_ep->index = 0; next_ep->name = "none"; next_ep->rcv_queue_size = 0; next_ep->rcv_queue = NULL; next_ep->next_in = 0; next_ep->next_out = 0; next_ep->rcv_not_empty_event = NULL; next_ep++; } next_ep = csi->endpoints; for (j=0; (jendpoint_countindex = endpoint_config[j].index; next_ep->name = endpoint_config[j].name; next_ep->rcv_queue_size = endpoint_config[j].rcv_queue_size; next_ep->rcv_queue = malloc(sizeof(*next_ep->rcv_queue) * endpoint_config[j].rcv_queue_size); REQUIRE(next_ep->rcv_queue!=NULL); next_ep->next_in = 0; next_ep->next_out = ~(0); next_ep++; csi->endpoint_count++; } } // create built-in endpoints bi_ep_mgmt_create(csi->coproc, &(csi->power_state_event)); bi_ep_crashlog_create(csi->coproc); bi_ep_console_create(csi->coproc); bi_ep_syslog_create(csi->coproc); // Find the firmware, prep it, start the coprocessor. if (CSI_COPROC_ANS == which_coproc) { // region is provided by iBoot csi->fw_desc.region_base = ASP_BASE; csi->fw_desc.region_size = (uint32_t)(ASP_SIZE); } else { // Allocate region for all other corpoc // XXX: what size is needed - to be added later when/if other coproc are supported. csi->fw_desc.region_base = 0x00; csi->fw_desc.region_size = 0x00; return CSI_STATUS_UNSUPPORTED; } status = fw_fixup_and_relocate(csi->coproc, &csi->fw_desc); if (status != CSI_STATUS_OK) { CSI_LOG(CRITICAL, which_coproc, "fw_fixup_and_relocate() error (%s)\n", CSI_STATUS_STR(status)); csi->state = COPROC_STATE_ERROR_FW; return status; } status = csi_start (which_coproc); if (status != CSI_STATUS_OK) { return status; } // create the power state event event_init(&(csi->power_state_event), EVENT_FLAG_AUTO_UNSIGNAL, false); return CSI_STATUS_OK; } csi_status_t csi_start (csi_coproc_t which_coproc) { csi_desc_t *csi; addr_t remap_base; uint64_t remap_size; int status; csi = csi_descs[which_coproc]; remap_base = (const addr_t)csi->fw_desc.region_base; remap_size = (const uint64_t)csi->fw_desc.region_size; status = akf_start(KFW(csi->coproc), remap_base, remap_size); if (status != 0) { CSI_LOG(CRITICAL, which_coproc, "akf_start() error: %d\n", status); csi->state = COPROC_STATE_ERROR_STARTING; return CSI_STATUS_ERROR; } csi_clr_feature (which_coproc, COPROC_FEATURE_SHUTDOWN_IN_PROGRESS); CSI_LOG(DEBUG_SPEW, which_coproc, "coproc started: remap base = %p, size = 0x%llx (feature=%x)\n", (void *)remap_base, remap_size, csi_get_feature(which_coproc)); csi->state = COPROC_STATE_RUNNING; // start the message reading task task_start(task_create("csi inbox_not_empty_task", inbox_not_empty_task, (void*)csi, kTaskStackSize_inbox_not_empty)); task_yield(); // give them a chance to run return CSI_STATUS_OK; } csi_status_t csi_panic_recover (csi_coproc_t which_coproc) { csi_desc_t *csi; csi_status_t status; csi = csi_descs[which_coproc]; // stop the AKF to reset it. csi_stop (which_coproc); // destroy all the allocated shared memory // force a power state change to off. handle_power_state_ack (mgmt_descs[which_coproc], CSI_PS_OFF); clock_reset_device(CLK_ANS); // give a chance to all other task to terminate task_yield(); // reset the memory status = fw_fixup_and_relocate (csi->coproc, &csi->fw_desc); if (status != CSI_STATUS_OK) { CSI_LOG(CRITICAL, which_coproc, "panic recovery failed: fw_fixup_and_relocate() error (%s)\n", CSI_STATUS_STR(status)); csi->state = COPROC_STATE_ERROR_FW; return status; } // restart the AKF return csi_start (which_coproc); } csi_status_t csi_late_init (void) { #if APPLICATION_IBOOT const char *boot_args; char *arg_str; uint32_t verbosity; verbosity = 9999; boot_args = env_get("boot-args"); if (boot_args != NULL) { arg_str = strstr(boot_args, "csi_syslog_verbosity="); if (arg_str != NULL) { verbosity = arg_str[strlen("csi_syslog_verbosity=")] - '0'; } } for (int j = 0; j < CSI_COPROC_MAX; j++) { bi_ep_syslog_verbosity(j, verbosity); } #endif return CSI_STATUS_OK; } csi_status_t csi_stop (csi_coproc_t which_coproc) { csi_desc_t *csi; csi = csi_descs[which_coproc]; akf_stop (KFW(csi->coproc)); csi->state = COPROC_STATE_STOPPED; return CSI_STATUS_OK; } csi_status_t csi_quiesce (enum boot_target target) { csi_status_t status = CSI_STATUS_OK; for (int j = 0; j < CSI_COPROC_MAX; j++) { csi_desc_t *csi = csi_descs[j]; if (NULL != csi) { if (csi->state == COPROC_STATE_RUNNING) { // put the coproc in suspend to ram for all state status = hibernate_iop (csi->coproc); } } } return status; } csi_status_t request_iop_power_state (csi_coproc_t which_coproc, csi_power_state_t ps) { csi_desc_t *csi; csi = csi_descs[which_coproc]; // if the power state event was previously signaled (panic recovery for example) // unsignal it was we need to wait for a new transition event_unsignal (&(csi->power_state_event)); // Request the IOP to transition to the specified power state // power message are always send thru the management channel bi_ep_mgmt_send_ps (which_coproc, ps); // Wait for the acknowledge message. event_wait (&(csi->power_state_event)); // make sure the power state is the one we want if (bi_ep_mgmt_get_power_state(csi->coproc) != ps) { return CSI_STATUS_ERROR; } return CSI_STATUS_OK; } csi_status_t quiesce_iop (csi_coproc_t which_coproc) { csi_desc_t *csi; csi_status_t status; csi = csi_descs[which_coproc]; // Request the new power state status = request_iop_power_state (which_coproc, CSI_PS_QUIESCE); if (CSI_STATUS_OK != status) { CSI_LOG(CRITICAL, csi->coproc, "Failed to quiesce\n"); return CSI_STATUS_ERROR; } CSI_LOG(CRITICAL, csi->coproc, "quiesced\n"); return CSI_STATUS_OK; } csi_status_t hibernate_iop (csi_coproc_t which_coproc) { csi_desc_t *csi; csi_status_t status; csi = csi_descs[which_coproc]; // Request the new power state csi_set_feature (which_coproc, COPROC_FEATURE_SHUTDOWN_IN_PROGRESS); status = request_iop_power_state (which_coproc, CSI_PS_SLEEP); if (CSI_STATUS_OK != status) { CSI_LOG(CRITICAL, csi->coproc, "Failed to hibernate\n"); return CSI_STATUS_ERROR; } CSI_LOG(CRITICAL, csi->coproc, "in suspend to RAM\n"); // now turn off the iop return csi_stop (which_coproc); } csi_status_t set_send_timeout (csi_coproc_t which_coproc, uint32_t timeout) { csi_desc_t *csi = csi_descs[which_coproc]; csi->send_timeout = timeout; return CSI_STATUS_OK; } uint32_t get_send_timeout (csi_coproc_t which_coproc) { csi_desc_t *csi = csi_descs[which_coproc]; return csi->send_timeout; } csi_status_t csi_set_feature (csi_coproc_t which_coproc, coproc_feature_t feature) { csi_desc_t *csi = csi_descs[which_coproc]; csi->feature = feature; return CSI_STATUS_OK; } coproc_feature_t csi_get_feature (csi_coproc_t which_coproc) { csi_desc_t *csi = csi_descs[which_coproc]; return csi->feature; } csi_status_t csi_clr_feature (csi_coproc_t which_coproc, coproc_feature_t feature) { csi_desc_t *csi = csi_descs[which_coproc]; csi->feature &= ~(feature); return CSI_STATUS_OK; } void csi_info (csi_coproc_t which_coproc) { fw_print_version (which_coproc, &(csi_descs[which_coproc]->fw_desc)); } //////////////////////////////////////////////////////////////////////////////// // allocate shared memory // static void * allocate_shared_memory(uint32_t capacity, uint32_t alignment) { uint32_t actual_capacity; void *sm; // We always allocate page-aligned and with multiples of the page size for alignment actual_capacity = roundup(capacity, PAGE_SIZE); alignment = roundup(alignment, PAGE_SIZE); // allocate sm = memalign(actual_capacity, alignment); maybe_do_cache_operation(CACHE_CLEAN, sm, actual_capacity); return sm; } static void free_shared_memory(void *sm) { free(sm); } static endpoint_desc_t * find_endpoint(csi_coproc_t which_coproc, ipc_endpoint_t which_ep) { int j; csi_desc_t *this_csi; endpoint_desc_t *next_ep; if (which_coproc < CSI_COPROC_MAX) { this_csi = csi_descs[which_coproc]; if (this_csi != NULL) { next_ep = this_csi->endpoints; for (j=this_csi->endpoint_count; --j>=0; next_ep++) { if (next_ep->index == which_ep) { return next_ep; } } } } return NULL; } //////////////////////////////////////////////////////////////////////////////// // inbox_not_empty_task // // Wait for and dispatch incoming messages from the IOP. // Get all the pending message in the mailbox and dispatch them to the appropriate // endpoint. Message to non existent endpoint are silently discarded. // static int inbox_not_empty_task(void *token) { volatile csi_desc_t *csi; int status; msg_payload_t msg; endpoint_desc_t *ep; REQUIRE (NULL != (csi = (csi_desc_t*)token)); if (csi->state != COPROC_STATE_RUNNING) { CSI_LOG(CRITICAL, (csi_coproc_t)csi->coproc, "error: inbox_not_empty_task() coproc not running\n"); return 1; } // we should only be trying to read message when the AKF is running otherwise data abort can happen. while (COPROC_STATE_RUNNING == csi->state) { // check the coproc status every 100ms while ((status = akf_recv_mbox(KFW(csi->coproc), &msg, 100000)) == 0) { // queue the message, the endpoint will ignore it if disabled. ep = find_endpoint(csi->coproc, ipc_msg_get_endpoint(&msg)); if (ep) { message_dispatcher(ep, (msg&IPC_MSG_PAYLOAD_MASK)); } else { CSI_LOG(CRITICAL, (csi_coproc_t)csi->coproc, "error: message received for unknown endpoint => message discarded (%d, 0x%llx)\n", ipc_msg_get_endpoint(&msg), msg); } } } // coproc has stopped running, terminate task return 0; } static void message_dispatcher(endpoint_desc_t *ep, msg_payload_t msg) { // do some basic filtering before signaling if (!ep->registered) { if (check_log_gov(&csi_descs[ep->coproc]->gov_ep_not_reg, ep->coproc)) { CSI_LOG(CRITICAL, ep->coproc, "error: %s endpoint not registered => message discarded\n", ep->name); } } else { // add the message to the queue ep->rcv_queue[ep->next_in] = msg; EP_QUEUE_ADVANCE(ep->next_in, ep->rcv_queue_size); // check for overflow. discard oldest message if it happens. if (ep->next_in == ep->next_out) { if (check_log_gov(&csi_descs[ep->coproc]->gov_ep_queue_ovflw, ep->coproc)) { CSI_LOG(CRITICAL, ep->coproc, "error: %s endpoint queue overflow\n", ep->name); } EP_QUEUE_ADVANCE(ep->next_out, ep->rcv_queue_size); } // if this was the first message move the _nextOut pointer to the newly received message if ((uint32_t)~(0) == ep->next_out) { ep->next_out = 0; } // signal the endpoint that its rcv queue is not empty REQUIRE(ep->rcv_not_empty_event!=NULL); event_signal(ep->rcv_not_empty_event); } } // endpoint support // NOTE: an unregister method could be added if necessary csi_status_t csi_register_endpoint(csi_coproc_t which_coproc, uint32_t which_ep, struct task_event *event, void **return_token, const char **name) { endpoint_desc_t *ep; ep = find_endpoint(which_coproc, (ipc_endpoint_t)which_ep); if (ep==NULL) { CSI_LOG(CRITICAL, which_coproc, "csi_register_endpoint(): no knowledge of endpoint #%d\n", which_ep); return CSI_STATUS_UNSUPPORTED; } if (ep->registered) { CSI_LOG(CRITICAL, which_coproc, "csi_register_endpoint(): %s endpoint already registered\n", ep->name); return CSI_STATUS_ERROR; } if (name != NULL) { *name = ep->name; } ep->rcv_not_empty_event = event; ep->registered = true; ep->sm_alignment = csi_descs[which_coproc]->fw_desc.alignment_req; *return_token = (void*)ep; return CSI_STATUS_OK; } void * csi_allocate_shared_memory(void *token, uint32_t capacity) { endpoint_desc_t *ep = (endpoint_desc_t*)token; void *sm; sm = allocate_shared_memory(capacity, ep->sm_alignment); return sm; } void csi_free_shared_memory(void *sm) { free_shared_memory (sm); // remove element from the list } csi_status_t csi_receive_message(void *token, msg_payload_t *msg_payload) { endpoint_desc_t *ep = (endpoint_desc_t*)token; REQUIRE(ep != NULL); if (!ep->registered) { return CSI_STATUS_ERROR; } // make sure we have a message available if (((uint32_t)~(0) == ep->next_out) || (ep->next_out == ep->next_in)) { return CSI_STATUS_UNAVAILABLE; } // get the message from ep input queue and return it. *msg_payload = ep->rcv_queue[ep->next_out]; EP_QUEUE_ADVANCE(ep->next_out, ep->rcv_queue_size); return CSI_STATUS_OK; } csi_status_t csi_send_message (void *token, msg_payload_t msg_payload) { endpoint_desc_t *ep; ipc_msg_t msg_out; int status; csi_desc_t *csi; REQUIRE(token != NULL); ep = (endpoint_desc_t*)token; msg_out = (ipc_msg_t)msg_payload; if (!ep->registered) { return CSI_STATUS_ERROR; } // FixMe - some clients already put in the endpoint field - others expect it to be done for them if ((msg_out & IPC_MSG_ENDPOINT_MASK) == 0) { ipc_msg_set_endpoint(&msg_out, ep->index); } if (ipc_msg_get_endpoint(&msg_out) != ep->index) { return CSI_STATUS_ERROR; } ipc_msg_set_endpoint(&msg_out, ep->index); csi = csi_descs[ep->coproc]; status = akf_send_mbox(KFW(ep->coproc), msg_out, csi->send_timeout); if (-2 == status) { // timeout CSI_LOG(CRITICAL, ep->coproc, "csi_send_message(): failed to send message %llx in %dus, AKF hanged?\n", msg_payload, csi->send_timeout); return CSI_STATUS_TIMEOUT; } // all other errors are non fatal if (0 != status) { return CSI_STATUS_ERROR; } return CSI_STATUS_OK; } static bool check_log_gov(log_governor_t *gov, csi_coproc_t which_coproc) { gov->occ_count++; if ((gov->occ_count == 1) || (timer_ticks_to_usecs(timer_get_ticks() - gov->last_log) >= LOG_GOV_PERIOD_USEC)) { if (gov->occ_count > 2) { CSI_LOG(CRITICAL, which_coproc, "WARNING: The condition logged below has occurred %d times\n", (gov->occ_count-1)); } gov->occ_count = 1; gov->last_log = timer_get_ticks(); return true; } return false; } void maybe_do_cache_operation(int operation, void *address, u_int32_t length) { #if WITH_NON_COHERENT_DMA void *aligned_address; u_int32_t aligned_length; aligned_address = (void*)((uintptr_t)address & ~(CPU_CACHELINE_SIZE-1)); aligned_length = roundup((length + (address - aligned_address)), CPU_CACHELINE_SIZE); platform_cache_operation(operation, aligned_address, aligned_length); #endif }