/**************************************************************************** * * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ #include #include #include #include #include #include #include #include "mxman.h" #include "scsc_mx_impl.h" #include "mifintrbit.h" #include "miframman.h" #include "mifmboxman.h" #include "srvman.h" #include "servman_messages.h" #include "mxmgmt_transport.h" static ulong sm_completion_timeout_ms = 1000; module_param(sm_completion_timeout_ms, ulong, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(sm_completion_timeout_ms, "Timeout Service Manager start/stop (ms) - default 1000. 0 = infinite"); #define SCSC_MX_SERVICE_RECOVERY_TIMEOUT 20000 /* 20 seconds */ #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0)) #define reinit_completion(completion) INIT_COMPLETION(*(completion)) #endif struct scsc_service { struct list_head list; struct scsc_mx *mx; enum scsc_service_id id; struct scsc_service_client *client; struct completion sm_msg_start_completion; struct completion sm_msg_stop_completion; }; void srvman_init(struct srvman *srvman, struct scsc_mx *mx) { SCSC_TAG_INFO(MXMAN, "\n"); srvman->mx = mx; INIT_LIST_HEAD(&srvman->service_list); mutex_init(&srvman->service_list_mutex); mutex_init(&srvman->api_access_mutex); wake_lock_init(&srvman->sm_wake_lock, WAKE_LOCK_SUSPEND, "srvman_wakelock"); } void srvman_deinit(struct srvman *srvman) { struct scsc_service *service, *next; SCSC_TAG_INFO(MXMAN, "\n"); list_for_each_entry_safe(service, next, &srvman->service_list, list) { list_del(&service->list); kfree(service); } mutex_destroy(&srvman->api_access_mutex); mutex_destroy(&srvman->service_list_mutex); wake_lock_destroy(&srvman->sm_wake_lock); } void srvman_set_error(struct srvman *srvman) { struct scsc_service *service; SCSC_TAG_INFO(MXMAN, "\n"); srvman->error = true; mutex_lock(&srvman->service_list_mutex); list_for_each_entry(service, &srvman->service_list, list) { complete(&service->sm_msg_start_completion); complete(&service->sm_msg_stop_completion); } mutex_unlock(&srvman->service_list_mutex); } void srvman_clear_error(struct srvman *srvman) { SCSC_TAG_INFO(MXMAN, "\n"); srvman->error = false; } #ifndef MAXWELL_SKIP_MANAGER static int wait_for_sm_msg_start_cfm(struct scsc_service *service) { int r; if (0 == sm_completion_timeout_ms) { /* Zero implies infinite wait, for development use only. * r = -ERESTARTSYS if interrupted (e.g. Ctrl-C), 0 if completed */ r = wait_for_completion_interruptible(&service->sm_msg_start_completion); if (r == -ERESTARTSYS) { /* Paranoid sink of any pending event skipped by the interrupted wait */ r = wait_for_completion_timeout(&service->sm_msg_start_completion, HZ / 2); if (r == 0) { SCSC_TAG_ERR(MXMAN, "timed out\n"); return -ETIMEDOUT; } } return r; } r = wait_for_completion_timeout(&service->sm_msg_start_completion, msecs_to_jiffies(sm_completion_timeout_ms)); if (r == 0) { SCSC_TAG_ERR(MXMAN, "timeout\n"); return -ETIMEDOUT; } return 0; } #endif static int wait_for_sm_msg_stop_cfm(struct scsc_service *service) { int r; if (0 == sm_completion_timeout_ms) { /* Zero implies infinite wait, for development use only. * r = -ERESTARTSYS if interrupted (e.g. Ctrl-C), 0 if completed */ r = wait_for_completion_interruptible(&service->sm_msg_stop_completion); if (r == -ERESTARTSYS) { /* Paranoid sink of any pending event skipped by the interrupted wait */ r = wait_for_completion_timeout(&service->sm_msg_stop_completion, HZ / 2); if (r == 0) { SCSC_TAG_ERR(MXMAN, "timed out\n"); return -ETIMEDOUT; } } return r; } r = wait_for_completion_timeout(&service->sm_msg_stop_completion, msecs_to_jiffies(sm_completion_timeout_ms)); if (r == 0) { SCSC_TAG_ERR(MXMAN, "timeout\n"); return -ETIMEDOUT; } return 0; } #ifndef MAXWELL_SKIP_MANAGER static int send_sm_msg_start_blocking(struct scsc_service *service, scsc_mifram_ref ref) { struct scsc_mx *mx = service->mx; struct mxmgmt_transport *mxmgmt_transport = scsc_mx_get_mxmgmt_transport(mx); int r; struct sm_msg_packet message = { .service_id = service->id, .msg = SM_MSG_START_REQ, .optional_data = ref }; reinit_completion(&service->sm_msg_start_completion); /* Send to FW in MM stream */ mxmgmt_transport_send(mxmgmt_transport, MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, &message, sizeof(message)); r = wait_for_sm_msg_start_cfm(service); if (r) SCSC_TAG_ERR(MXMAN, "wait_for_sm_msg_start_cfm() failed: r=%d\n", r); return r; } #endif static int send_sm_msg_stop_blocking(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct mxman *mxman = scsc_mx_get_mxman(mx); struct mxmgmt_transport *mxmgmt_transport = scsc_mx_get_mxmgmt_transport(mx); int r; struct sm_msg_packet message = { .service_id = service->id, .msg = SM_MSG_STOP_REQ, .optional_data = 0 }; if (mxman->mxman_state == MXMAN_STATE_FAILED) return 0; reinit_completion(&service->sm_msg_stop_completion); /* Send to FW in MM stream */ mxmgmt_transport_send(mxmgmt_transport, MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, &message, sizeof(message)); r = wait_for_sm_msg_stop_cfm(service); if (r) SCSC_TAG_ERR(MXMAN, "wait_for_sm_msg_stop_cfm() for service=%p service->id=%d failed: r=%d\n", service, service->id, r); return r; } #ifndef MAXWELL_SKIP_MANAGER /* * Receive handler for messages from the FW along the maxwell management transport */ static void srv_message_handler(const void *message, void *data) { struct srvman *srvman = (struct srvman *)data; struct scsc_service *service; const struct sm_msg_packet *msg = message; bool found = false; mutex_lock(&srvman->service_list_mutex); list_for_each_entry(service, &srvman->service_list, list) { if (service->id == msg->service_id) { found = true; break; } } mutex_unlock(&srvman->service_list_mutex); if (!found) { SCSC_TAG_ERR(MXMAN, "No service for msg->service_id=%d", msg->service_id); return; } /* Forward the message to the applicable service to deal with */ switch (msg->msg) { case SM_MSG_START_CFM: SCSC_TAG_INFO(MXMAN, "Received SM_MSG_START_CFM message service=%p with service_id=%d from the firmware\n", service, msg->service_id); complete(&service->sm_msg_start_completion); break; case SM_MSG_STOP_CFM: SCSC_TAG_INFO(MXMAN, "Received SM_MSG_STOP_CFM message for service=%p with service_id=%d from the firmware\n", service, msg->service_id); complete(&service->sm_msg_stop_completion); break; default: /* HERE: Unknown message, raise fault */ SCSC_TAG_WARNING(MXMAN, "Received unknown message for service=%p with service_id=%d from the firmware: msg->msg=%d\n", service, msg->msg, msg->service_id); break; } } #endif int scsc_mx_service_start(struct scsc_service *service, scsc_mifram_ref ref) { struct scsc_mx *mx = service->mx; struct srvman *srvman = scsc_mx_get_srvman(mx); #ifndef MAXWELL_SKIP_MANAGER int r; #endif SCSC_TAG_INFO(MXMAN, "\n"); #ifdef CONFIG_SCSC_CHV_SUPPORT if (chv_run) return 0; #endif mutex_lock(&srvman->api_access_mutex); wake_lock(&srvman->sm_wake_lock); if (srvman->error) { SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure\n"); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return -EILSEQ; } #ifndef MAXWELL_SKIP_MANAGER r = send_sm_msg_start_blocking(service, ref); if (r) { SCSC_TAG_ERR(MXMAN, "send_sm_msg_start_blocking() failed: r=%d\n", r); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return r; } #endif wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return 0; } EXPORT_SYMBOL(scsc_mx_service_start); int scsc_mx_service_stop(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct srvman *srvman = scsc_mx_get_srvman(mx); int r; SCSC_TAG_INFO(MXMAN, "\n"); #ifndef MAXWELL_SKIP_MANAGER #ifdef CONFIG_SCSC_CHV_SUPPORT if (chv_run) return 0; #endif mutex_lock(&srvman->api_access_mutex); wake_lock(&srvman->sm_wake_lock); if (srvman->error) { SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure\n"); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return -EILSEQ; } r = send_sm_msg_stop_blocking(service); if (r) { SCSC_TAG_ERR(MXMAN, "send_sm_msg_stop_blocking() failed: r=%d\n", r); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return -EIO; } wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); #endif /* MAXWELL_SKIP_MANAGER */ return 0; } EXPORT_SYMBOL(scsc_mx_service_stop); void srvman_freeze_services(struct srvman *srvman) { struct scsc_service *service; struct mxman *mxman = scsc_mx_get_mxman(srvman->mx); SCSC_TAG_INFO(MXMAN, "\n"); mutex_lock(&srvman->service_list_mutex); list_for_each_entry(service, &srvman->service_list, list) { service->client->stop_on_failure(service->client); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 13, 0) reinit_completion(&mxman->recovery_completion); #else INIT_COMPLETION(mxman->recovery_completion); #endif mutex_unlock(&srvman->service_list_mutex); SCSC_TAG_INFO(MXMAN, "OK\n"); } void srvman_unfreeze_services(struct srvman *srvman, u16 scsc_panic_code) { struct scsc_service *service; SCSC_TAG_INFO(MXMAN, "\n"); mutex_lock(&srvman->service_list_mutex); list_for_each_entry(service, &srvman->service_list, list) { service->client->failure_reset(service->client, scsc_panic_code); } mutex_unlock(&srvman->service_list_mutex); SCSC_TAG_INFO(MXMAN, "OK\n"); } /** Signal a failure detected by the Client. This will trigger the systemwide * failure handling procedure: _All_ Clients will be called back via * their stop_on_failure() handler as a side-effect. */ void scsc_mx_service_service_failed(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct srvman *srvman = scsc_mx_get_srvman(mx); SCSC_TAG_INFO(MXMAN, "\n"); srvman_set_error(srvman); mxman_fail(scsc_mx_get_mxman(mx), SCSC_PANIC_CODE_HOST << 15); } EXPORT_SYMBOL(scsc_mx_service_service_failed); void scsc_mx_service_close(struct scsc_service *service) { struct mxman *mxman = scsc_mx_get_mxman(service->mx); struct scsc_mx *mx = service->mx; struct srvman *srvman = scsc_mx_get_srvman(mx); #ifndef MAXWELL_SKIP_MANAGER bool empty; #endif SCSC_TAG_INFO(MXMAN, "\n"); mutex_lock(&srvman->api_access_mutex); wake_lock(&srvman->sm_wake_lock); if (srvman->error) { SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure\n"); mutex_unlock(&srvman->api_access_mutex); wake_unlock(&srvman->sm_wake_lock); return; } /* remove the service from the list and deallocate the service memory */ mutex_lock(&srvman->service_list_mutex); list_del(&service->list); #ifndef MAXWELL_SKIP_MANAGER empty = list_empty(&srvman->service_list); #endif mutex_unlock(&srvman->service_list_mutex); #ifndef MAXWELL_SKIP_MANAGER if (empty) /* unregister channel handler */ mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, NULL, NULL); #endif kfree(service); mxman_close(mxman); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); } EXPORT_SYMBOL(scsc_mx_service_close); /* Consider move to a public scsc_mx interface */ struct scsc_service *scsc_mx_service_open(struct scsc_mx *mx, enum scsc_service_id id, struct scsc_service_client *client, int *status) { int ret; struct scsc_service *service; struct srvman *srvman = scsc_mx_get_srvman(mx); struct mxman *mxman = scsc_mx_get_mxman(mx); #ifndef MAXWELL_SKIP_MANAGER bool empty; #endif SCSC_TAG_INFO(MXMAN, "\n"); mutex_lock(&srvman->api_access_mutex); wake_lock(&srvman->sm_wake_lock); if (srvman->error) { SCSC_TAG_ERR(MXMAN, "error: refused due to previous f/w failure\n"); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); *status = -EILSEQ; return NULL; } if (mxman->mxman_state == MXMAN_STATE_FAILED) { int r; mutex_unlock(&srvman->api_access_mutex); r = wait_for_completion_timeout(&mxman->recovery_completion, msecs_to_jiffies(SCSC_MX_SERVICE_RECOVERY_TIMEOUT)); if (r == 0) { SCSC_TAG_ERR(MXMAN, "Recovery timeout\n"); wake_unlock(&srvman->sm_wake_lock); *status = -EIO; return NULL; } mutex_lock(&srvman->api_access_mutex); } service = kmalloc(sizeof(struct scsc_service), GFP_KERNEL); if (service) { /* MaxwellManager Should allocate Mem and download FW */ ret = mxman_open(mxman); if (ret) { kfree(service); wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); *status = ret; return NULL; } /* Initialise service struct here */ service->mx = mx; service->id = id; service->client = client; init_completion(&service->sm_msg_start_completion); init_completion(&service->sm_msg_stop_completion); mutex_lock(&srvman->service_list_mutex); #ifndef MAXWELL_SKIP_MANAGER empty = list_empty(&srvman->service_list); #endif mutex_unlock(&srvman->service_list_mutex); #ifndef MAXWELL_SKIP_MANAGER if (empty) mxmgmt_transport_register_channel_handler(scsc_mx_get_mxmgmt_transport(mx), MMTRANS_CHAN_ID_SERVICE_MANAGEMENT, &srv_message_handler, srvman); #endif mutex_lock(&srvman->service_list_mutex); list_add_tail(&service->list, &srvman->service_list); mutex_unlock(&srvman->service_list_mutex); } else *status = -ENOMEM; wake_unlock(&srvman->sm_wake_lock); mutex_unlock(&srvman->api_access_mutex); return service; } EXPORT_SYMBOL(scsc_mx_service_open); /** Allocate a contiguous block of SDRAM accessible to Client Driver */ int scsc_mx_service_mifram_alloc(struct scsc_service *service, size_t nbytes, scsc_mifram_ref *ref, u32 align) { struct scsc_mx *mx = service->mx; void *mem; int ret; mem = miframman_alloc(scsc_mx_get_ramman(mx), nbytes, align); if (!mem) { SCSC_TAG_ERR(MXMAN, "miframman_alloc() failed\n"); return -ENOMEM; } SCSC_TAG_DEBUG(MXMAN, "Allocated mem %p\n", mem); /* Transform native pointer and get mifram_ref type */ ret = scsc_mx_service_mif_ptr_to_addr(service, mem, ref); if (ret) { SCSC_TAG_ERR(MXMAN, "scsc_mx_service_mif_ptr_to_addr() failed: ret=%d", ret); miframman_free(scsc_mx_get_ramman(mx), mem); } else SCSC_TAG_DEBUG(MXMAN, "mem %p ref %d\n", mem, *ref); return ret; } EXPORT_SYMBOL(scsc_mx_service_mifram_alloc); /** Free a contiguous block of SDRAM */ void scsc_mx_service_mifram_free(struct scsc_service *service, scsc_mifram_ref ref) { struct scsc_mx *mx = service->mx; void *mem; mem = scsc_mx_service_mif_addr_to_ptr(service, ref); SCSC_TAG_DEBUG(MXMAN, "**** Freeing %p\n", mem); miframman_free(scsc_mx_get_ramman(mx), mem); } EXPORT_SYMBOL(scsc_mx_service_mifram_free); /* MIF ALLOCATIONS */ bool scsc_mx_service_alloc_mboxes(struct scsc_service *service, int n, int *first_mbox_index) { struct scsc_mx *mx = service->mx; return mifmboxman_alloc_mboxes(scsc_mx_get_mboxman(mx), n, first_mbox_index); } EXPORT_SYMBOL(scsc_mx_service_alloc_mboxes); void scsc_service_free_mboxes(struct scsc_service *service, int n, int first_mbox_index) { struct scsc_mx *mx = service->mx; return mifmboxman_free_mboxes(scsc_mx_get_mboxman(mx), first_mbox_index, n); } EXPORT_SYMBOL(scsc_service_free_mboxes); u32 *scsc_mx_service_get_mbox_ptr(struct scsc_service *service, int mbox_index) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mifmboxman_get_mbox_ptr(scsc_mx_get_mboxman(mx), mif_abs, mbox_index); } EXPORT_SYMBOL(scsc_mx_service_get_mbox_ptr); int scsc_service_mifintrbit_bit_mask_status_get(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_bit_mask_status_get(mif_abs); } EXPORT_SYMBOL(scsc_service_mifintrbit_bit_mask_status_get); int scsc_service_mifintrbit_get(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_get(mif_abs); } EXPORT_SYMBOL(scsc_service_mifintrbit_get); void scsc_service_mifintrbit_bit_set(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_bit_set(mif_abs, which_bit, dir); } EXPORT_SYMBOL(scsc_service_mifintrbit_bit_set); void scsc_service_mifintrbit_bit_clear(struct scsc_service *service, int which_bit) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_bit_clear(mif_abs, which_bit); } EXPORT_SYMBOL(scsc_service_mifintrbit_bit_clear); void scsc_service_mifintrbit_bit_mask(struct scsc_service *service, int which_bit) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_bit_mask(mif_abs, which_bit); } EXPORT_SYMBOL(scsc_service_mifintrbit_bit_mask); void scsc_service_mifintrbit_bit_unmask(struct scsc_service *service, int which_bit) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->irq_bit_unmask(mif_abs, which_bit); } EXPORT_SYMBOL(scsc_service_mifintrbit_bit_unmask); int scsc_service_mifintrbit_alloc_fromhost(struct scsc_service *service, enum scsc_mifintr_target dir) { struct scsc_mx *mx = service->mx; return mifintrbit_alloc_fromhost(scsc_mx_get_intrbit(mx), dir); } EXPORT_SYMBOL(scsc_service_mifintrbit_alloc_fromhost); int scsc_service_mifintrbit_free_fromhost(struct scsc_service *service, int which_bit, enum scsc_mifintr_target dir) { struct scsc_mx *mx = service->mx; return mifintrbit_free_fromhost(scsc_mx_get_intrbit(mx), which_bit, dir); } EXPORT_SYMBOL(scsc_service_mifintrbit_free_fromhost); int scsc_service_mifintrbit_register_tohost(struct scsc_service *service, void (*handler)(int irq, void *data), void *data) { struct scsc_mx *mx = service->mx; SCSC_TAG_DEBUG(MXMAN, "Registering %pS\n", handler); return mifintrbit_alloc_tohost(scsc_mx_get_intrbit(mx), handler, data); } EXPORT_SYMBOL(scsc_service_mifintrbit_register_tohost); int scsc_service_mifintrbit_unregister_tohost(struct scsc_service *service, int which_bit) { struct scsc_mx *mx = service->mx; SCSC_TAG_DEBUG(MXMAN, "Deregistering int for bit %d\n", which_bit); return mifintrbit_free_tohost(scsc_mx_get_intrbit(mx), which_bit); } EXPORT_SYMBOL(scsc_service_mifintrbit_unregister_tohost); void *scsc_mx_service_mif_addr_to_ptr(struct scsc_service *service, scsc_mifram_ref ref) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); return mif_abs->get_mifram_ptr(mif_abs, ref); } EXPORT_SYMBOL(scsc_mx_service_mif_addr_to_ptr); int scsc_mx_service_mif_ptr_to_addr(struct scsc_service *service, void *mem_ptr, scsc_mifram_ref *ref) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); /* Transform native pointer and get mifram_ref type */ if (mif_abs->get_mifram_ref(mif_abs, mem_ptr, ref)) { SCSC_TAG_ERR(MXMAN, "ooops somethig went wrong"); return -ENOMEM; } return 0; } EXPORT_SYMBOL(scsc_mx_service_mif_ptr_to_addr); int scsc_mx_service_mif_dump_registers(struct scsc_service *service) { struct scsc_mx *mx = service->mx; struct scsc_mif_abs *mif_abs; mif_abs = scsc_mx_get_mif_abs(mx); /* Dump registers */ mif_abs->mif_dump_registers(mif_abs); return 0; } EXPORT_SYMBOL(scsc_mx_service_mif_dump_registers); struct device *scsc_service_get_device(struct scsc_service *service) { return scsc_mx_get_device(service->mx); } EXPORT_SYMBOL(scsc_service_get_device); int scsc_service_force_panic(struct scsc_service *service) { struct mxman *mxman = scsc_mx_get_mxman(service->mx); return mxman_force_panic(mxman); } EXPORT_SYMBOL(scsc_service_force_panic);