/* drivers/soc/qcom/smp2p_loopback.c * * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include "smem_private.h" #include "smp2p_private.h" /** * struct smp2p_loopback_ctx - Representation of remote loopback object. * * @proc_id: Processor id of the processor that sends the loopback commands. * @out: Handle to the smem entry structure for providing the response. * @out_nb: Notifies the opening of local entry. * @out_is_active: Outbound entry events should be processed. * @in_nb: Notifies changes in the remote entry. * @in_is_active: Inbound entry events should be processed. * @rmt_lpb_work: Work item that handles the incoming loopback commands. * @rmt_cmd: Structure that holds the current and previous value of the entry. */ struct smp2p_loopback_ctx { int proc_id; struct msm_smp2p_out *out; struct notifier_block out_nb; bool out_is_active; struct notifier_block in_nb; bool in_is_active; struct work_struct rmt_lpb_work; struct msm_smp2p_update_notif rmt_cmd; }; static struct smp2p_loopback_ctx remote_loopback[SMP2P_NUM_PROCS]; static struct msm_smp2p_remote_mock remote_mock; /** * remote_spinlock_test - Handles remote spinlock test. * * @ctx: Loopback context */ static void remote_spinlock_test(struct smp2p_loopback_ctx *ctx) { uint32_t test_request; uint32_t test_response; unsigned long flags; int n; unsigned lock_count = 0; remote_spinlock_t *smem_spinlock; test_request = 0x0; SMP2P_SET_RMT_CMD_TYPE_REQ(test_request); smem_spinlock = smem_get_remote_spinlock(); if (!smem_spinlock) { pr_err("%s: unable to get remote spinlock\n", __func__); return; } for (;;) { remote_spin_lock_irqsave(smem_spinlock, flags); ++lock_count; SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_LOCKED); (void)msm_smp2p_out_write(ctx->out, test_request); for (n = 0; n < 10000; ++n) { (void)msm_smp2p_in_read(ctx->proc_id, "smp2p", &test_response); test_response = SMP2P_GET_RMT_CMD(test_response); if (test_response == SMP2P_LB_CMD_RSPIN_END) break; if (test_response != SMP2P_LB_CMD_RSPIN_UNLOCKED) SMP2P_ERR("%s: invalid spinlock command %x\n", __func__, test_response); } if (test_response == SMP2P_LB_CMD_RSPIN_END) { SMP2P_SET_RMT_CMD_TYPE_RESP(test_request); SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_END); SMP2P_SET_RMT_DATA(test_request, lock_count); (void)msm_smp2p_out_write(ctx->out, test_request); break; } SMP2P_SET_RMT_CMD(test_request, SMP2P_LB_CMD_RSPIN_UNLOCKED); (void)msm_smp2p_out_write(ctx->out, test_request); remote_spin_unlock_irqrestore(smem_spinlock, flags); } remote_spin_unlock_irqrestore(smem_spinlock, flags); } /** * smp2p_rmt_lpb_worker - Handles incoming remote loopback commands. * * @work: Work Item scheduled to handle the incoming commands. */ static void smp2p_rmt_lpb_worker(struct work_struct *work) { struct smp2p_loopback_ctx *ctx; int lpb_cmd; int lpb_cmd_type; int lpb_data; ctx = container_of(work, struct smp2p_loopback_ctx, rmt_lpb_work); if (!ctx->in_is_active || !ctx->out_is_active) return; if (ctx->rmt_cmd.previous_value == ctx->rmt_cmd.current_value) return; lpb_cmd_type = SMP2P_GET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value); lpb_cmd = SMP2P_GET_RMT_CMD(ctx->rmt_cmd.current_value); lpb_data = SMP2P_GET_RMT_DATA(ctx->rmt_cmd.current_value); if (lpb_cmd & SMP2P_RLPB_IGNORE) return; switch (lpb_cmd) { case SMP2P_LB_CMD_NOOP: /* Do nothing */ break; case SMP2P_LB_CMD_ECHO: SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0); SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value, lpb_data); (void)msm_smp2p_out_write(ctx->out, ctx->rmt_cmd.current_value); break; case SMP2P_LB_CMD_CLEARALL: ctx->rmt_cmd.current_value = 0; (void)msm_smp2p_out_write(ctx->out, ctx->rmt_cmd.current_value); break; case SMP2P_LB_CMD_PINGPONG: SMP2P_SET_RMT_CMD_TYPE(ctx->rmt_cmd.current_value, 0); if (lpb_data) { lpb_data--; SMP2P_SET_RMT_DATA(ctx->rmt_cmd.current_value, lpb_data); (void)msm_smp2p_out_write(ctx->out, ctx->rmt_cmd.current_value); } break; case SMP2P_LB_CMD_RSPIN_START: remote_spinlock_test(ctx); break; case SMP2P_LB_CMD_RSPIN_LOCKED: case SMP2P_LB_CMD_RSPIN_UNLOCKED: case SMP2P_LB_CMD_RSPIN_END: /* not used for remote spinlock test */ break; default: SMP2P_DBG("%s: Unknown loopback command %x\n", __func__, lpb_cmd); break; } } /** * smp2p_rmt_in_edge_notify - Schedules a work item to handle the commands. * * @nb: Notifier block, this is called when the value in remote entry changes. * @event: Takes value SMP2P_ENTRY_UPDATE or SMP2P_OPEN based on the event. * @data: Consists of previous and current value in case of entry update. * @returns: 0 for success (return value required for notifier chains). */ static int smp2p_rmt_in_edge_notify(struct notifier_block *nb, unsigned long event, void *data) { struct smp2p_loopback_ctx *ctx; if (!(event == SMP2P_ENTRY_UPDATE || event == SMP2P_OPEN)) return 0; ctx = container_of(nb, struct smp2p_loopback_ctx, in_nb); if (data && ctx->in_is_active) { ctx->rmt_cmd = *(struct msm_smp2p_update_notif *)data; schedule_work(&ctx->rmt_lpb_work); } return 0; } /** * smp2p_rmt_out_edge_notify - Notifies on the opening of the outbound entry. * * @nb: Notifier block, this is called when the local entry is open. * @event: Takes on value SMP2P_OPEN when the local entry is open. * @data: Consist of current value of the remote entry, if entry is open. * @returns: 0 for success (return value required for notifier chains). */ static int smp2p_rmt_out_edge_notify(struct notifier_block *nb, unsigned long event, void *data) { struct smp2p_loopback_ctx *ctx; ctx = container_of(nb, struct smp2p_loopback_ctx, out_nb); if (event == SMP2P_OPEN) SMP2P_DBG("%s: 'smp2p':%d opened\n", __func__, ctx->proc_id); return 0; } /** * msm_smp2p_init_rmt_lpb - Initializes the remote loopback object. * * @ctx: Pointer to remote loopback object that needs to be initialized. * @pid: Processor id of the processor that is sending the commands. * @entry: Name of the entry that needs to be opened locally. * @returns: 0 on success, standard Linux error code otherwise. */ static int msm_smp2p_init_rmt_lpb(struct smp2p_loopback_ctx *ctx, int pid, const char *entry) { int ret = 0; int tmp; if (!ctx || !entry || pid > SMP2P_NUM_PROCS) return -EINVAL; ctx->in_nb.notifier_call = smp2p_rmt_in_edge_notify; ctx->out_nb.notifier_call = smp2p_rmt_out_edge_notify; ctx->proc_id = pid; ctx->in_is_active = true; ctx->out_is_active = true; tmp = msm_smp2p_out_open(pid, entry, &ctx->out_nb, &ctx->out); if (tmp) { SMP2P_ERR("%s: open failed outbound entry '%s':%d - ret %d\n", __func__, entry, pid, tmp); ret = tmp; } tmp = msm_smp2p_in_register(ctx->proc_id, SMP2P_RLPB_ENTRY_NAME, &ctx->in_nb); if (tmp) { SMP2P_ERR("%s: unable to open inbound entry '%s':%d - ret %d\n", __func__, entry, pid, tmp); ret = tmp; } return ret; } /** * msm_smp2p_init_rmt_lpb_proc - Wrapper over msm_smp2p_init_rmt_lpb * * @remote_pid: Processor ID of the processor that sends loopback command. * @returns: Pointer to outbound entry handle. */ void *msm_smp2p_init_rmt_lpb_proc(int remote_pid) { int tmp; void *ret = NULL; tmp = msm_smp2p_init_rmt_lpb(&remote_loopback[remote_pid], remote_pid, SMP2P_RLPB_ENTRY_NAME); if (!tmp) ret = remote_loopback[remote_pid].out; return ret; } EXPORT_SYMBOL(msm_smp2p_init_rmt_lpb_proc); /** * msm_smp2p_deinit_rmt_lpb_proc - Unregister support for remote processor. * * @remote_pid: Processor ID of the remote system. * @returns: 0 on success, standard Linux error code otherwise. * * Unregister loopback support for remote processor. */ int msm_smp2p_deinit_rmt_lpb_proc(int remote_pid) { int ret = 0; int tmp; struct smp2p_loopback_ctx *ctx; if (remote_pid >= SMP2P_NUM_PROCS) return -EINVAL; ctx = &remote_loopback[remote_pid]; /* abort any pending notifications */ remote_loopback[remote_pid].out_is_active = false; remote_loopback[remote_pid].in_is_active = false; flush_work(&ctx->rmt_lpb_work); /* unregister entries */ tmp = msm_smp2p_out_close(&remote_loopback[remote_pid].out); remote_loopback[remote_pid].out = NULL; if (tmp) { SMP2P_ERR("%s: outbound 'smp2p':%d close failed %d\n", __func__, remote_pid, tmp); ret = tmp; } tmp = msm_smp2p_in_unregister(remote_pid, SMP2P_RLPB_ENTRY_NAME, &remote_loopback[remote_pid].in_nb); if (tmp) { SMP2P_ERR("%s: inbound 'smp2p':%d close failed %d\n", __func__, remote_pid, tmp); ret = tmp; } return ret; } EXPORT_SYMBOL(msm_smp2p_deinit_rmt_lpb_proc); /** * msm_smp2p_set_remote_mock_exists - Sets the remote mock configuration. * * @item_exists: true = Remote mock SMEM item exists * * This is used in the testing environment to simulate the existence of the * remote smem item in order to test the negotiation algorithm. */ void msm_smp2p_set_remote_mock_exists(bool item_exists) { remote_mock.item_exists = item_exists; } EXPORT_SYMBOL(msm_smp2p_set_remote_mock_exists); /** * msm_smp2p_get_remote_mock - Get remote mock object. * * @returns: Point to the remote mock object. */ void *msm_smp2p_get_remote_mock(void) { return &remote_mock; } EXPORT_SYMBOL(msm_smp2p_get_remote_mock); /** * msm_smp2p_get_remote_mock_smem_item - Returns a pointer to remote item. * * @size: Size of item. * @returns: Pointer to mock remote smem item. */ void *msm_smp2p_get_remote_mock_smem_item(uint32_t *size) { void *ptr = NULL; if (remote_mock.item_exists) { *size = sizeof(remote_mock.remote_item); ptr = &(remote_mock.remote_item); } return ptr; } EXPORT_SYMBOL(msm_smp2p_get_remote_mock_smem_item); /** * smp2p_remote_mock_rx_interrupt - Triggers receive interrupt for mock proc. * * @returns: 0 for success * * This function simulates the receiving of interrupt by the mock remote * processor in a testing environment. */ int smp2p_remote_mock_rx_interrupt(void) { remote_mock.rx_interrupt_count++; if (remote_mock.initialized) complete(&remote_mock.cb_completion); return 0; } EXPORT_SYMBOL(smp2p_remote_mock_rx_interrupt); /** * smp2p_remote_mock_tx_interrupt - Calls the SMP2P interrupt handler. * * This function calls the interrupt handler of the Apps processor to simulate * receiving interrupts from a remote processor. */ static void smp2p_remote_mock_tx_interrupt(void) { msm_smp2p_interrupt_handler(SMP2P_REMOTE_MOCK_PROC); } /** * smp2p_remote_mock_init - Initialize the remote mock and loopback objects. * * @returns: 0 for success */ static int __init smp2p_remote_mock_init(void) { int i; struct smp2p_interrupt_config *int_cfg; smp2p_init_header(&remote_mock.remote_item.header, SMP2P_REMOTE_MOCK_PROC, SMP2P_APPS_PROC, 0, 0); remote_mock.rx_interrupt_count = 0; remote_mock.rx_interrupt = smp2p_remote_mock_rx_interrupt; remote_mock.tx_interrupt = smp2p_remote_mock_tx_interrupt; remote_mock.item_exists = false; init_completion(&remote_mock.cb_completion); remote_mock.initialized = true; for (i = 0; i < SMP2P_NUM_PROCS; i++) { INIT_WORK(&(remote_loopback[i].rmt_lpb_work), smp2p_rmt_lpb_worker); if (i == SMP2P_REMOTE_MOCK_PROC) /* do not register loopback for remote mock proc */ continue; int_cfg = smp2p_get_interrupt_config(); if (!int_cfg) { SMP2P_ERR("Remote processor config unavailable\n"); return 0; } if (!int_cfg[i].is_configured) continue; msm_smp2p_init_rmt_lpb(&remote_loopback[i], i, SMP2P_RLPB_ENTRY_NAME); } return 0; } module_init(smp2p_remote_mock_init);