1/* 2 * Intel SST generic IPC Support 3 * 4 * Copyright (C) 2015, Intel Corporation. All rights reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License version 8 * 2 as published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 */ 16 17#include <linux/types.h> 18#include <linux/kernel.h> 19#include <linux/list.h> 20#include <linux/wait.h> 21#include <linux/module.h> 22#include <linux/spinlock.h> 23#include <linux/device.h> 24#include <linux/slab.h> 25#include <linux/workqueue.h> 26#include <linux/sched.h> 27#include <linux/delay.h> 28#include <linux/platform_device.h> 29#include <linux/kthread.h> 30#include <sound/asound.h> 31 32#include "sst-dsp.h" 33#include "sst-dsp-priv.h" 34#include "sst-ipc.h" 35 36/* IPC message timeout (msecs) */ 37#define IPC_TIMEOUT_MSECS 300 38 39#define IPC_EMPTY_LIST_SIZE 8 40 41/* locks held by caller */ 42static struct ipc_message *msg_get_empty(struct sst_generic_ipc *ipc) 43{ 44 struct ipc_message *msg = NULL; 45 46 if (!list_empty(&ipc->empty_list)) { 47 msg = list_first_entry(&ipc->empty_list, struct ipc_message, 48 list); 49 list_del(&msg->list); 50 } 51 52 return msg; 53} 54 55static int tx_wait_done(struct sst_generic_ipc *ipc, 56 struct ipc_message *msg, void *rx_data) 57{ 58 unsigned long flags; 59 int ret; 60 61 /* wait for DSP completion (in all cases atm inc pending) */ 62 ret = wait_event_timeout(msg->waitq, msg->complete, 63 msecs_to_jiffies(IPC_TIMEOUT_MSECS)); 64 65 spin_lock_irqsave(&ipc->dsp->spinlock, flags); 66 if (ret == 0) { 67 if (ipc->ops.shim_dbg != NULL) 68 ipc->ops.shim_dbg(ipc, "message timeout"); 69 70 list_del(&msg->list); 71 ret = -ETIMEDOUT; 72 } else { 73 74 /* copy the data returned from DSP */ 75 if (msg->rx_size) 76 memcpy(rx_data, msg->rx_data, msg->rx_size); 77 ret = msg->errno; 78 } 79 80 list_add_tail(&msg->list, &ipc->empty_list); 81 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 82 return ret; 83} 84 85static int ipc_tx_message(struct sst_generic_ipc *ipc, u64 header, 86 void *tx_data, size_t tx_bytes, void *rx_data, 87 size_t rx_bytes, int wait) 88{ 89 struct ipc_message *msg; 90 unsigned long flags; 91 92 spin_lock_irqsave(&ipc->dsp->spinlock, flags); 93 94 msg = msg_get_empty(ipc); 95 if (msg == NULL) { 96 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 97 return -EBUSY; 98 } 99 100 msg->header = header; 101 msg->tx_size = tx_bytes; 102 msg->rx_size = rx_bytes; 103 msg->wait = wait; 104 msg->errno = 0; 105 msg->pending = false; 106 msg->complete = false; 107 108 if ((tx_bytes) && (ipc->ops.tx_data_copy != NULL)) 109 ipc->ops.tx_data_copy(msg, tx_data, tx_bytes); 110 111 list_add_tail(&msg->list, &ipc->tx_list); 112 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 113 114 queue_kthread_work(&ipc->kworker, &ipc->kwork); 115 116 if (wait) 117 return tx_wait_done(ipc, msg, rx_data); 118 else 119 return 0; 120} 121 122static int msg_empty_list_init(struct sst_generic_ipc *ipc) 123{ 124 int i; 125 126 ipc->msg = kzalloc(sizeof(struct ipc_message) * 127 IPC_EMPTY_LIST_SIZE, GFP_KERNEL); 128 if (ipc->msg == NULL) 129 return -ENOMEM; 130 131 for (i = 0; i < IPC_EMPTY_LIST_SIZE; i++) { 132 init_waitqueue_head(&ipc->msg[i].waitq); 133 list_add(&ipc->msg[i].list, &ipc->empty_list); 134 } 135 136 return 0; 137} 138 139static void ipc_tx_msgs(struct kthread_work *work) 140{ 141 struct sst_generic_ipc *ipc = 142 container_of(work, struct sst_generic_ipc, kwork); 143 struct ipc_message *msg; 144 unsigned long flags; 145 u64 ipcx; 146 147 spin_lock_irqsave(&ipc->dsp->spinlock, flags); 148 149 if (list_empty(&ipc->tx_list) || ipc->pending) { 150 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 151 return; 152 } 153 154 /* if the DSP is busy, we will TX messages after IRQ. 155 * also postpone if we are in the middle of procesing completion irq*/ 156 ipcx = sst_dsp_shim_read_unlocked(ipc->dsp, SST_IPCX); 157 if (ipcx & (SST_IPCX_BUSY | SST_IPCX_DONE)) { 158 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 159 return; 160 } 161 162 msg = list_first_entry(&ipc->tx_list, struct ipc_message, list); 163 list_move(&msg->list, &ipc->rx_list); 164 165 if (ipc->ops.tx_msg != NULL) 166 ipc->ops.tx_msg(ipc, msg); 167 168 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 169} 170 171int sst_ipc_tx_message_wait(struct sst_generic_ipc *ipc, u64 header, 172 void *tx_data, size_t tx_bytes, void *rx_data, size_t rx_bytes) 173{ 174 return ipc_tx_message(ipc, header, tx_data, tx_bytes, 175 rx_data, rx_bytes, 1); 176} 177EXPORT_SYMBOL_GPL(sst_ipc_tx_message_wait); 178 179int sst_ipc_tx_message_nowait(struct sst_generic_ipc *ipc, u64 header, 180 void *tx_data, size_t tx_bytes) 181{ 182 return ipc_tx_message(ipc, header, tx_data, tx_bytes, 183 NULL, 0, 0); 184} 185EXPORT_SYMBOL_GPL(sst_ipc_tx_message_nowait); 186 187struct ipc_message *sst_ipc_reply_find_msg(struct sst_generic_ipc *ipc, 188 u64 header) 189{ 190 struct ipc_message *msg; 191 u64 mask; 192 193 if (ipc->ops.reply_msg_match != NULL) 194 header = ipc->ops.reply_msg_match(header, &mask); 195 196 if (list_empty(&ipc->rx_list)) { 197 dev_err(ipc->dev, "error: rx list empty but received 0x%llx\n", 198 header); 199 return NULL; 200 } 201 202 list_for_each_entry(msg, &ipc->rx_list, list) { 203 if ((msg->header & mask) == header) 204 return msg; 205 } 206 207 return NULL; 208} 209EXPORT_SYMBOL_GPL(sst_ipc_reply_find_msg); 210 211/* locks held by caller */ 212void sst_ipc_tx_msg_reply_complete(struct sst_generic_ipc *ipc, 213 struct ipc_message *msg) 214{ 215 msg->complete = true; 216 217 if (!msg->wait) 218 list_add_tail(&msg->list, &ipc->empty_list); 219 else 220 wake_up(&msg->waitq); 221} 222EXPORT_SYMBOL_GPL(sst_ipc_tx_msg_reply_complete); 223 224void sst_ipc_drop_all(struct sst_generic_ipc *ipc) 225{ 226 struct ipc_message *msg, *tmp; 227 unsigned long flags; 228 int tx_drop_cnt = 0, rx_drop_cnt = 0; 229 230 /* drop all TX and Rx messages before we stall + reset DSP */ 231 spin_lock_irqsave(&ipc->dsp->spinlock, flags); 232 233 list_for_each_entry_safe(msg, tmp, &ipc->tx_list, list) { 234 list_move(&msg->list, &ipc->empty_list); 235 tx_drop_cnt++; 236 } 237 238 list_for_each_entry_safe(msg, tmp, &ipc->rx_list, list) { 239 list_move(&msg->list, &ipc->empty_list); 240 rx_drop_cnt++; 241 } 242 243 spin_unlock_irqrestore(&ipc->dsp->spinlock, flags); 244 245 if (tx_drop_cnt || rx_drop_cnt) 246 dev_err(ipc->dev, "dropped IPC msg RX=%d, TX=%d\n", 247 tx_drop_cnt, rx_drop_cnt); 248} 249EXPORT_SYMBOL_GPL(sst_ipc_drop_all); 250 251int sst_ipc_init(struct sst_generic_ipc *ipc) 252{ 253 int ret; 254 255 INIT_LIST_HEAD(&ipc->tx_list); 256 INIT_LIST_HEAD(&ipc->rx_list); 257 INIT_LIST_HEAD(&ipc->empty_list); 258 init_waitqueue_head(&ipc->wait_txq); 259 260 ret = msg_empty_list_init(ipc); 261 if (ret < 0) 262 return -ENOMEM; 263 264 /* start the IPC message thread */ 265 init_kthread_worker(&ipc->kworker); 266 ipc->tx_thread = kthread_run(kthread_worker_fn, 267 &ipc->kworker, "%s", 268 dev_name(ipc->dev)); 269 if (IS_ERR(ipc->tx_thread)) { 270 dev_err(ipc->dev, "error: failed to create message TX task\n"); 271 ret = PTR_ERR(ipc->tx_thread); 272 kfree(ipc->msg); 273 return ret; 274 } 275 276 init_kthread_work(&ipc->kwork, ipc_tx_msgs); 277 return 0; 278} 279EXPORT_SYMBOL_GPL(sst_ipc_init); 280 281void sst_ipc_fini(struct sst_generic_ipc *ipc) 282{ 283 if (ipc->tx_thread) 284 kthread_stop(ipc->tx_thread); 285 286 if (ipc->msg) 287 kfree(ipc->msg); 288} 289EXPORT_SYMBOL_GPL(sst_ipc_fini); 290 291/* Module information */ 292MODULE_AUTHOR("Jin Yao"); 293MODULE_DESCRIPTION("Intel SST IPC generic"); 294MODULE_LICENSE("GPL v2"); 295