// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2018, Linaro Limited */ #include #include #include #include #include #include #include /* lockdep_node::flags values */ /* Flags used for depth-first topological sorting */ #define LOCKDEP_NODE_TEMP_MARK BIT(0) #define LOCKDEP_NODE_PERM_MARK BIT(1) /* Flag used during breadth-first search (print shortest cycle) */ #define LOCKDEP_NODE_BFS_VISITED BIT(2) /* Find node in graph or add it */ static struct lockdep_node *lockdep_add_to_graph( struct lockdep_node_head *graph, uintptr_t lock_id) { struct lockdep_node *node = NULL; assert(graph); TAILQ_FOREACH(node, graph, link) if (node->lock_id == lock_id) return node; node = calloc(1, sizeof(*node)); if (!node) return NULL; node->lock_id = lock_id; STAILQ_INIT(&node->edges); TAILQ_INSERT_TAIL(graph, node, link); return node; } static vaddr_t *dup_call_stack(vaddr_t *stack) { vaddr_t *nstack = NULL; int n = 0; if (!stack) return NULL; while (stack[n]) n++; nstack = malloc((n + 1) * sizeof(vaddr_t)); if (!nstack) return NULL; memcpy(nstack, stack, (n + 1) * sizeof(vaddr_t)); return nstack; } static void lockdep_print_call_stack(vaddr_t *stack) { vaddr_t *p = NULL; EMSG_RAW("Call stack:"); for (p = stack; p && *p; p++) EMSG_RAW(" %#" PRIxPTR, *p); } static TEE_Result lockdep_add_edge(struct lockdep_node *from, struct lockdep_node *to, vaddr_t *call_stack_from, vaddr_t *call_stack_to, uintptr_t thread_id) { struct lockdep_edge *edge = NULL; STAILQ_FOREACH(edge, &from->edges, link) if (edge->to == to) return TEE_SUCCESS; edge = calloc(1, sizeof(*edge)); if (!edge) return TEE_ERROR_OUT_OF_MEMORY; edge->to = to; edge->call_stack_from = dup_call_stack(call_stack_from); edge->call_stack_to = dup_call_stack(call_stack_to); edge->thread_id = thread_id; STAILQ_INSERT_TAIL(&from->edges, edge, link); return TEE_SUCCESS; } struct lockdep_bfs { struct lockdep_node *node; uintptr_t *path; int pathlen; TAILQ_ENTRY(lockdep_bfs) link; }; TAILQ_HEAD(lockdep_bfs_head, lockdep_bfs); static void lockdep_bfs_queue_delete(struct lockdep_bfs_head *queue) { struct lockdep_bfs *cur = NULL; struct lockdep_bfs *next = NULL; TAILQ_FOREACH_SAFE(cur, queue, link, next) { TAILQ_REMOVE(queue, cur, link); free(cur->path); free(cur); } } /* * Print shortest cycle in @graph that contains @node. * This function performs an iterative breadth-first search starting from @node, * and stops when it reaches @node again. In each node we're tracking the path * from the start node. */ static uintptr_t *lockdep_graph_get_shortest_cycle(struct lockdep_node *node) { struct lockdep_bfs_head queue; struct lockdep_bfs *qe = NULL; uintptr_t *ret = NULL; TAILQ_INIT(&queue); node->flags |= LOCKDEP_NODE_BFS_VISITED; qe = calloc(1, sizeof(*qe)); if (!qe) goto out; qe->node = node; qe->path = malloc(sizeof(uintptr_t)); if (!qe->path) goto out; qe->path[0] = node->lock_id; qe->pathlen = 1; TAILQ_INSERT_TAIL(&queue, qe, link); while (!TAILQ_EMPTY(&queue)) { qe = TAILQ_FIRST(&queue); struct lockdep_node *n = qe->node; TAILQ_REMOVE(&queue, qe, link); struct lockdep_edge *e = NULL; STAILQ_FOREACH(e, &n->edges, link) { if (e->to->lock_id == node->lock_id) { uintptr_t *tmp = NULL; size_t nlen = qe->pathlen + 1; /* * Cycle found. Terminate cycle path with NULL * and return it. */ tmp = realloc(qe->path, nlen * sizeof(uintptr_t)); if (!tmp) { EMSG("Out of memory"); free(qe->path); ret = NULL; goto out; } qe->path = tmp; qe->path[nlen - 1] = 0; ret = qe->path; goto out; } if (!(e->to->flags & LOCKDEP_NODE_BFS_VISITED)) { e->to->flags |= LOCKDEP_NODE_BFS_VISITED; size_t nlen = qe->pathlen + 1; struct lockdep_bfs *nqe = calloc(1, sizeof(*nqe)); if (!nqe) goto out; nqe->node = e->to; nqe->path = malloc(nlen * sizeof(uintptr_t)); if (!nqe->path) goto out; nqe->pathlen = nlen; memcpy(nqe->path, qe->path, qe->pathlen * sizeof(uintptr_t)); nqe->path[nlen - 1] = e->to->lock_id; TAILQ_INSERT_TAIL(&queue, nqe, link); } } free(qe->path); free(qe); qe = NULL; } out: free(qe); lockdep_bfs_queue_delete(&queue); return ret; } static TEE_Result lockdep_visit(struct lockdep_node *node) { if (node->flags & LOCKDEP_NODE_PERM_MARK) return TEE_SUCCESS; if (node->flags & LOCKDEP_NODE_TEMP_MARK) return TEE_ERROR_BAD_STATE; /* Not a DAG! */ node->flags |= LOCKDEP_NODE_TEMP_MARK; struct lockdep_edge *e; STAILQ_FOREACH(e, &node->edges, link) { TEE_Result res = lockdep_visit(e->to); if (res) return res; } node->flags |= LOCKDEP_NODE_PERM_MARK; return TEE_SUCCESS; } static TEE_Result lockdep_graph_sort(struct lockdep_node_head *graph) { struct lockdep_node *node = NULL; TAILQ_FOREACH(node, graph, link) { if (!node->flags) { /* Unmarked node */ TEE_Result res = lockdep_visit(node); if (res) return res; } } TAILQ_FOREACH(node, graph, link) node->flags = 0; return TEE_SUCCESS; } static struct lockdep_edge *lockdep_find_edge(struct lockdep_node_head *graph, uintptr_t from, uintptr_t to) { struct lockdep_node *node = NULL; struct lockdep_edge *edge = NULL; TAILQ_FOREACH(node, graph, link) if (node->lock_id == from) STAILQ_FOREACH(edge, &node->edges, link) if (edge->to->lock_id == to) return edge; return NULL; } static void lockdep_print_edge_info(uintptr_t from __maybe_unused, struct lockdep_edge *edge) { uintptr_t __maybe_unused to = edge->to->lock_id; EMSG_RAW("-> Thread %#" PRIxPTR " acquired lock %#" PRIxPTR " at:", edge->thread_id, to); lockdep_print_call_stack(edge->call_stack_to); EMSG_RAW("...while holding lock %#" PRIxPTR " acquired at:", from); lockdep_print_call_stack(edge->call_stack_from); } /* * Find cycle containing @node in the lock graph, then print full debug * information about each edge (thread that acquired the locks and call stacks) */ static void lockdep_print_cycle_info(struct lockdep_node_head *graph, struct lockdep_node *node) { struct lockdep_edge *edge = NULL; uintptr_t *cycle = NULL; uintptr_t *p = NULL; uintptr_t from = 0; uintptr_t to = 0; cycle = lockdep_graph_get_shortest_cycle(node); assert(cycle && cycle[0]); EMSG_RAW("-> Shortest cycle:"); for (p = cycle; *p; p++) EMSG_RAW(" Lock %#" PRIxPTR, *p); for (p = cycle; ; p++) { if (!*p) { assert(p != cycle); from = to; to = cycle[0]; edge = lockdep_find_edge(graph, from, to); lockdep_print_edge_info(from, edge); break; } if (p != cycle) from = to; to = *p; if (p != cycle) { edge = lockdep_find_edge(graph, from, to); lockdep_print_edge_info(from, edge); } } free(cycle); } TEE_Result __lockdep_lock_acquire(struct lockdep_node_head *graph, struct lockdep_lock_head *owned, uintptr_t id) { struct lockdep_node *node = lockdep_add_to_graph(graph, id); if (!node) return TEE_ERROR_OUT_OF_MEMORY; struct lockdep_lock *lock = NULL; vaddr_t *acq_stack = unw_get_kernel_stack(); TAILQ_FOREACH(lock, owned, link) { TEE_Result res = lockdep_add_edge(lock->node, node, lock->call_stack, acq_stack, (uintptr_t)owned); if (res) return res; } TEE_Result res = lockdep_graph_sort(graph); if (res) { EMSG_RAW("Potential deadlock detected!"); EMSG_RAW("When trying to acquire lock %#" PRIxPTR, id); lockdep_print_cycle_info(graph, node); return res; } lock = calloc(1, sizeof(*lock)); if (!lock) return TEE_ERROR_OUT_OF_MEMORY; lock->node = node; lock->call_stack = acq_stack; TAILQ_INSERT_TAIL(owned, lock, link); return TEE_SUCCESS; } TEE_Result __lockdep_lock_release(struct lockdep_lock_head *owned, uintptr_t id) { struct lockdep_lock *lock = NULL; TAILQ_FOREACH_REVERSE(lock, owned, lockdep_lock_head, link) { if (lock->node->lock_id == id) { TAILQ_REMOVE(owned, lock, link); free(lock->call_stack); free(lock); return TEE_SUCCESS; } } EMSG_RAW("Thread %p does not own lock %#" PRIxPTR, (void *)owned, id); return TEE_ERROR_ITEM_NOT_FOUND; } static void lockdep_node_delete(struct lockdep_node *node) { struct lockdep_edge *edge = NULL; struct lockdep_edge *next = NULL; STAILQ_FOREACH_SAFE(edge, &node->edges, link, next) { free(edge->call_stack_from); free(edge->call_stack_to); free(edge); } free(node); } void lockdep_graph_delete(struct lockdep_node_head *graph) { struct lockdep_node *node = NULL; struct lockdep_node *next = NULL; TAILQ_FOREACH_SAFE(node, graph, link, next) { TAILQ_REMOVE(graph, node, link); lockdep_node_delete(node); } } void lockdep_queue_delete(struct lockdep_lock_head *owned) { struct lockdep_lock *lock = NULL; struct lockdep_lock *next = NULL; TAILQ_FOREACH_SAFE(lock, owned, link, next) { TAILQ_REMOVE(owned, lock, link); free(lock); } }