staging: nvshm: create new communication channel for RPC
[linux-3.10.git] / drivers / staging / nvshm / nvshm_ipc.c
1 /*
2  * Copyright (C) 2012-2013 NVIDIA Corporation.
3  *
4  *
5  * This software is licensed under the terms of the GNU General Public
6  * License version 2, as published by the Free Software Foundation, and
7  * may be copied, distributed, and modified under those terms.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  */
15
16 #include "nvshm_types.h"
17 #include "nvshm_if.h"
18 #include "nvshm_priv.h"
19 #include "nvshm_iobuf.h"
20 #include "nvshm_ipc.h"
21 #include "nvshm_queue.h"
22
23 #include <linux/interrupt.h>
24 #include <asm/mach/map.h>
25 #include <mach/tegra_bb.h>
26 #include <asm/cacheflush.h>
27
28 #define NVSHM_WAKE_TIMEOUT_NS (20 * NSEC_PER_MSEC)
29 #define NVSHM_WAKE_MAX_COUNT (50)
30
31 static int ipc_readconfig(struct nvshm_handle *handle)
32 {
33         struct nvshm_config *conf;
34         int chan;
35
36         pr_debug("%s\n", __func__);
37
38         conf = (struct nvshm_config *)(handle->mb_base_virt
39                                        + NVSHM_CONFIG_OFFSET);
40         if (conf->version != NVSHM_CONFIG_VERSION) {
41                 pr_warn("%s: new/old config version 0x%x vs. 0x%x\n",
42                                 __func__,
43                                 (unsigned int)conf->version,
44                                 NVSHM_CONFIG_VERSION);
45         }
46         if (handle->ipc_size != conf->shmem_size) {
47                 pr_warn("%s shmem mapped/reported not matching: 0x%x/0x%x\n",
48                         __func__, (unsigned int)handle->ipc_size,
49                         conf->shmem_size);
50         }
51         handle->desc_base_virt = handle->ipc_base_virt
52                 + conf->region_ap_desc_offset;
53         pr_debug("%s desc_base_virt=0x%p\n",
54                  __func__, handle->desc_base_virt);
55
56         handle->desc_size = conf->region_ap_desc_size;
57         pr_debug("%s desc_size=%d\n",
58                 __func__, (int)handle->desc_size);
59
60         /* Data is cached */
61         handle->data_base_virt = handle->ipc_base_virt
62                 + conf->region_ap_data_offset;
63         pr_debug("%s data_base_virt=0x%p\n",
64                 __func__, handle->data_base_virt);
65
66         handle->data_size = conf->region_ap_data_size;
67         pr_debug("%s data_size=%d\n", __func__, (int)handle->data_size);
68
69 #ifndef CONFIG_TEGRA_BASEBAND_SIMU
70         handle->shared_queue_head =
71                 (struct nvshm_iobuf *)(handle->ipc_base_virt
72                                      + conf->queue_bb_offset);
73         pr_debug("%s shared_queue_head offset=0x%lx\n",
74                 __func__,
75                  (long)handle->shared_queue_head - (long)handle->ipc_base_virt);
76 #else
77         handle->shared_queue_head =
78                 (struct nvshm_iobuf *)(handle->ipc_base_virt
79                                       + conf->queue_ap_offset);
80         pr_debug("%s shared_queue_head offset=0x%lx\n",
81                 __func__,
82                  (long)handle->shared_queue_head - (long)handle->ipc_base_virt);
83 #endif
84         handle->shared_queue_tail =
85                 (struct nvshm_iobuf *)(handle->ipc_base_virt
86                                      + conf->queue_ap_offset);
87         pr_debug("%s shared_queue_tail offset=0x%lx\n",
88                 __func__, (long)handle->shared_queue_tail -
89                 (long)handle->ipc_base_virt);
90
91         for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
92                 handle->chan[chan].index = chan;
93                 handle->chan[chan].map = conf->chan_map[chan];
94                 if (handle->chan[chan].map.type != NVSHM_CHAN_UNMAP) {
95                         pr_debug("%s chan[%d]=%s\n",
96                                 __func__, chan, handle->chan[chan].map.name);
97                 }
98         }
99
100         if (conf->version >= NVSHM_CONFIG_SERIAL_VERSION) {
101                 /* Serial number (e.g BBC PCID) */
102                 tegra_bb_set_ipc_serial(handle->tegra_bb, conf->serial);
103         }
104
105         handle->conf = conf;
106         handle->configured = 1;
107         return 0;
108 }
109
110 static int init_interfaces(struct nvshm_handle *handle)
111 {
112         int nlog = 0, ntty = 0, nnet = 0, nrpc = 0;
113         int chan;
114
115         for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
116                 switch (handle->chan[chan].map.type) {
117                 case NVSHM_CHAN_UNMAP:
118                         break;
119                 case NVSHM_CHAN_TTY:
120                 case NVSHM_CHAN_LOG:
121                         ntty++;
122                         handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_TTY;
123                         break;
124                 case NVSHM_CHAN_NET:
125                         handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_NET;
126                         nnet++;
127                         break;
128                 case NVSHM_CHAN_RPC:
129                         handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_RPC;
130                         nrpc++;
131                         break;
132                 default:
133                         break;
134                 }
135         }
136
137         if (ntty) {
138                 pr_debug("%s init %d tty channels\n", __func__, ntty);
139                 nvshm_tty_init(handle);
140         }
141
142         if (nlog)
143                 pr_debug("%s init %d log channels\n", __func__, nlog);
144
145         if (nnet) {
146                 pr_debug("%s init %d net channels\n", __func__, nnet);
147                 nvshm_net_init(handle);
148         }
149
150         if (nrpc) {
151                 pr_debug("%s init %d rpc channels\n", __func__, nrpc);
152                 nvshm_rpc_init(handle);
153         }
154
155         return 0;
156 }
157
158 static int cleanup_interfaces(struct nvshm_handle *handle)
159 {
160         int nlog = 0, ntty = 0, nnet = 0, nrpc = 0;
161         int chan;
162
163         /* No need to protect this as configuration will arrive after cleanup
164          * is propagated to userland
165          */
166         handle->configured = 0;
167
168         for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
169                 switch (handle->chan[chan].map.type) {
170                 case NVSHM_CHAN_TTY:
171                 case NVSHM_CHAN_LOG:
172                         ntty++;
173                         break;
174                 case NVSHM_CHAN_NET:
175                         nnet++;
176                         break;
177                 case NVSHM_CHAN_RPC:
178                         nrpc++;
179                         break;
180                 default:
181                         break;
182                 }
183         }
184
185         if (ntty) {
186                 pr_debug("%s cleanup %d tty channels\n", __func__, ntty);
187                 nvshm_tty_cleanup();
188         }
189
190         if (nlog)
191                 pr_debug("%s cleanup %d log channels\n", __func__, nlog);
192
193         if (nnet) {
194                 pr_debug("%s cleanup %d net channels\n", __func__, nnet);
195                 nvshm_net_cleanup();
196         }
197
198         if (nrpc) {
199                 pr_debug("%s cleanup %d rpc channels\n", __func__, nrpc);
200                 nvshm_rpc_cleanup();
201         }
202
203     /* Remove serial sysfs entry */
204         tegra_bb_set_ipc_serial(handle->tegra_bb, NULL);
205
206         return 0;
207 }
208
209 static void ipc_work(struct work_struct *work)
210 {
211         struct nvshm_handle *handle = container_of(work,
212                                                    struct nvshm_handle,
213                                                    nvshm_work);
214         int new_state;
215         int cmd;
216
217         if (!wake_lock_active(&handle->dl_lock))
218                 wake_lock(&handle->dl_lock);
219         new_state = *((int *)handle->mb_base_virt);
220         cmd = new_state & 0xFFFF;
221         if (((~new_state >> 16) ^ (cmd)) & 0xFFFF) {
222                 pr_err("%s IPC check failure msg=0x%x\n",
223                        __func__, new_state);
224                 if (handle->configured) {
225                         nvshm_abort_queue(handle);
226                         cleanup_interfaces(handle);
227                 }
228                 enable_irq(handle->bb_irq);
229                 wake_unlock(&handle->dl_lock);
230                 return;
231         }
232         switch (cmd) {
233         case NVSHM_IPC_READY:
234                 /* most encountered message - process queue */
235                 if (cmd == handle->old_status) {
236                         /* Process IPC queue but do not notify sysfs */
237                         nvshm_process_queue(handle);
238                 } else {
239                         ipc_readconfig(handle);
240                         nvshm_iobuf_init(handle);
241                         nvshm_init_queue(handle);
242                         init_interfaces(handle);
243                 }
244                 handle->old_status = cmd;
245                 enable_irq(handle->bb_irq);
246                 wake_unlock(&handle->dl_lock);
247                 return;
248         case NVSHM_IPC_BOOT_FW_REQ:
249         case NVSHM_IPC_BOOT_RESTART_FW_REQ:
250                 if (handle->configured) {
251                         nvshm_abort_queue(handle);
252                         cleanup_interfaces(handle);
253                         pr_debug("%s: cleanup done\n", __func__);
254                 }
255                 break;
256         case NVSHM_IPC_BOOT_ERROR_BT2_HDR:
257         case NVSHM_IPC_BOOT_ERROR_BT2_SIGN:
258         case NVSHM_IPC_BOOT_ERROR_HWID:
259         case NVSHM_IPC_BOOT_ERROR_APP_HDR:
260         case NVSHM_IPC_BOOT_ERROR_APP_SIGN:
261         case NVSHM_IPC_BOOT_ERROR_UNLOCK_HEADER:
262         case NVSHM_IPC_BOOT_ERROR_UNLOCK_SIGN:
263         case NVSHM_IPC_BOOT_ERROR_UNLOCK_PCID:
264                 pr_err("%s BB startup failure: msg=0x%x\n",
265                        __func__, new_state);
266                 break;
267         case NVSHM_IPC_BOOT_COLD_BOOT_IND:
268         case NVSHM_IPC_BOOT_FW_CONF:
269                 /* Should not have these - something went wrong... */
270                 pr_err("%s IPC IT error: msg=0x%x\n",
271                        __func__, new_state);
272                 break;
273         default:
274                 pr_err("%s unknown IPC message found: msg=0x%x\n",
275                        __func__, new_state);
276                 break;
277         }
278         handle->old_status = cmd;
279         enable_irq(handle->bb_irq);
280         wake_unlock(&handle->dl_lock);
281 }
282
283 static void nvshm_ipc_handler(void *data)
284 {
285         struct nvshm_handle *handle = (struct nvshm_handle *)data;
286         int ret;
287         pr_debug("%s\n", __func__);
288         ret = queue_work(handle->nvshm_wq, &handle->nvshm_work);
289 }
290
291 static enum hrtimer_restart nvshm_ipc_timer_func(struct hrtimer *timer)
292 {
293         struct nvshm_handle *handle =
294                 container_of(timer, struct nvshm_handle, wake_timer);
295
296         if (tegra_bb_check_ipc(handle->tegra_bb) == 1) {
297                 pr_debug("%s AP2BB is cleared\n", __func__);
298                 wake_unlock(&handle->ul_lock);
299                 return HRTIMER_NORESTART;
300         }
301         if (handle->timeout++ > NVSHM_WAKE_MAX_COUNT) {
302                 pr_warn("%s AP2BB not cleared in 1s - aborting\n", __func__);
303                 tegra_bb_abort_ipc(handle->tegra_bb);
304                 wake_unlock(&handle->ul_lock);
305                 return HRTIMER_NORESTART;
306         }
307         pr_debug("%s AP2BB is still set\n", __func__);
308         hrtimer_forward_now(timer, ktime_set(0, NVSHM_WAKE_TIMEOUT_NS));
309         return HRTIMER_RESTART;
310 }
311
312 int nvshm_register_ipc(struct nvshm_handle *handle)
313 {
314         pr_debug("%s\n", __func__);
315         snprintf(handle->wq_name, 15, "nvshm_queue%d", handle->instance);
316         handle->nvshm_wq = create_singlethread_workqueue(handle->wq_name);
317         INIT_WORK(&handle->nvshm_work, ipc_work);
318
319         hrtimer_init(&handle->wake_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
320         handle->wake_timer.function = nvshm_ipc_timer_func;
321
322         tegra_bb_register_ipc(handle->tegra_bb, nvshm_ipc_handler, handle);
323         return 0;
324 }
325
326 int nvshm_unregister_ipc(struct nvshm_handle *handle)
327 {
328         pr_debug("%s flush workqueue\n", __func__);
329         flush_workqueue(handle->nvshm_wq);
330
331         pr_debug("%s destroy workqueue\n", __func__);
332         destroy_workqueue(handle->nvshm_wq);
333
334         pr_debug("%s unregister tegra_bb\n", __func__);
335         tegra_bb_register_ipc(handle->tegra_bb, NULL, NULL);
336
337         hrtimer_cancel(&handle->wake_timer);
338         return 0;
339 }
340
341 int nvshm_generate_ipc(struct nvshm_handle *handle)
342 {
343         int ret;
344
345         /* take wake lock until BB ack our irq */
346         if (!wake_lock_active(&handle->ul_lock))
347                 wake_lock(&handle->ul_lock);
348
349         if (!hrtimer_active(&handle->wake_timer)) {
350                 handle->timeout = 0;
351                 ret = hrtimer_start(&handle->wake_timer,
352                                     ktime_set(0, NVSHM_WAKE_TIMEOUT_NS),
353                                     HRTIMER_MODE_REL);
354         }
355         mb();
356         /* generate ipc */
357         tegra_bb_generate_ipc(handle->tegra_bb);
358         return 0;
359 }
360