6a9af0ea6500913fbcc8b1d7526684558c32da6a
[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;
113         int chan;
114
115         for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
116                 switch (handle->chan[chan].map.type) {
117                 case NVSHM_CHAN_TTY:
118                 case NVSHM_CHAN_LOG:
119                         ntty++;
120                         handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_TTY;
121                         break;
122                 case NVSHM_CHAN_NET:
123                         handle->chan[chan].rate_counter = NVSHM_RATE_LIMIT_NET;
124                         nnet++;
125                         break;
126                 default:
127                         break;
128                 }
129         }
130
131         if (ntty) {
132                 pr_debug("%s init %d tty channels\n", __func__, ntty);
133                 nvshm_tty_init(handle);
134         }
135
136         if (nlog)
137                 pr_debug("%s init %d log channels\n", __func__, nlog);
138
139         if (nnet) {
140                 pr_debug("%s init %d net channels\n", __func__, nnet);
141                 nvshm_net_init(handle);
142         }
143
144         return 0;
145 }
146
147 static int cleanup_interfaces(struct nvshm_handle *handle)
148 {
149         int nlog = 0, ntty = 0, nnet = 0;
150         int chan;
151
152         /* No need to protect this as configuration will arrive after cleanup
153          * is propagated to userland
154          */
155         handle->configured = 0;
156
157         for (chan = 0; chan < NVSHM_MAX_CHANNELS; chan++) {
158                 switch (handle->chan[chan].map.type) {
159                 case NVSHM_CHAN_TTY:
160                 case NVSHM_CHAN_LOG:
161                         ntty++;
162                         break;
163                 case NVSHM_CHAN_NET:
164                         nnet++;
165                         break;
166                 default:
167                         break;
168                 }
169         }
170
171         if (ntty) {
172                 pr_debug("%s cleanup %d tty channels\n", __func__, ntty);
173                 nvshm_tty_cleanup();
174         }
175
176         if (nlog)
177                 pr_debug("%s cleanup %d log channels\n", __func__, nlog);
178
179         if (nnet) {
180                 pr_debug("%s cleanup %d net channels\n", __func__, nnet);
181                 nvshm_net_cleanup();
182         }
183
184         /* Remove serial sysfs entry */
185         tegra_bb_set_ipc_serial(handle->tegra_bb, NULL);
186
187         return 0;
188 }
189
190 static void ipc_work(struct work_struct *work)
191 {
192         struct nvshm_handle *handle = container_of(work,
193                                                    struct nvshm_handle,
194                                                    nvshm_work);
195         int new_state;
196         int cmd;
197
198         if (!wake_lock_active(&handle->dl_lock))
199                 wake_lock(&handle->dl_lock);
200         new_state = *((int *)handle->mb_base_virt);
201         cmd = new_state & 0xFFFF;
202         if (((~new_state >> 16) ^ (cmd)) & 0xFFFF) {
203                 pr_err("%s IPC check failure msg=0x%x\n",
204                        __func__, new_state);
205                 if (handle->configured) {
206                         nvshm_abort_queue(handle);
207                         cleanup_interfaces(handle);
208                 }
209                 enable_irq(handle->bb_irq);
210                 wake_unlock(&handle->dl_lock);
211                 return;
212         }
213         switch (cmd) {
214         case NVSHM_IPC_READY:
215                 /* most encountered message - process queue */
216                 if (cmd == handle->old_status) {
217                         /* Process IPC queue but do not notify sysfs */
218                         nvshm_process_queue(handle);
219                 } else {
220                         ipc_readconfig(handle);
221                         nvshm_iobuf_init(handle);
222                         nvshm_init_queue(handle);
223                         init_interfaces(handle);
224                 }
225                 handle->old_status = cmd;
226                 enable_irq(handle->bb_irq);
227                 wake_unlock(&handle->dl_lock);
228                 return;
229         case NVSHM_IPC_BOOT_FW_REQ:
230         case NVSHM_IPC_BOOT_RESTART_FW_REQ:
231                 if (handle->configured) {
232                         nvshm_abort_queue(handle);
233                         cleanup_interfaces(handle);
234                         pr_debug("%s: cleanup done\n", __func__);
235                 }
236                 break;
237         case NVSHM_IPC_BOOT_ERROR_BT2_HDR:
238         case NVSHM_IPC_BOOT_ERROR_BT2_SIGN:
239         case NVSHM_IPC_BOOT_ERROR_HWID:
240         case NVSHM_IPC_BOOT_ERROR_APP_HDR:
241         case NVSHM_IPC_BOOT_ERROR_APP_SIGN:
242         case NVSHM_IPC_BOOT_ERROR_UNLOCK_HEADER:
243         case NVSHM_IPC_BOOT_ERROR_UNLOCK_SIGN:
244         case NVSHM_IPC_BOOT_ERROR_UNLOCK_PCID:
245                 pr_err("%s BB startup failure: msg=0x%x\n",
246                        __func__, new_state);
247                 break;
248         case NVSHM_IPC_BOOT_COLD_BOOT_IND:
249         case NVSHM_IPC_BOOT_FW_CONF:
250                 /* Should not have these - something went wrong... */
251                 pr_err("%s IPC IT error: msg=0x%x\n",
252                        __func__, new_state);
253                 break;
254         default:
255                 pr_err("%s unknown IPC message found: msg=0x%x\n",
256                        __func__, new_state);
257                 break;
258         }
259         handle->old_status = cmd;
260         enable_irq(handle->bb_irq);
261         wake_unlock(&handle->dl_lock);
262 }
263
264 static void nvshm_ipc_handler(void *data)
265 {
266         struct nvshm_handle *handle = (struct nvshm_handle *)data;
267         int ret;
268         pr_debug("%s\n", __func__);
269         ret = queue_work(handle->nvshm_wq, &handle->nvshm_work);
270 }
271
272 static enum hrtimer_restart nvshm_ipc_timer_func(struct hrtimer *timer)
273 {
274         struct nvshm_handle *handle =
275                 container_of(timer, struct nvshm_handle, wake_timer);
276
277         if (tegra_bb_check_ipc(handle->tegra_bb) == 1) {
278                 pr_debug("%s AP2BB is cleared\n", __func__);
279                 wake_unlock(&handle->ul_lock);
280                 return HRTIMER_NORESTART;
281         }
282         if (handle->timeout++ > NVSHM_WAKE_MAX_COUNT) {
283                 pr_warn("%s AP2BB not cleared in 1s - aborting\n", __func__);
284                 tegra_bb_abort_ipc(handle->tegra_bb);
285                 wake_unlock(&handle->ul_lock);
286                 return HRTIMER_NORESTART;
287         }
288         pr_debug("%s AP2BB is still set\n", __func__);
289         hrtimer_forward_now(timer, ktime_set(0, NVSHM_WAKE_TIMEOUT_NS));
290         return HRTIMER_RESTART;
291 }
292
293 int nvshm_register_ipc(struct nvshm_handle *handle)
294 {
295         pr_debug("%s\n", __func__);
296         snprintf(handle->wq_name, 15, "nvshm_queue%d", handle->instance);
297         handle->nvshm_wq = create_singlethread_workqueue(handle->wq_name);
298         INIT_WORK(&handle->nvshm_work, ipc_work);
299
300         hrtimer_init(&handle->wake_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
301         handle->wake_timer.function = nvshm_ipc_timer_func;
302
303         tegra_bb_register_ipc(handle->tegra_bb, nvshm_ipc_handler, handle);
304         return 0;
305 }
306
307 int nvshm_unregister_ipc(struct nvshm_handle *handle)
308 {
309         pr_debug("%s flush workqueue\n", __func__);
310         flush_workqueue(handle->nvshm_wq);
311
312         pr_debug("%s destroy workqueue\n", __func__);
313         destroy_workqueue(handle->nvshm_wq);
314
315         pr_debug("%s unregister tegra_bb\n", __func__);
316         tegra_bb_register_ipc(handle->tegra_bb, NULL, NULL);
317
318         hrtimer_cancel(&handle->wake_timer);
319         return 0;
320 }
321
322 int nvshm_generate_ipc(struct nvshm_handle *handle)
323 {
324         int ret;
325
326         /* take wake lock until BB ack our irq */
327         if (!wake_lock_active(&handle->ul_lock))
328                 wake_lock(&handle->ul_lock);
329
330         if (!hrtimer_active(&handle->wake_timer)) {
331                 handle->timeout = 0;
332                 ret = hrtimer_start(&handle->wake_timer,
333                                     ktime_set(0, NVSHM_WAKE_TIMEOUT_NS),
334                                     HRTIMER_MODE_REL);
335         }
336         mb();
337         /* generate ipc */
338         tegra_bb_generate_ipc(handle->tegra_bb);
339         return 0;
340 }
341