tlk: 6/19 update
[3rdparty/ote_partner/tlk.git] / kernel / task_unload.c
1 /*
2  * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files
6  * (the "Software"), to deal in the Software without restriction,
7  * including without limitation the rights to use, copy, modify, merge,
8  * publish, distribute, sublicense, and/or sell copies of the Software,
9  * and to permit persons to whom the Software is furnished to do so,
10  * subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23 #include <debug.h>
24 #include <sys/types.h>
25 #include <assert.h>
26 #include <string.h>
27 #include <err.h>
28 #include <malloc.h>
29 #include <kernel/task.h>
30 #include <kernel/thread.h>
31 #include <kernel/task_load.h>
32 #include <kernel/event.h>
33 #include <ote_intf.h>
34
35 static event_t task_reaper_event;
36 static uint32_t task_unload_counter;
37
38 /*! Release resources of a terminated task.
39  *
40  * Called by the task_reaper thread.
41  */
42 static void task_free_resources(task_t **taskp_p)
43 {
44         if (taskp_p && *taskp_p) {
45                 task_map_t *mptr = NULL;
46                 task_map_t *mptr_tmp = NULL;
47                 task_t *taskp = *taskp_p;
48
49                 ASSERT(taskp->task_state == TASK_STATE_TERMINATED);
50                 ASSERT(taskp->elf_hdr);
51                 ASSERT(taskp->task_size > 0);
52                 ASSERT(list_is_empty(&taskp->thread_node));
53                 ASSERT(list_in_list(&taskp->node));
54
55                 list_for_every_entry_safe(&taskp->map_list, mptr, mptr_tmp, task_map_t, node) {
56                         arch_task_unmap_memory(taskp, mptr);
57                 }
58
59                 /* clear task image in tlk heap before deallocating it */
60                 memset(taskp->elf_hdr, 0, taskp->task_size);
61
62                 if (taskp->task_type == TASK_TYPE_LOADED) {
63                         /* dealloc task image memory, if allocated from TLK heap */
64                         task_dealloc_memory((vaddr_t)taskp->elf_hdr, taskp->task_size);
65                 }
66
67                 enter_critical_section();
68                 list_delete(&taskp->node);
69                 exit_critical_section();
70
71                 /* Clear task header in task_list */
72                 memset(taskp, 0, sizeof(task_t));
73
74                 free(taskp);
75                 *taskp_p = NULL;
76
77                 task_unload_counter++;
78         }
79 }
80
81 /*! A reaper thread that gets an event up every time a kernel thread associated
82  * with a task gets killed.
83  *
84  * This scans the task list for any tasks that has no threads left AND
85  * is flagged as terminated. This means tasks started without threads or tasks committing
86  * suicide will not get reaped, they need to be explicitely unloaded.
87  *
88  * If it finds such a zombie task it will release its resources.
89  */
90 static int task_reaper(void *arg)
91 {
92         (void)arg;
93
94         while(1) {
95                 uint32_t index = 0;
96                 task_t *taskp = NULL;
97                 u_int task_next_index = 0;
98
99                 event_wait(&task_reaper_event);
100
101                 enter_critical_section();
102
103                 task_next_index = task_get_count();
104
105                 /* Scan task list for terminated tasks that have no threads left.
106                  *
107                  * Scan the list every time from the beginning to find all terminated
108                  * tasks since last scan.
109                  */
110                 while(1) {
111                         if (index >= task_next_index) {
112                                 taskp = NULL;
113                                 break;
114                         }
115
116                         taskp = task_find_task_by_index(index++);
117                         if (!taskp)
118                                 continue;
119
120                         if ((taskp->task_state == TASK_STATE_TERMINATED) &&
121                             (list_is_empty(&taskp->thread_node))) {
122                                 /*
123                                  * taskp has no threads left and in terminated
124                                  * state => release it...
125                                  */
126                                 break;
127                         }
128                 }
129
130                 exit_critical_section();
131
132                 if (taskp) {
133                         /* and then free the resources */
134                         task_free_resources(&taskp);
135
136                         /* Do not unsignal event when dead task found,
137                          * instead scan for more.
138                          */
139                         continue;
140                 }
141
142                 event_unsignal(&task_reaper_event);
143         }
144
145         return 0;
146 }
147
148 void task_unload_init(void)
149 {
150         event_init(&task_reaper_event, false, 0);
151
152         (void)thread_resume(thread_create("task reaper", &task_reaper,
153                                           NULL, DEFAULT_PRIORITY,
154                                           DEFAULT_STACK_SIZE));
155
156         dprintf(SPEW, "task reaper started\n");
157 }
158
159 /*! Remove a dead thread from the thread list of a task.
160  *
161  * Called by thread cleanup before the thread resources
162  * are deallocated when the thread belongs to a task.
163  */
164 void task_thread_killed(thread_t *thread)
165 {
166         task_t *taskp = NULL;
167
168         if (!thread)
169                 return;
170
171         ASSERT(thread->state == THREAD_DEATH);
172         ASSERT(thread->arch.task);
173         ASSERT(list_in_list(&thread->task_node));
174
175         /* remove the thread from TE session lists */
176         te_session_cancel_thread(thread);
177
178         taskp = thread->arch.task;
179
180         /* remove dead thread from task's thread list */
181         enter_critical_section();
182         list_delete(&thread->task_node);
183         exit_critical_section();
184
185         /* Was this the last thread of a task?
186          * If so, flag the task terminated and wakeup the Grim Reaper.
187          */
188         if (list_is_empty(&taskp->thread_node)) {
189                 taskp->task_state = TASK_STATE_TERMINATED;
190                 event_signal(&task_reaper_event, false);
191         }
192 }
193
194 /*! Initiate unloading of a task.
195  *
196  *  Called by task load syscall handler.
197  *
198  * Note that after unloading the last task which has installer privileges
199  * you can no longer install anything to the system.
200  */
201 status_t task_unload(task_t **taskp_p)
202 {
203 #if HAVE_UNLOAD_TASKS == 0
204         dprintf(INFO, "task unloading not enabled\n");
205         return ERR_NOT_ALLOWED;
206 #else
207         status_t err = NO_ERROR;
208         thread_t *thread = NULL;
209         thread_t *th_tmp = NULL;
210         int killed_thread_count = 0;
211         const char *ch_state = NULL;
212         char tp_name[OTE_TASK_NAME_MAX_LENGTH+3];
213         task_t *taskp = NULL;
214
215         if (!taskp_p || !*taskp_p) {
216                 err = ERR_INVALID_ARGS;
217                 goto exit;
218         }
219         taskp = *taskp_p;
220
221         task_print_uuid(INFO, "task unload request ", taskp);
222
223 #if HAVE_UNLOAD_STATIC_TASKS == 0
224         if (taskp->task_type != TASK_TYPE_LOADED) {
225                 dprintf(INFO, "Can only unload previously loaded tasks\n");
226                 err = ERR_NOT_ALLOWED;
227                 goto exit;
228         }
229 #endif
230
231         switch (taskp->task_state) {
232         case TASK_STATE_BLOCKED:
233                 if (!ch_state)
234                         ch_state = "blocked";
235                 /* FALLTHROUGH */
236
237         case TASK_STATE_ACTIVE:
238                 if (!ch_state)
239                         ch_state = "active";
240
241                 if (taskp->props.initial_state & OTE_MANIFEST_TASK_ISTATE_STICKY) {
242                         dprintf(INFO, "Sticky %s task#%u%s can not be unloaded\n",
243                                 ch_state,
244                                 taskp->task_index,
245                                 task_get_name_str(taskp, " (", ")", tp_name, sizeof(tp_name)));
246                         err = ERR_NOT_ALLOWED;
247                         goto exit;
248                 }
249
250                 taskp->task_state = TASK_STATE_BLOCKED;
251
252                 /* The loop kills all kernel threads of the task which will wakeup
253                  * task reaper which will reclaim resources when the task dies.
254                  */
255                 list_for_every_entry_safe(&taskp->thread_node, thread, th_tmp, thread_t, task_node) {
256                         dprintf(INFO, "killing %s task#%u%s thread#%u\n",
257                                 ch_state, taskp->task_index,
258                                 task_get_name_str(taskp, " (", ")", tp_name, sizeof(tp_name)),
259                                 killed_thread_count);
260
261                         killed_thread_count++;
262                         thread_kill(thread);
263                 }
264
265                 /* All active tasks should have at least one thread, but
266                  * if there are no threads release the task resources directly.
267                  * (Task creation allows this when the EFL binary does not
268                  * have an entry point).
269                  */
270                 if (killed_thread_count == 0) {
271                         /* task had no associated threads so just release resources
272                          */
273                         dprintf(INFO, "releasing resources of %s task#%u%s -- no threads\n",
274                                 ch_state, taskp->task_index,
275                                 task_get_name_str(taskp, " (", ")", tp_name, sizeof(tp_name)));
276
277                         taskp->task_state = TASK_STATE_TERMINATED;
278                         task_free_resources(&taskp);
279                 }
280                 break;
281
282         case TASK_STATE_INIT:
283                 /* No threads have yet been created for a task in INIT state */
284                 if (!ch_state)
285                         ch_state = "INIT";
286                 /* FALLTHROUGH */
287
288         case TASK_STATE_TERMINATED:
289                 /* all threads must be killed before task enters TERMINATED state */
290                 if (!ch_state)
291                         ch_state = "TERMINATED";
292
293                 ASSERT(list_is_empty(&taskp->thread_node));
294
295                 dprintf(INFO, "releasing resources of %s task#%u%s -- no threads\n",
296                         ch_state, taskp->task_index,
297                         task_get_name_str(taskp, " (", ")", tp_name, sizeof(tp_name)));
298
299                 taskp->task_state = TASK_STATE_TERMINATED;
300                 task_free_resources(&taskp);
301                 break;
302
303         case TASK_STATE_UNKNOWN:
304                 if (!ch_state)
305                         ch_state = "UNKNOWN";
306                 /* FALLTHROUGH */
307
308         default:
309                 if (!ch_state)
310                         ch_state = "(unknown)";
311
312                 dprintf(INFO, "Attempt to unload task#%u with obscure state %s(%u)?\n",
313                         taskp->task_index, ch_state, taskp->task_state);
314                 break;
315         }
316
317 exit:
318         if (taskp_p)
319                 *taskp_p = taskp;
320         return err;
321 #endif /* HAVE_UNLOAD_TASKS */
322 }
323
324 uint32_t task_get_unload_counter()
325 {
326         return task_unload_counter;
327 }