/**************************************************************************** * * Copyright (c) 2014 - 2017 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ /** Uses */ #include #include #include #include #include #include "scsc_mif_abs.h" #include "mifintrbit.h" /** Implements */ #include "mxlog_transport.h" #define MXLOG_TRANSPORT_BUF_LENGTH (16 * 1024) #define MXLOG_TRANSPORT_PACKET_SIZE (4) #define MXLOG_TRANSPORT_MAX_BAD_SYNC_HEADER_CNT (250) /* Flag that an error has occurred so the I/O thread processing should stop */ void mxlog_transport_set_error(struct mxlog_transport *mxlog_transport) { SCSC_TAG_WARNING(MXLOG_TRANS, "I/O thread processing is suspended\n"); mxlog_transport->mxlog_thread.block_thread = 1; } static void input_irq_handler(int irq, void *data) { struct mxlog_transport *mxlog_transport = (struct mxlog_transport *)data; struct mxlog_thread *th = &mxlog_transport->mxlog_thread; struct scsc_mif_abs *mif_abs; SCSC_TAG_DEBUG(MXLOG_TRANS, "mxlog intr\n"); /* Clear the interrupt first to ensure we can't possibly miss one */ mif_abs = scsc_mx_get_mif_abs(mxlog_transport->mx); #if IS_ENABLED(CONFIG_SCSC_INDEPENDENT_SUBSYSTEM) mif_abs->irq_bit_clear(mif_abs, irq, mxlog_transport->target); #else mif_abs->irq_bit_clear(mif_abs, irq); #endif /* The the other side wrote some data to the input stream, * wake up the thread that deals with this. */ if (th->task == NULL) { SCSC_TAG_ERR(MXLOG_TRANS, "mxlog_thread is NOT running\n"); return; } /* * If an error has occured, we discard silently all messages from * the stream until the error has been processed and the system has * been reinitialised. */ if (th->block_thread == 1) { SCSC_TAG_ERR(MXLOG_TRANS, "discard message.\n"); /* * Do not try to acknowledge a pending interrupt here. * This function is called by a function which in turn can be * running in an atomic or 'disabled irq' level. */ return; } th->wakeup_flag = 1; /* wake up I/O thread */ wake_up_interruptible(&th->wakeup_q); } #if IS_ENABLED(CONFIG_SCSC_INDEPENDENT_SUBSYSTEM) static void mxlog_input_irq_handler_wpan(int irq, void *data) { struct mxlog_transport *mxlog_transport = (struct mxlog_transport *)data; struct mxlog_thread *th = &mxlog_transport->mxlog_thread; struct scsc_mif_abs *mif_abs; SCSC_TAG_DEBUG(MXLOG_TRANS, "mxlog intr\n"); /* Clear the interrupt first to ensure we can't possibly miss one */ mif_abs = scsc_mx_get_mif_abs(mxlog_transport->mx); mif_abs->irq_bit_clear(mif_abs, irq, SCSC_MIF_ABS_TARGET_WPAN); /* The the other side wrote some data to the input stream, * wake up the thread that deals with this. */ if (th->task == NULL) { SCSC_TAG_ERR(MXLOG_TRANS, "mxlog_thread is NOT running\n"); return; } /* * If an error has occured, we discard silently all messages from * the stream until the error has been processed and the system has * been reinitialised. */ if (th->block_thread == 1) { SCSC_TAG_ERR(MXLOG_TRANS, "discard message.\n"); /* * Do not try to acknowledge a pending interrupt here. * This function is called by a function which in turn can be * running in an atomic or 'disabled irq' level. */ return; } th->wakeup_flag = 1; /* wake up I/O thread */ wake_up_interruptible(&th->wakeup_q); } #endif static void thread_wait_until_stopped(struct mxlog_transport *mxlog_transport) { struct mxlog_thread *th = &mxlog_transport->mxlog_thread; /* * kthread_stop() cannot handle the th exiting while * kthread_should_stop() is false, so sleep until kthread_stop() * wakes us up. */ SCSC_TAG_INFO(MXLOG_TRANS, "%s waiting for the stop signal.\n", th->name); set_current_state(TASK_INTERRUPTIBLE); if (!kthread_should_stop()) { SCSC_TAG_DEBUG(MXLOG_TRANS, "%s schedule....\n", th->name); schedule(); } /** * Caller that spawned the kthread did a get_task_struct() * on task_struct, that will be released on stop... * ...so we should NOT nullify th->task here. */ SCSC_TAG_DEBUG(MXLOG_TRANS, "%s exiting.\n", th->name); } /** * A thread that forwards messages sent across the transport to * the registered handlers for each channel. */ static int mxlog_thread_function(void *arg) { struct mxlog_transport *mxlog_transport = (struct mxlog_transport *)arg; struct mxlog_thread *th = &mxlog_transport->mxlog_thread; int ret; u32 header; char *buf = NULL; size_t buf_sz = 4096; /* * Do not die on first invalid packet header check atleast for MXLOG_TRANSPORT_MAX_BAD_SYNC_HEADER_CNT * consecutive invalid packet head */ u32 invld_hdr_cnt = 0; buf = kmalloc(buf_sz, GFP_KERNEL); if (!buf) { SCSC_TAG_ERR(MXLOG_TRANS, "Failed to alloc %s local buffer...exiting.\n", th->name); return -ENOMEM; } th->block_thread = 0; /* completion is used only for startup thread-synchronization */ complete(&th->completion); /* Thread ready...start ISR processing*/ while (!kthread_should_stop()) { /* wait until an error occurs, or we need to process */ ret = wait_event_interruptible(th->wakeup_q, (th->wakeup_flag && !th->block_thread) || kthread_should_stop()); if (kthread_should_stop()) { SCSC_TAG_DEBUG(MXLOG_TRANS, "signalled to exit\n"); break; } if (ret < 0) { SCSC_TAG_DEBUG(MXLOG_TRANS, "wait_event returned %d, thread will exit\n", ret); thread_wait_until_stopped(mxlog_transport); break; } th->wakeup_flag = 0; SCSC_TAG_DEBUG(MXLOG_TRANS, "wokeup: r=%d\n", ret); if (!mxlog_transport->header_handler_fn) { /* Invalid header handler: * unrecoverable log and terminate */ SCSC_TAG_WARNING(MXLOG_TRANS, "mxlog_transport->header_handler_fn_==NULL\n"); break; } while (mif_stream_read(&mxlog_transport->mif_stream, &header, sizeof(uint32_t))) { u8 level = 0; u8 phase = 0; u32 num_bytes = 0; mutex_lock(&mxlog_transport->lock); if (!mxlog_transport->header_handler_fn) { /* Invalid header handler: * unrecoverable log and terminate */ SCSC_TAG_WARNING(MXLOG_TRANS, "mxlog_transport->header_handler_fn_==NULL. Channel has been released\n"); mutex_unlock(&mxlog_transport->lock); /* not recoverable, terminate straight away */ goto mxlog_thread_exit; } /** * A generic header processor will properly retrieve * level and num_bytes as specifically implemented * by the phase. */ if (mxlog_transport->header_handler_fn(header, &phase, &level, &num_bytes)) { SCSC_TAG_ERR(MXLOG_TRANS, "Bad sync in header: header=0x%08x\n", header); if (++invld_hdr_cnt > MXLOG_TRANSPORT_MAX_BAD_SYNC_HEADER_CNT) { SCSC_TAG_ERR(MXLOG_TRANS, "Received %d continuous bad sync headers. Terminating ...\n", MXLOG_TRANSPORT_MAX_BAD_SYNC_HEADER_CNT); mutex_unlock(&mxlog_transport->lock); /* not recoverable, terminate straight away */ goto mxlog_thread_exit; } mutex_unlock(&mxlog_transport->lock); continue; } if (num_bytes > 0 && num_bytes < (MXLOG_TRANSPORT_BUF_LENGTH - sizeof(uint32_t))) { u32 ret_bytes = 0; /* We have received valid packets reset invalid header count */ invld_hdr_cnt = 0; /* 2nd read - payload (msg) */ ret_bytes = mif_stream_read(&mxlog_transport->mif_stream, buf, num_bytes); mxlog_transport->channel_handler_fn(phase, buf, ret_bytes, level, mxlog_transport->channel_handler_data); } else { SCSC_TAG_ERR(MXLOG_TRANS, "Bad num_bytes(%d) in header: header=0x%08x\n", num_bytes, header); } mutex_unlock(&mxlog_transport->lock); } } mxlog_thread_exit: SCSC_TAG_INFO(MXLOG_TRANS, "exiting....\n"); kfree(buf); return 0; } static int mxlog_thread_start(struct mxlog_transport *mxlog_transport) { int err; struct mxlog_thread *th = &mxlog_transport->mxlog_thread; if (th->task != NULL) { SCSC_TAG_WARNING(MXLOG_TRANS, "%s thread already started\n", th->name); return 0; } /* Initialise thread structure */ th->block_thread = 1; init_waitqueue_head(&th->wakeup_q); init_completion(&th->completion); th->wakeup_flag = 0; snprintf(th->name, MXLOG_THREAD_NAME_MAX_LENGTH, "mxlog_thread"); /* Start the kernel thread */ th->task = kthread_run(mxlog_thread_function, mxlog_transport, "%s", th->name); if (IS_ERR(th->task)) { int err = (int)PTR_ERR(th->task); th->task = NULL; return err; } /** * Avoid this task_struct vanishes immediately * if the kthread exits by its own. */ get_task_struct(th->task); /* wait until thread function is running */ #define LOG_THREAD_START_TMO_SEC (3) err = wait_for_completion_timeout(&th->completion, msecs_to_jiffies(LOG_THREAD_START_TMO_SEC * 1000)); if (err == 0) { SCSC_TAG_ERR(MXLOG_TRANS, "timeout starting %s\n", th->name); kthread_stop(th->task); put_task_struct(th->task); return -ETIMEDOUT; } SCSC_TAG_INFO(MXLOG_TRANS, "Started thread %s\n", th->name); return 0; } static void mxlog_thread_stop(struct mxlog_transport *mxlog_transport) { struct mxlog_thread *th = &mxlog_transport->mxlog_thread; if (!th->task) { SCSC_TAG_WARNING(MXLOG_TRANS, "%s is already stopped\n", th->name); return; } SCSC_TAG_INFO(MXLOG_TRANS, "Stopping thread %s [%d]\n", th->name, th->task->pid); /* kthread_stop() marks thread as KTHREAD_SHOULD_STOP * and wait for it to terminate */ if (kthread_stop(th->task)) SCSC_TAG_ERR(MXLOG_TRANS, "Failed to stop %s [%d]\n", th->name, th->task->pid); /* Finally release the task_struct we held on start */ put_task_struct(th->task); th->task = NULL; } void mxlog_transport_release(struct mxlog_transport *mxlog_transport) { mxlog_thread_stop(mxlog_transport); mif_stream_release(&mxlog_transport->mif_stream); } void mxlog_transport_config_serialise(struct mxlog_transport *mxlog_transport, struct mxlogconf *mxlogconf) { mif_stream_config_serialise(&mxlog_transport->mif_stream, &mxlogconf->stream_conf); } /** Public functions */ int mxlog_transport_init(struct mxlog_transport *mxlog_transport, struct scsc_mx *mx) { int r; uint32_t mem_length = MXLOG_TRANSPORT_BUF_LENGTH; uint32_t packet_size = MXLOG_TRANSPORT_PACKET_SIZE; uint32_t num_packets; /* * Initialising a buffer of 1 byte is never legitimate, do not allow it. * The memory buffer length must be a multiple of the packet size. */ memset(mxlog_transport, 0, sizeof(struct mxlog_transport)); mutex_init(&mxlog_transport->lock); num_packets = mem_length / packet_size; mxlog_transport->mx = mx; r = mif_stream_init(&mxlog_transport->mif_stream, SCSC_MIF_ABS_TARGET_WLAN, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, input_irq_handler, mxlog_transport); if (r) return r; r = mxlog_thread_start(mxlog_transport); if (r) { mif_stream_release(&mxlog_transport->mif_stream); return r; } return 0; } /** TOFACTORIZE **/ #if IS_ENABLED(CONFIG_SCSC_INDEPENDENT_SUBSYSTEM) int mxlog_transport_init_wpan(struct mxlog_transport *mxlog_transport, struct scsc_mx *mx) { int r; uint32_t mem_length = MXLOG_TRANSPORT_BUF_LENGTH; uint32_t packet_size = MXLOG_TRANSPORT_PACKET_SIZE; uint32_t num_packets; /* * Initialising a buffer of 1 byte is never legitimate, do not allow it. * The memory buffer length must be a multiple of the packet size. */ memset(mxlog_transport, 0, sizeof(struct mxlog_transport)); mutex_init(&mxlog_transport->lock); num_packets = mem_length / packet_size; mxlog_transport->mx = mx; r = mif_stream_init(&mxlog_transport->mif_stream, SCSC_MIF_ABS_TARGET_WPAN, MIF_STREAM_DIRECTION_IN, num_packets, packet_size, mx, MIF_STREAM_INTRBIT_TYPE_ALLOC, mxlog_input_irq_handler_wpan, mxlog_transport); if (r) return r; r = mxlog_thread_start(mxlog_transport); if (r) { mif_stream_release(&mxlog_transport->mif_stream); return r; } mxlog_transport->target = SCSC_MIF_ABS_TARGET_WPAN; return 0; } #endif void mxlog_transport_register_channel_handler(struct mxlog_transport *mxlog_transport, mxlog_header_handler parser, mxlog_channel_handler handler, void *data) { mutex_lock(&mxlog_transport->lock); mxlog_transport->header_handler_fn = parser; mxlog_transport->channel_handler_fn = handler; mxlog_transport->channel_handler_data = (void *)data; mutex_unlock(&mxlog_transport->lock); }