/* * Locking overview * * There are 3 main spinlocks which must be acquired in the * order shown: * * 1) proc->outer_lock : protects binder_ref * binder_proc_lock() and binder_proc_unlock() are * used to acq/rel. * 2) node->lock : protects most fields of binder_node. * binder_node_lock() and binder_node_unlock() are * used to acq/rel * 3) proc->inner_lock : protects the thread and node lists * (proc->threads, proc->waiting_threads, proc->nodes) * and all todo lists associated with the binder_proc * (proc->todo, thread->todo, proc->delivered_death and * node->async_todo), as well as thread->transaction_stack * binder_inner_proc_lock() and binder_inner_proc_unlock() * are used to acq/rel * * Any lock under procA must never be nested under any lock at the same * level or below on procB. * * Functions that require a lock held on entry indicate which lock * in the suffix of the function name: * * foo_olocked() : requires node->outer_lock * foo_nlocked() : requires node->lock * foo_ilocked() : requires proc->inner_lock * foo_oilocked(): requires proc->outer_lock and proc->inner_lock * foo_nilocked(): requires node->lock and proc->inner_lock * ...
*/
struct binder_transaction_log_entry { int debug_id; int debug_id_done; int call_type; int from_proc; int from_thread; int target_handle; int to_proc; int to_thread; int to_node; int data_size; int offsets_size; int return_error_line;
uint32_t return_error;
uint32_t return_error_param; char context_name[BINDERFS_MAX_NAME + 1];
};
if (cur >= ARRAY_SIZE(log->entry))
log->full = true;
e = &log->entry[cur % ARRAY_SIZE(log->entry)];
WRITE_ONCE(e->debug_id_done, 0); /* * write-barrier to synchronize access to e->debug_id_done. * We make sure the initialized 0 value is seen before * memset() other fields are zeroed by memset.
*/
smp_wmb();
memset(e, 0, sizeof(*e)); return e;
}
/** * binder_worklist_empty() - Check if no items on the work list * @proc: binder_proc associated with list * @list: list to check * * Return: true if there are no items on list, else false
*/ staticbool binder_worklist_empty(struct binder_proc *proc, struct list_head *list)
{ bool ret;
binder_inner_proc_lock(proc);
ret = binder_worklist_empty_ilocked(list);
binder_inner_proc_unlock(proc); return ret;
}
/** * binder_enqueue_work_ilocked() - Add an item to the work list * @work: struct binder_work to add to list * @target_list: list to add work to * * Adds the work to the specified list. Asserts that work * is not already on a list. * * Requires the proc->inner_lock to be held.
*/ staticvoid
binder_enqueue_work_ilocked(struct binder_work *work, struct list_head *target_list)
{
BUG_ON(target_list == NULL);
BUG_ON(work->entry.next && !list_empty(&work->entry));
list_add_tail(&work->entry, target_list);
}
/** * binder_enqueue_deferred_thread_work_ilocked() - Add deferred thread work * @thread: thread to queue work to * @work: struct binder_work to add to list * * Adds the work to the todo list of the thread. Doesn't set the process_todo * flag, which means that (if it wasn't already set) the thread will go to * sleep without handling this work when it calls read. * * Requires the proc->inner_lock to be held.
*/ staticvoid
binder_enqueue_deferred_thread_work_ilocked(struct binder_thread *thread, struct binder_work *work)
{
WARN_ON(!list_empty(&thread->waiting_thread_node));
binder_enqueue_work_ilocked(work, &thread->todo);
}
/** * binder_enqueue_thread_work_ilocked() - Add an item to the thread work list * @thread: thread to queue work to * @work: struct binder_work to add to list * * Adds the work to the todo list of the thread, and enables processing * of the todo queue. * * Requires the proc->inner_lock to be held.
*/ staticvoid
binder_enqueue_thread_work_ilocked(struct binder_thread *thread, struct binder_work *work)
{
WARN_ON(!list_empty(&thread->waiting_thread_node));
binder_enqueue_work_ilocked(work, &thread->todo);
/* (e)poll-based threads require an explicit wakeup signal when * queuing their own work; they rely on these events to consume * messages without I/O block. Without it, threads risk waiting * indefinitely without handling the work.
*/ if (thread->looper & BINDER_LOOPER_STATE_POLL &&
thread->pid == current->pid && !thread->process_todo)
wake_up_interruptible_sync(&thread->wait);
thread->process_todo = true;
}
/** * binder_enqueue_thread_work() - Add an item to the thread work list * @thread: thread to queue work to * @work: struct binder_work to add to list * * Adds the work to the todo list of the thread, and enables processing * of the todo queue.
*/ staticvoid
binder_enqueue_thread_work(struct binder_thread *thread, struct binder_work *work)
{
binder_inner_proc_lock(thread->proc);
binder_enqueue_thread_work_ilocked(thread, work);
binder_inner_proc_unlock(thread->proc);
}
/** * binder_dequeue_work() - Removes an item from the work list * @proc: binder_proc associated with list * @work: struct binder_work to remove from list * * Removes the specified work item from whatever list it is on. * Can safely be called if work is not on any list.
*/ staticvoid
binder_dequeue_work(struct binder_proc *proc, struct binder_work *work)
{
binder_inner_proc_lock(proc);
binder_dequeue_work_ilocked(work);
binder_inner_proc_unlock(proc);
}
for (n = rb_first(&proc->threads); n != NULL; n = rb_next(n)) {
thread = rb_entry(n, struct binder_thread, rb_node); if (thread->looper & BINDER_LOOPER_STATE_POLL &&
binder_available_for_proc_work_ilocked(thread)) { if (sync)
wake_up_interruptible_sync(&thread->wait); else
wake_up_interruptible(&thread->wait);
}
}
}
/** * binder_select_thread_ilocked() - selects a thread for doing proc work. * @proc: process to select a thread from * * Note that calling this function moves the thread off the waiting_threads * list, so it can only be woken up by the caller of this function, or a * signal. Therefore, callers *should* always wake up the thread this function * returns. * * Return: If there's a thread currently waiting for process work, * returns that thread. Otherwise returns NULL.
*/ staticstruct binder_thread *
binder_select_thread_ilocked(struct binder_proc *proc)
{ struct binder_thread *thread;
if (thread)
list_del_init(&thread->waiting_thread_node);
return thread;
}
/** * binder_wakeup_thread_ilocked() - wakes up a thread for doing proc work. * @proc: process to wake up a thread in * @thread: specific thread to wake-up (may be NULL) * @sync: whether to do a synchronous wake-up * * This function wakes up a thread in the @proc process. * The caller may provide a specific thread to wake-up in * the @thread parameter. If @thread is NULL, this function * will wake up threads that have called poll(). * * Note that for this function to work as expected, callers * should first call binder_select_thread() to find a thread * to handle the work (if they don't have a thread already), * and pass the result into the @thread parameter.
*/ staticvoid binder_wakeup_thread_ilocked(struct binder_proc *proc, struct binder_thread *thread, bool sync)
{
assert_spin_locked(&proc->inner_lock);
if (thread) { if (sync)
wake_up_interruptible_sync(&thread->wait); else
wake_up_interruptible(&thread->wait); return;
}
/* Didn't find a thread waiting for proc work; this can happen * in two scenarios: * 1. All threads are busy handling transactions * In that case, one of those threads should call back into * the kernel driver soon and pick up this work. * 2. Threads are using the (e)poll interface, in which case * they may be blocked on the waitqueue without having been * added to waiting_threads. For this case, we just iterate * over all threads not handling transaction work, and * wake them all up. We wake all because we don't know whether * a thread that called into (e)poll is handling non-binder * work currently.
*/
binder_wakeup_poll_threads_ilocked(proc, sync);
}
if (!new_node) return NULL;
binder_inner_proc_lock(proc);
node = binder_init_node_ilocked(proc, new_node, fp);
binder_inner_proc_unlock(proc); if (node != new_node) /* * The node was already added by another thread
*/
kfree(new_node);
staticvoid binder_inc_node_tmpref_ilocked(struct binder_node *node)
{ /* * No call to binder_inc_node() is needed since we * don't need to inform userspace of any changes to * tmp_refs
*/
node->tmp_refs++;
}
/** * binder_inc_node_tmpref() - take a temporary reference on node * @node: node to reference * * Take reference on node to prevent the node from being freed * while referenced only by a local variable. The inner lock is * needed to serialize with the node work on the queue (which * isn't needed after the node is dead). If the node is dead * (node->proc is NULL), use binder_dead_nodes_lock to protect * node->tmp_refs against dead-node-only cases where the node * lock cannot be acquired (eg traversing the dead node list to * print nodes)
*/ staticvoid binder_inc_node_tmpref(struct binder_node *node)
{
binder_node_lock(node); if (node->proc)
binder_inner_proc_lock(node->proc); else
spin_lock(&binder_dead_nodes_lock);
binder_inc_node_tmpref_ilocked(node); if (node->proc)
binder_inner_proc_unlock(node->proc); else
spin_unlock(&binder_dead_nodes_lock);
binder_node_unlock(node);
}
/** * binder_dec_node_tmpref() - remove a temporary reference on node * @node: node to reference * * Release temporary reference on node taken via binder_inc_node_tmpref()
*/ staticvoid binder_dec_node_tmpref(struct binder_node *node)
{ bool free_node;
binder_node_inner_lock(node); if (!node->proc)
spin_lock(&binder_dead_nodes_lock); else
__acquire(&binder_dead_nodes_lock);
node->tmp_refs--;
BUG_ON(node->tmp_refs < 0); if (!node->proc)
spin_unlock(&binder_dead_nodes_lock); else
__release(&binder_dead_nodes_lock); /* * Call binder_dec_node() to check if all refcounts are 0 * and cleanup is needed. Calling with strong=0 and internal=1 * causes no actual reference to be released in binder_dec_node(). * If that changes, a change is needed here too.
*/
free_node = binder_dec_node_nilocked(node, 0, 1);
binder_node_inner_unlock(node); if (free_node)
binder_free_node(node);
}
desc = offset; for (n = rb_first(&proc->refs_by_desc); n; n = rb_next(n)) {
ref = rb_entry(n, struct binder_ref, rb_node_desc); if (ref->data.desc > desc) break;
desc = ref->data.desc + 1;
}
return desc;
}
/* * Find an available reference descriptor ID. The proc->outer_lock might * be released in the process, in which case -EAGAIN is returned and the * @desc should be considered invalid.
*/ staticint get_ref_desc_olocked(struct binder_proc *proc, struct binder_node *node,
u32 *desc)
{ struct dbitmap *dmap = &proc->dmap; unsignedint nbits, offset; unsignedlong *new, bit;
/* 0 is reserved for the context manager */
offset = (node == proc->context->binder_context_mgr_node) ? 0 : 1;
if (!dbitmap_enabled(dmap)) {
*desc = slow_desc_lookup_olocked(proc, offset); return 0;
}
/* * The dbitmap is full and needs to grow. The proc->outer_lock * is briefly released to allocate the new bitmap safely.
*/
nbits = dbitmap_grow_nbits(dmap);
binder_proc_unlock(proc); new = bitmap_zalloc(nbits, GFP_KERNEL);
binder_proc_lock(proc);
dbitmap_grow(dmap, new, nbits);
return -EAGAIN;
}
/** * binder_get_ref_for_node_olocked() - get the ref associated with given node * @proc: binder_proc that owns the ref * @node: binder_node of target * @new_ref: newly allocated binder_ref to be initialized or %NULL * * Look up the ref for the given node and return it if it exists * * If it doesn't exist and the caller provides a newly allocated * ref, initialize the fields of the newly allocated ref and insert * into the given proc rb_trees and node refs list. * * Return: the ref for node. It is possible that another thread * allocated/initialized the ref first in which case the * returned ref would be different than the passed-in * new_ref. new_ref must be kfree'd by the caller in * this case.
*/ staticstruct binder_ref *binder_get_ref_for_node_olocked( struct binder_proc *proc, struct binder_node *node, struct binder_ref *new_ref)
{ struct binder_ref *ref; struct rb_node *parent; struct rb_node **p;
u32 desc;
retry:
p = &proc->refs_by_node.rb_node;
parent = NULL; while (*p) {
parent = *p;
ref = rb_entry(parent, struct binder_ref, rb_node_node);
if (node < ref->node)
p = &(*p)->rb_left; elseif (node > ref->node)
p = &(*p)->rb_right; else return ref;
} if (!new_ref) return NULL;
/* might release the proc->outer_lock */ if (get_ref_desc_olocked(proc, node, &desc) == -EAGAIN) goto retry;
if (dbitmap_enabled(dmap))
dbitmap_clear_bit(dmap, ref->data.desc);
rb_erase(&ref->rb_node_desc, &ref->proc->refs_by_desc);
rb_erase(&ref->rb_node_node, &ref->proc->refs_by_node);
binder_node_inner_lock(ref->node); if (ref->data.strong)
binder_dec_node_nilocked(ref->node, 1, 1);
hlist_del(&ref->node_entry);
delete_node = binder_dec_node_nilocked(ref->node, 0, 1);
binder_node_inner_unlock(ref->node); /* * Clear ref->node unless we want the caller to free the node
*/ if (!delete_node) { /* * The caller uses ref->node to determine * whether the node needs to be freed. Clear * it since the node is still alive.
*/
ref->node = NULL;
}
if (ref->death) {
binder_debug(BINDER_DEBUG_DEAD_BINDER, "%d delete ref %d desc %d has death notification\n",
ref->proc->pid, ref->data.debug_id,
ref->data.desc);
binder_dequeue_work(ref->proc, &ref->death->work);
binder_stats_deleted(BINDER_STAT_DEATH);
}
if (ref->freeze) {
binder_dequeue_work(ref->proc, &ref->freeze->work);
binder_stats_deleted(BINDER_STAT_FREEZE);
}
binder_stats_deleted(BINDER_STAT_REF);
}
/** * binder_inc_ref_olocked() - increment the ref for given handle * @ref: ref to be incremented * @strong: if true, strong increment, else weak * @target_list: list to queue node work on * * Increment the ref. @ref->proc->outer_lock must be held on entry * * Return: 0, if successful, else errno
*/ staticint binder_inc_ref_olocked(struct binder_ref *ref, int strong, struct list_head *target_list)
{ int ret;
if (strong) { if (ref->data.strong == 0) {
ret = binder_inc_node(ref->node, 1, 1, target_list); if (ret) return ret;
}
ref->data.strong++;
} else { if (ref->data.weak == 0) {
ret = binder_inc_node(ref->node, 0, 1, target_list); if (ret) return ret;
}
ref->data.weak++;
} return 0;
}
/** * binder_dec_ref_olocked() - dec the ref for given handle * @ref: ref to be decremented * @strong: if true, strong decrement, else weak * * Decrement the ref. * * Return: %true if ref is cleaned up and ready to be freed.
*/ staticbool binder_dec_ref_olocked(struct binder_ref *ref, int strong)
{ if (strong) { if (ref->data.strong == 0) {
binder_user_error("%d invalid dec strong, ref %d desc %d s %d w %d\n",
ref->proc->pid, ref->data.debug_id,
ref->data.desc, ref->data.strong,
ref->data.weak); returnfalse;
}
ref->data.strong--; if (ref->data.strong == 0)
binder_dec_node(ref->node, strong, 1);
} else { if (ref->data.weak == 0) {
binder_user_error("%d invalid dec weak, ref %d desc %d s %d w %d\n",
ref->proc->pid, ref->data.debug_id,
ref->data.desc, ref->data.strong,
ref->data.weak); returnfalse;
}
ref->data.weak--;
} if (ref->data.strong == 0 && ref->data.weak == 0) {
binder_cleanup_ref_olocked(ref); returntrue;
} returnfalse;
}
/** * binder_get_node_from_ref() - get the node from the given proc/desc * @proc: proc containing the ref * @desc: the handle associated with the ref * @need_strong_ref: if true, only return node if ref is strong * @rdata: the id/refcount data for the ref * * Given a proc and ref handle, return the associated binder_node * * Return: a binder_node or NULL if not found or not strong when strong required
*/ staticstruct binder_node *binder_get_node_from_ref( struct binder_proc *proc,
u32 desc, bool need_strong_ref, struct binder_ref_data *rdata)
{ struct binder_node *node; struct binder_ref *ref;
binder_proc_lock(proc);
ref = binder_get_ref_olocked(proc, desc, need_strong_ref); if (!ref) goto err_no_ref;
node = ref->node; /* * Take an implicit reference on the node to ensure * it stays alive until the call to binder_put_node()
*/
binder_inc_node_tmpref(node); if (rdata)
*rdata = ref->data;
binder_proc_unlock(proc);
/** * binder_free_ref() - free the binder_ref * @ref: ref to free * * Free the binder_ref. Free the binder_node indicated by ref->node * (if non-NULL) and the binder_ref_death indicated by ref->death.
*/ staticvoid binder_free_ref(struct binder_ref *ref)
{ if (ref->node)
binder_free_node(ref->node);
kfree(ref->death);
kfree(ref->freeze);
kfree(ref);
}
/* shrink descriptor bitmap if needed */ staticvoid try_shrink_dmap(struct binder_proc *proc)
{ unsignedlong *new; int nbits;
new = bitmap_zalloc(nbits, GFP_KERNEL);
binder_proc_lock(proc);
dbitmap_shrink(&proc->dmap, new, nbits);
binder_proc_unlock(proc);
}
/** * binder_update_ref_for_handle() - inc/dec the ref for given handle * @proc: proc containing the ref * @desc: the handle associated with the ref * @increment: true=inc reference, false=dec reference * @strong: true=strong reference, false=weak reference * @rdata: the id/refcount data for the ref * * Given a proc and ref handle, increment or decrement the ref * according to "increment" arg. * * Return: 0 if successful, else errno
*/ staticint binder_update_ref_for_handle(struct binder_proc *proc,
uint32_t desc, bool increment, bool strong, struct binder_ref_data *rdata)
{ int ret = 0; struct binder_ref *ref; bool delete_ref = false;
binder_proc_lock(proc);
ref = binder_get_ref_olocked(proc, desc, strong); if (!ref) {
ret = -EINVAL; goto err_no_ref;
} if (increment)
ret = binder_inc_ref_olocked(ref, strong, NULL); else
delete_ref = binder_dec_ref_olocked(ref, strong);
if (rdata)
*rdata = ref->data;
binder_proc_unlock(proc);
if (delete_ref) {
binder_free_ref(ref);
try_shrink_dmap(proc);
} return ret;
/** * binder_dec_ref_for_handle() - dec the ref for given handle * @proc: proc containing the ref * @desc: the handle associated with the ref * @strong: true=strong reference, false=weak reference * @rdata: the id/refcount data for the ref * * Just calls binder_update_ref_for_handle() to decrement the ref. * * Return: 0 if successful, else errno
*/ staticint binder_dec_ref_for_handle(struct binder_proc *proc,
uint32_t desc, bool strong, struct binder_ref_data *rdata)
{ return binder_update_ref_for_handle(proc, desc, false, strong, rdata);
}
/** * binder_inc_ref_for_node() - increment the ref for given proc/node * @proc: proc containing the ref * @node: target node * @strong: true=strong reference, false=weak reference * @target_list: worklist to use if node is incremented * @rdata: the id/refcount data for the ref * * Given a proc and node, increment the ref. Create the ref if it * doesn't already exist * * Return: 0 if successful, else errno
*/ staticint binder_inc_ref_for_node(struct binder_proc *proc, struct binder_node *node, bool strong, struct list_head *target_list, struct binder_ref_data *rdata)
{ struct binder_ref *ref; struct binder_ref *new_ref = NULL; int ret = 0;
binder_proc_lock(proc);
ref = binder_get_ref_for_node_olocked(proc, node, NULL); if (!ref) {
binder_proc_unlock(proc);
new_ref = kzalloc(sizeof(*ref), GFP_KERNEL); if (!new_ref) return -ENOMEM;
binder_proc_lock(proc);
ref = binder_get_ref_for_node_olocked(proc, node, new_ref);
}
ret = binder_inc_ref_olocked(ref, strong, target_list);
*rdata = ref->data; if (ret && ref == new_ref) { /* * Cleanup the failed reference here as the target * could now be dead and have already released its * references by now. Calling on the new reference * with strong=0 and a tmp_refs will not decrement * the node. The new_ref gets kfree'd below.
*/
binder_cleanup_ref_olocked(new_ref);
ref = NULL;
}
binder_proc_unlock(proc); if (new_ref && ref != new_ref) /* * Another thread created the ref first so * free the one we allocated
*/
kfree(new_ref); return ret;
}
/** * binder_thread_dec_tmpref() - decrement thread->tmp_ref * @thread: thread to decrement * * A thread needs to be kept alive while being used to create or * handle a transaction. binder_get_txn_from() is used to safely * extract t->from from a binder_transaction and keep the thread * indicated by t->from from being freed. When done with that * binder_thread, this function is called to decrement the * tmp_ref and free if appropriate (thread has been released * and no transaction being processed by the driver)
*/ staticvoid binder_thread_dec_tmpref(struct binder_thread *thread)
{ /* * atomic is used to protect the counter value while * it cannot reach zero or thread->is_dead is false
*/
binder_inner_proc_lock(thread->proc);
atomic_dec(&thread->tmp_ref); if (thread->is_dead && !atomic_read(&thread->tmp_ref)) {
binder_inner_proc_unlock(thread->proc);
binder_free_thread(thread); return;
}
binder_inner_proc_unlock(thread->proc);
}
/** * binder_proc_dec_tmpref() - decrement proc->tmp_ref * @proc: proc to decrement * * A binder_proc needs to be kept alive while being used to create or * handle a transaction. proc->tmp_ref is incremented when * creating a new transaction or the binder_proc is currently in-use * by threads that are being released. When done with the binder_proc, * this function is called to decrement the counter and free the * proc if appropriate (proc has been released, all threads have * been released and not currently in-use to process a transaction).
*/ staticvoid binder_proc_dec_tmpref(struct binder_proc *proc)
{
binder_inner_proc_lock(proc);
proc->tmp_ref--; if (proc->is_dead && RB_EMPTY_ROOT(&proc->threads) &&
!proc->tmp_ref) {
binder_inner_proc_unlock(proc);
binder_free_proc(proc); return;
}
binder_inner_proc_unlock(proc);
}
/** * binder_get_txn_from() - safely extract the "from" thread in transaction * @t: binder transaction for t->from * * Atomically return the "from" thread and increment the tmp_ref * count for the thread to ensure it stays alive until * binder_thread_dec_tmpref() is called. * * Return: the value of t->from
*/ staticstruct binder_thread *binder_get_txn_from( struct binder_transaction *t)
{ struct binder_thread *from;
guard(spinlock)(&t->lock);
from = t->from; if (from)
atomic_inc(&from->tmp_ref); return from;
}
/** * binder_get_txn_from_and_acq_inner() - get t->from and acquire inner lock * @t: binder transaction for t->from * * Same as binder_get_txn_from() except it also acquires the proc->inner_lock * to guarantee that the thread cannot be released while operating on it. * The caller must call binder_inner_proc_unlock() to release the inner lock * as well as call binder_dec_thread_txn() to release the reference. * * Return: the value of t->from
*/ staticstruct binder_thread *binder_get_txn_from_and_acq_inner( struct binder_transaction *t)
__acquires(&t->from->proc->inner_lock)
{ struct binder_thread *from;
from = binder_get_txn_from(t); if (!from) {
__acquire(&from->proc->inner_lock); return NULL;
}
binder_inner_proc_lock(from->proc); if (t->from) {
BUG_ON(from != t->from); return from;
}
binder_inner_proc_unlock(from->proc);
__acquire(&from->proc->inner_lock);
binder_thread_dec_tmpref(from); return NULL;
}
/** * binder_free_txn_fixups() - free unprocessed fd fixups * @t: binder transaction for t->from * * If the transaction is being torn down prior to being * processed by the target process, free all of the * fd fixups and fput the file structs. It is safe to * call this function after the fixups have been * processed -- in that case, the list will be empty.
*/ staticvoid binder_free_txn_fixups(struct binder_transaction *t)
{ struct binder_txn_fd_fixup *fixup, *tmp;
if (target_proc) {
binder_inner_proc_lock(target_proc);
target_proc->outstanding_txns--; if (target_proc->outstanding_txns < 0)
pr_warn("%s: Unexpected outstanding_txns %d\n",
__func__, target_proc->outstanding_txns); if (!target_proc->outstanding_txns && target_proc->is_frozen)
wake_up_interruptible_all(&target_proc->freeze_wait); if (t->buffer)
t->buffer->transaction = NULL;
binder_inner_proc_unlock(target_proc);
} if (trace_binder_txn_latency_free_enabled())
binder_txn_latency_free(t); /* * If the transaction has no target_proc, then * t->buffer->transaction has already been cleared.
*/
binder_free_txn_fixups(t);
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
BUG_ON(t->flags & TF_ONE_WAY); while (1) {
target_thread = binder_get_txn_from_and_acq_inner(t); if (target_thread) {
binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, "send failed reply for transaction %d to %d:%d\n",
t->debug_id,
target_thread->proc->pid,
target_thread->pid);
binder_pop_transaction_ilocked(target_thread, t); if (target_thread->reply_error.cmd == BR_OK) {
target_thread->reply_error.cmd = error_code;
binder_enqueue_thread_work_ilocked(
target_thread,
&target_thread->reply_error.work);
wake_up_interruptible(&target_thread->wait);
} else { /* * Cannot get here for normal operation, but * we can if multiple synchronous transactions * are sent without blocking for responses. * Just ignore the 2nd error in this case.
*/
pr_warn("Unexpected reply error: %u\n",
target_thread->reply_error.cmd);
}
binder_inner_proc_unlock(target_thread->proc);
binder_thread_dec_tmpref(target_thread);
binder_free_transaction(t); return;
}
__release(&target_thread->proc->inner_lock);
next = t->from_parent;
binder_debug(BINDER_DEBUG_FAILED_TRANSACTION, "send failed reply for transaction %d, target dead\n",
t->debug_id);
binder_free_transaction(t); if (next == NULL) {
binder_debug(BINDER_DEBUG_DEAD_BINDER, "reply failed, no target thread at root\n"); return;
}
t = next;
binder_debug(BINDER_DEBUG_DEAD_BINDER, "reply failed, no target thread -- retry %d\n",
t->debug_id);
}
}
/** * binder_cleanup_transaction() - cleans up undelivered transaction * @t: transaction that needs to be cleaned up * @reason: reason the transaction wasn't delivered * @error_code: error to return to caller (if synchronous call)
*/ staticvoid binder_cleanup_transaction(struct binder_transaction *t, constchar *reason,
uint32_t error_code)
{ if (t->buffer->target_node && !(t->flags & TF_ONE_WAY)) {
binder_send_failed_reply(t, error_code);
} else {
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION, "undelivered transaction %d, %s\n",
t->debug_id, reason);
binder_free_transaction(t);
}
}
/** * binder_get_object() - gets object and checks for valid metadata * @proc: binder_proc owning the buffer * @u: sender's user pointer to base of buffer * @buffer: binder_buffer that we're parsing. * @offset: offset in the @buffer at which to validate an object. * @object: struct binder_object to read into * * Copy the binder object at the given offset into @object. If @u is * provided then the copy is from the sender's buffer. If not, then * it is copied from the target's @buffer. * * Return: If there's a valid metadata object at @offset, the * size of that object. Otherwise, it returns zero. The object * is read into the struct binder_object pointed to by @object.
*/ static size_t binder_get_object(struct binder_proc *proc, constvoid __user *u, struct binder_buffer *buffer, unsignedlong offset, struct binder_object *object)
{
size_t read_size; struct binder_object_header *hdr;
size_t object_size = 0;
if (u) { if (copy_from_user(object, u + offset, read_size)) return 0;
} else { if (binder_alloc_copy_from_buffer(&proc->alloc, object, buffer,
offset, read_size)) return 0;
}
/* Ok, now see if we read a complete object. */
hdr = &object->hdr; switch (hdr->type) { case BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE:
object_size = sizeof(struct flat_binder_object); break; case BINDER_TYPE_FD:
object_size = sizeof(struct binder_fd_object); break; case BINDER_TYPE_PTR:
object_size = sizeof(struct binder_buffer_object); break; case BINDER_TYPE_FDA:
object_size = sizeof(struct binder_fd_array_object); break; default: return 0;
} if (offset <= buffer->data_size - object_size &&
buffer->data_size >= object_size) return object_size; else return 0;
}
/** * binder_validate_ptr() - validates binder_buffer_object in a binder_buffer. * @proc: binder_proc owning the buffer * @b: binder_buffer containing the object * @object: struct binder_object to read into * @index: index in offset array at which the binder_buffer_object is * located * @start_offset: points to the start of the offset array * @object_offsetp: offset of @object read from @b * @num_valid: the number of valid offsets in the offset array * * Return: If @index is within the valid range of the offset array * described by @start and @num_valid, and if there's a valid * binder_buffer_object at the offset found in index @index * of the offset array, that object is returned. Otherwise, * %NULL is returned. * Note that the offset found in index @index itself is not * verified; this function assumes that @num_valid elements * from @start were previously verified to have valid offsets. * If @object_offsetp is non-NULL, then the offset within * @b is written to it.
*/ staticstruct binder_buffer_object *binder_validate_ptr( struct binder_proc *proc, struct binder_buffer *b, struct binder_object *object,
binder_size_t index,
binder_size_t start_offset,
binder_size_t *object_offsetp,
binder_size_t num_valid)
{
size_t object_size;
binder_size_t object_offset; unsignedlong buffer_offset;
/** * binder_validate_fixup() - validates pointer/fd fixups happen in order. * @proc: binder_proc owning the buffer * @b: transaction buffer * @objects_start_offset: offset to start of objects buffer * @buffer_obj_offset: offset to binder_buffer_object in which to fix up * @fixup_offset: start offset in @buffer to fix up * @last_obj_offset: offset to last binder_buffer_object that we fixed * @last_min_offset: minimum fixup offset in object at @last_obj_offset * * Return: %true if a fixup in buffer @buffer at offset @offset is * allowed. * * For safety reasons, we only allow fixups inside a buffer to happen * at increasing offsets; additionally, we only allow fixup on the last * buffer object that was verified, or one of its parents. * * Example of what is allowed: * * A * B (parent = A, offset = 0) * C (parent = A, offset = 16) * D (parent = C, offset = 0) * E (parent = A, offset = 32) // min_offset is 16 (C.parent_offset) * * Examples of what is not allowed: * * Decreasing offsets within the same parent: * A * C (parent = A, offset = 16) * B (parent = A, offset = 0) // decreasing offset within A * * Referring to a parent that wasn't the last object or any of its parents: * A * B (parent = A, offset = 0) * C (parent = A, offset = 0) * C (parent = A, offset = 16) * D (parent = B, offset = 0) // B is not A or any of A's parents
*/ staticbool binder_validate_fixup(struct binder_proc *proc, struct binder_buffer *b,
binder_size_t objects_start_offset,
binder_size_t buffer_obj_offset,
binder_size_t fixup_offset,
binder_size_t last_obj_offset,
binder_size_t last_min_offset)
{ if (!last_obj_offset) { /* Nothing to fix up in */ returnfalse;
}
last_bbo = &last_object.bbo; /* * Safe to retrieve the parent of last_obj, since it * was already previously verified by the driver.
*/ if ((last_bbo->flags & BINDER_BUFFER_FLAG_HAS_PARENT) == 0) returnfalse;
last_min_offset = last_bbo->parent_offset + sizeof(uintptr_t);
buffer_offset = objects_start_offset + sizeof(binder_size_t) * last_bbo->parent; if (binder_alloc_copy_from_buffer(&proc->alloc,
&last_obj_offset,
b, buffer_offset, sizeof(last_obj_offset))) returnfalse;
} return (fixup_offset >= last_min_offset);
}
/** * struct binder_task_work_cb - for deferred close * * @twork: callback_head for task work * @file: file to close * * Structure to pass task work to be handled after * returning from binder_ioctl() via task_work_add().
*/ struct binder_task_work_cb { struct callback_head twork; struct file *file;
};
/** * binder_do_fd_close() - close list of file descriptors * @twork: callback head for task work * * It is not safe to call ksys_close() during the binder_ioctl() * function if there is a chance that binder's own file descriptor * might be closed. This is to meet the requirements for using * fdget() (see comments for __fget_light()). Therefore use * task_work_add() to schedule the close operation once we have * returned from binder_ioctl(). This function is a callback * for that mechanism and does the actual ksys_close() on the * given file descriptor.
*/ staticvoid binder_do_fd_close(struct callback_head *twork)
{ struct binder_task_work_cb *twcb = container_of(twork, struct binder_task_work_cb, twork);
fput(twcb->file);
kfree(twcb);
}
/** * binder_deferred_fd_close() - schedule a close for the given file-descriptor * @fd: file-descriptor to close * * See comments in binder_do_fd_close(). This function is used to schedule * a file-descriptor to be closed after returning from binder_ioctl().
*/ staticvoid binder_deferred_fd_close(int fd)
{ struct binder_task_work_cb *twcb;
twcb = kzalloc(sizeof(*twcb), GFP_KERNEL); if (!twcb) return;
init_task_work(&twcb->twork, binder_do_fd_close);
twcb->file = file_close_fd(fd); if (twcb->file) { // pin it until binder_do_fd_close(); see comments there
get_file(twcb->file);
filp_close(twcb->file, current->files);
task_work_add(current, &twcb->twork, TWA_RESUME);
} else {
kfree(twcb);
}
}
if (ret) {
pr_err("transaction release %d bad handle %d, ret = %d\n",
debug_id, fp->handle, ret); break;
}
binder_debug(BINDER_DEBUG_TRANSACTION, " ref %d desc %d\n",
rdata.debug_id, rdata.desc);
} break;
case BINDER_TYPE_FD: { /* * No need to close the file here since user-space * closes it for successfully delivered * transactions. For transactions that weren't * delivered, the new fd was never allocated so * there is no need to close and the fput on the * file is done when the transaction is torn * down.
*/
} break; case BINDER_TYPE_PTR: /* * Nothing to do here, this will get cleaned up when the * transaction buffer gets freed
*/ break; case BINDER_TYPE_FDA: { struct binder_fd_array_object *fda; struct binder_buffer_object *parent; struct binder_object ptr_object;
binder_size_t fda_offset;
size_t fd_index;
binder_size_t fd_buf_size;
binder_size_t num_valid;
if (is_failure) { /* * The fd fixups have not been applied so no * fds need to be closed.
*/ continue;
}
num_valid = (buffer_offset - off_start_offset) / sizeof(binder_size_t);
fda = to_binder_fd_array_object(hdr);
parent = binder_validate_ptr(proc, buffer, &ptr_object,
fda->parent,
off_start_offset,
NULL,
num_valid); if (!parent) {
pr_err("transaction release %d bad parent offset\n",
debug_id); continue;
}
fd_buf_size = sizeof(u32) * fda->num_fds; if (fda->num_fds >= SIZE_MAX / sizeof(u32)) {
pr_err("transaction release %d invalid number of fds (%lld)\n",
debug_id, (u64)fda->num_fds); continue;
} if (fd_buf_size > parent->length ||
fda->parent_offset > parent->length - fd_buf_size) { /* No space for all file descriptors here. */
pr_err("transaction release %d not enough space for %lld fds in buffer\n",
debug_id, (u64)fda->num_fds); continue;
} /* * the source data for binder_buffer_object is visible * to user-space and the @buffer element is the user * pointer to the buffer_object containing the fd_array. * Convert the address to an offset relative to * the base of the transaction buffer.
*/
fda_offset = parent->buffer - buffer->user_data +
fda->parent_offset; for (fd_index = 0; fd_index < fda->num_fds;
fd_index++) {
u32 fd; int err;
binder_size_t offset = fda_offset +
fd_index * sizeof(fd);
err = binder_alloc_copy_from_buffer(
&proc->alloc, &fd, buffer,
offset, sizeof(fd));
WARN_ON(err); if (!err) {
binder_deferred_fd_close(fd); /* * Need to make sure the thread goes * back to userspace to complete the * deferred close
*/ if (thread)
thread->looper_need_return = true;
}
}
} break; default:
pr_err("transaction release %d bad object type %x\n",
debug_id, hdr->type); break;
}
}
}
/* Clean up all the objects in the buffer */ staticinlinevoid binder_release_entire_buffer(struct binder_proc *proc, struct binder_thread *thread, struct binder_buffer *buffer, bool is_failure)
{
binder_size_t off_end_offset;
if (in_reply_to)
target_allows_fd = !!(in_reply_to->flags & TF_ACCEPT_FDS); else
target_allows_fd = t->buffer->target_node->accept_fds; if (!target_allows_fd) {
binder_user_error("%d:%d got %s with fd, %d, but target does not allow fds\n",
proc->pid, thread->pid,
in_reply_to ? "reply" : "transaction",
fd);
ret = -EPERM; goto err_fd_not_accepted;
}
file = fget(fd); if (!file) {
binder_user_error("%d:%d got transaction with invalid fd, %d\n",
proc->pid, thread->pid, fd);
ret = -EBADF; goto err_fget;
}
ret = security_binder_transfer_file(proc->cred, target_proc->cred, file); if (ret < 0) {
ret = -EPERM; goto err_security;
}
/* * Add fixup record for this transaction. The allocation * of the fd in the target needs to be done from a * target thread.
*/
fixup = kzalloc(sizeof(*fixup), GFP_KERNEL); if (!fixup) {
ret = -ENOMEM; goto err_alloc;
}
fixup->file = file;
fixup->offset = fd_offset;
fixup->target_fd = -1;
trace_binder_transaction_fd_send(t, fd, fixup->offset);
list_add_tail(&fixup->fixup_entry, &t->fd_fixups);
/** * struct binder_ptr_fixup - data to be fixed-up in target buffer * @offset offset in target buffer to fixup * @skip_size bytes to skip in copy (fixup will be written later) * @fixup_data data to write at fixup offset * @node list node * * This is used for the pointer fixup list (pf) which is created and consumed * during binder_transaction() and is only accessed locally. No * locking is necessary. * * The list is ordered by @offset.
*/ struct binder_ptr_fixup {
binder_size_t offset;
size_t skip_size;
binder_uintptr_t fixup_data; struct list_head node;
};
/** * struct binder_sg_copy - scatter-gather data to be copied * @offset offset in target buffer * @sender_uaddr user address in source buffer * @length bytes to copy * @node list node * * This is used for the sg copy list (sgc) which is created and consumed * during binder_transaction() and is only accessed locally. No * locking is necessary. * * The list is ordered by @offset.
*/ struct binder_sg_copy {
binder_size_t offset; constvoid __user *sender_uaddr;
size_t length; struct list_head node;
};
/** * binder_do_deferred_txn_copies() - copy and fixup scatter-gather data * @alloc: binder_alloc associated with @buffer * @buffer: binder buffer in target process * @sgc_head: list_head of scatter-gather copy list * @pf_head: list_head of pointer fixup list * * Processes all elements of @sgc_head, applying fixups from @pf_head * and copying the scatter-gather data from the source process' user * buffer to the target's buffer. It is expected that the list creation * and processing all occurs during binder_transaction() so these lists * are only accessed in local context. * * Return: 0=success, else -errno
*/ staticint binder_do_deferred_txn_copies(struct binder_alloc *alloc, struct binder_buffer *buffer, struct list_head *sgc_head, struct list_head *pf_head)
{ int ret = 0; struct binder_sg_copy *sgc, *tmpsgc; struct binder_ptr_fixup *tmppf; struct binder_ptr_fixup *pf =
list_first_entry_or_null(pf_head, struct binder_ptr_fixup,
node);
/* * We copy up to the fixup (pointed to by pf)
*/
copy_size = pf ? min(bytes_left, (size_t)pf->offset - offset)
: bytes_left; if (!ret && copy_size)
ret = binder_alloc_copy_user_to_buffer(
alloc, buffer,
offset,
sgc->sender_uaddr + bytes_copied,
copy_size);
bytes_copied += copy_size; if (copy_size != bytes_left) {
BUG_ON(!pf); /* we stopped at a fixup offset */ if (pf->skip_size) { /* * we are just skipping. This is for * BINDER_TYPE_FDA where the translated * fds will be fixed up when we get * to target context.
*/
bytes_copied += pf->skip_size;
} else { /* apply the fixup indicated by pf */ if (!ret)
ret = binder_alloc_copy_to_buffer(
alloc, buffer,
pf->offset,
&pf->fixup_data, sizeof(pf->fixup_data));
bytes_copied += sizeof(pf->fixup_data);
}
list_del(&pf->node);
kfree(pf);
pf = list_first_entry_or_null(pf_head, struct binder_ptr_fixup, node);
}
}
list_del(&sgc->node);
kfree(sgc);
}
list_for_each_entry_safe(pf, tmppf, pf_head, node) {
BUG_ON(pf->skip_size == 0);
list_del(&pf->node);
kfree(pf);
}
BUG_ON(!list_empty(sgc_head));
return ret > 0 ? -EINVAL : ret;
}
/** * binder_cleanup_deferred_txn_lists() - free specified lists * @sgc_head: list_head of scatter-gather copy list * @pf_head: list_head of pointer fixup list * * Called to clean up @sgc_head and @pf_head if there is an * error.
*/ staticvoid binder_cleanup_deferred_txn_lists(struct list_head *sgc_head, struct list_head *pf_head)
{ struct binder_sg_copy *sgc, *tmpsgc; struct binder_ptr_fixup *pf, *tmppf;
/** * binder_defer_copy() - queue a scatter-gather buffer for copy * @sgc_head: list_head of scatter-gather copy list * @offset: binder buffer offset in target process * @sender_uaddr: user address in source process * @length: bytes to copy * * Specify a scatter-gather block to be copied. The actual copy must * be deferred until all the needed fixups are identified and queued. * Then the copy and fixups are done together so un-translated values * from the source are never visible in the target buffer. * * We are guaranteed that repeated calls to this function will have * monotonically increasing @offset values so the list will naturally * be ordered. * * Return: 0=success, else -errno
*/ staticint binder_defer_copy(struct list_head *sgc_head, binder_size_t offset, constvoid __user *sender_uaddr, size_t length)
{ struct binder_sg_copy *bc = kzalloc(sizeof(*bc), GFP_KERNEL);
/* * We are guaranteed that the deferred copies are in-order * so just add to the tail.
*/
list_add_tail(&bc->node, sgc_head);
return 0;
}
/** * binder_add_fixup() - queue a fixup to be applied to sg copy * @pf_head: list_head of binder ptr fixup list * @offset: binder buffer offset in target process * @fixup: bytes to be copied for fixup * @skip_size: bytes to skip when copying (fixup will be applied later) * * Add the specified fixup to a list ordered by @offset. When copying * the scatter-gather buffers, the fixup will be copied instead of * data from the source buffer. For BINDER_TYPE_FDA fixups, the fixup * will be applied later (in target process context), so we just skip * the bytes specified by @skip_size. If @skip_size is 0, we copy the * value in @fixup. * * This function is called *mostly* in @offset order, but there are * exceptions. Since out-of-order inserts are relatively uncommon, * we insert the new element by searching backward from the tail of * the list. * * Return: 0=success, else -errno
*/ staticint binder_add_fixup(struct list_head *pf_head, binder_size_t offset,
binder_uintptr_t fixup, size_t skip_size)
{ struct binder_ptr_fixup *pf = kzalloc(sizeof(*pf), GFP_KERNEL); struct binder_ptr_fixup *tmppf;
/* Fixups are *mostly* added in-order, but there are some * exceptions. Look backwards through list for insertion point.
*/
list_for_each_entry_reverse(tmppf, pf_head, node) { if (tmppf->offset < pf->offset) {
list_add(&pf->node, &tmppf->node); return 0;
}
} /* * if we get here, then the new offset is the lowest so * insert at the head
*/
list_add(&pf->node, pf_head); return 0;
}
fd_buf_size = sizeof(u32) * fda->num_fds; if (fda->num_fds >= SIZE_MAX / sizeof(u32)) {
binder_user_error("%d:%d got transaction with invalid number of fds (%lld)\n",
proc->pid, thread->pid, (u64)fda->num_fds); return -EINVAL;
} if (fd_buf_size > parent->length ||
fda->parent_offset > parent->length - fd_buf_size) { /* No space for all file descriptors here. */
binder_user_error("%d:%d not enough space to store %lld fds in buffer\n",
proc->pid, thread->pid, (u64)fda->num_fds); return -EINVAL;
} /* * the source data for binder_buffer_object is visible * to user-space and the @buffer element is the user * pointer to the buffer_object containing the fd_array. * Convert the address to an offset relative to * the base of the transaction buffer.
*/
fda_offset = parent->buffer - t->buffer->user_data +
fda->parent_offset;
sender_ufda_base = (void __user *)(uintptr_t)sender_uparent->buffer +
fda->parent_offset;
if (!IS_ALIGNED((unsignedlong)fda_offset, sizeof(u32)) ||
!IS_ALIGNED((unsignedlong)sender_ufda_base, sizeof(u32))) {
binder_user_error("%d:%d parent offset not aligned correctly.\n",
proc->pid, thread->pid); return -EINVAL;
}
ret = binder_add_fixup(pf_head, fda_offset, 0, fda->num_fds * sizeof(u32)); if (ret) return ret;
/** * binder_can_update_transaction() - Can a txn be superseded by an updated one? * @t1: the pending async txn in the frozen process * @t2: the new async txn to supersede the outdated pending one * * Return: true if t2 can supersede t1 * false if t2 can not supersede t1
*/ staticbool binder_can_update_transaction(struct binder_transaction *t1, struct binder_transaction *t2)
{ if ((t1->flags & t2->flags & (TF_ONE_WAY | TF_UPDATE_TXN)) !=
(TF_ONE_WAY | TF_UPDATE_TXN) || !t1->to_proc || !t2->to_proc) returnfalse; if (t1->to_proc->tsk == t2->to_proc->tsk && t1->code == t2->code &&
t1->flags == t2->flags && t1->buffer->pid == t2->buffer->pid &&
t1->buffer->target_node->ptr == t2->buffer->target_node->ptr &&
t1->buffer->target_node->cookie == t2->buffer->target_node->cookie) returntrue; returnfalse;
}
/** * binder_find_outdated_transaction_ilocked() - Find the outdated transaction * @t: new async transaction * @target_list: list to find outdated transaction * * Return: the outdated transaction if found * NULL if no outdated transacton can be found * * Requires the proc->inner_lock to be held.
*/ staticstruct binder_transaction *
binder_find_outdated_transaction_ilocked(struct binder_transaction *t, struct list_head *target_list)
{ struct binder_work *w;
if (w->type != BINDER_WORK_TRANSACTION) continue;
t_queued = container_of(w, struct binder_transaction, work); if (binder_can_update_transaction(t_queued, t)) return t_queued;
} return NULL;
}
/** * binder_proc_transaction() - sends a transaction to a process and wakes it up * @t: transaction to send * @proc: process to send the transaction to * @thread: thread in @proc to send the transaction to (may be NULL) * * This function queues a transaction to the specified process. It will try * to find a thread in the target process to handle the transaction and * wake it up. If no thread is found, the work is queued to the proc * waitqueue. * * If the @thread parameter is not NULL, the transaction is always queued * to the waitlist of that specific thread. * * Return: 0 if the transaction was successfully queued * BR_DEAD_REPLY if the target process or thread is dead * BR_FROZEN_REPLY if the target process or thread is frozen and * the sync transaction was rejected * BR_TRANSACTION_PENDING_FROZEN if the target process is frozen * and the async transaction was successfully queued
*/ staticint binder_proc_transaction(struct binder_transaction *t, struct binder_proc *proc, struct binder_thread *thread)
{ struct binder_node *node = t->buffer->target_node; bool oneway = !!(t->flags & TF_ONE_WAY); bool pending_async = false; struct binder_transaction *t_outdated = NULL; bool frozen = false;
BUG_ON(!node);
binder_node_lock(node); if (oneway) {
BUG_ON(thread); if (node->has_async_transaction)
pending_async = true; else
node->has_async_transaction = true;
}
/* * To reduce potential contention, free the outdated transaction and * buffer after releasing the locks.
*/ if (t_outdated) { struct binder_buffer *buffer = t_outdated->buffer;
if (oneway && frozen) return BR_TRANSACTION_PENDING_FROZEN;
return 0;
}
/** * binder_get_node_refs_for_txn() - Get required refs on node for txn * @node: struct binder_node for which to get refs * @procp: returns @node->proc if valid * @error: if no @procp then returns BR_DEAD_REPLY * * User-space normally keeps the node alive when creating a transaction * since it has a reference to the target. The local strong ref keeps it * alive if the sending process dies before the target process processes * the transaction. If the source process is malicious or has a reference * counting bug, relying on the local strong ref can fail. * * Since user-space can cause the local strong ref to go away, we also take * a tmpref on the node to ensure it survives while we are constructing * the transaction. We also need a tmpref on the proc while we are * constructing the transaction, so we take that here as well. * * Return: The target_node with refs taken or NULL if no @node->proc is NULL. * Also sets @procp if valid. If the @node->proc is NULL indicating that the * target proc has died, @error is set to BR_DEAD_REPLY.
*/ staticstruct binder_node *binder_get_node_refs_for_txn( struct binder_node *node, struct binder_proc **procp,
uint32_t *error)
{ struct binder_node *target_node = NULL;
/* * There must already be a strong ref * on this node. If so, do a strong * increment on the node to ensure it * stays alive until the transaction is * done.
*/
binder_proc_lock(proc);
ref = binder_get_ref_olocked(proc, tr->target.handle, true); if (ref) {
target_node = binder_get_node_refs_for_txn(
ref->node, &target_proc,
&return_error);
} else {
binder_user_error("%d:%d got transaction to invalid handle, %u\n",
proc->pid, thread->pid, tr->target.handle);
return_error = BR_FAILED_REPLY;
}
binder_proc_unlock(proc);
} else {
mutex_lock(&context->context_mgr_node_lock);
target_node = context->binder_context_mgr_node; if (target_node)
target_node = binder_get_node_refs_for_txn(
target_node, &target_proc,
&return_error); else
return_error = BR_DEAD_REPLY;
mutex_unlock(&context->context_mgr_node_lock); if (target_node && target_proc->pid == proc->pid) {
binder_user_error("%d:%d got transaction to context manager from process owning it\n",
proc->pid, thread->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EINVAL;
return_error_line = __LINE__; goto err_invalid_target_handle;
}
} if (!target_node) {
binder_txn_error("%d:%d cannot find target node\n",
proc->pid, thread->pid); /* return_error is set above */
return_error_param = -EINVAL;
return_error_line = __LINE__; goto err_dead_binder;
}
e->to_node = target_node->debug_id; if (WARN_ON(proc == target_proc)) {
binder_txn_error("%d:%d self transactions not allowed\n",
thread->pid, proc->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EINVAL;
return_error_line = __LINE__; goto err_invalid_target_handle;
} if (security_binder_transaction(proc->cred,
target_proc->cred) < 0) {
binder_txn_error("%d:%d transaction credentials failed\n",
thread->pid, proc->pid);
return_error = BR_FAILED_REPLY;
return_error_param = -EPERM;
return_error_line = __LINE__; goto err_invalid_target_handle;
}
binder_inner_proc_lock(proc);
w = list_first_entry_or_null(&thread->todo, struct binder_work, entry); if (!(tr->flags & TF_ONE_WAY) && w &&
w->type == BINDER_WORK_TRANSACTION) { /* * Do not allow new outgoing transaction from a * thread that has a transaction at the head of * its todo list. Only need to check the head * because binder_select_thread_ilocked picks a * thread from proc->waiting_threads to enqueue * the transaction, and nothing is queued to the * todo list while the thread is on waiting_threads.
*/
binder_user_error("%d:%d new transaction not allowed when there is a transaction on thread todo\n",
proc->pid, thread->pid);
binder_inner_proc_unlock(proc);
return_error = BR_FAILED_REPLY;
return_error_param = -EPROTO;
return_error_line = __LINE__; goto err_bad_todo_list;
}
if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp;
tmp = thread->transaction_stack; if (tmp->to_thread != thread) {
spin_lock(&tmp->lock);
binder_user_error("%d:%d got new transaction with bad transaction stack, transaction %d has target %d:%d\n",
proc->pid, thread->pid, tmp->debug_id,
tmp->to_proc ? tmp->to_proc->pid : 0,
tmp->to_thread ?
tmp->to_thread->pid : 0);
spin_unlock(&tmp->lock);
binder_inner_proc_unlock(proc);
return_error = BR_FAILED_REPLY;
return_error_param = -EPROTO;
return_error_line = __LINE__; goto err_bad_call_stack;
} while (tmp) { struct binder_thread *from;
if (reply) {
binder_enqueue_thread_work(thread, tcomplete);
binder_inner_proc_lock(target_proc); if (target_thread->is_dead) {
return_error = BR_DEAD_REPLY;
binder_inner_proc_unlock(target_proc); goto err_dead_proc_or_thread;
}
BUG_ON(t->buffer->async_transaction != 0);
binder_pop_transaction_ilocked(target_thread, in_reply_to);
binder_enqueue_thread_work_ilocked(target_thread, &t->work);
target_proc->outstanding_txns++;
binder_inner_proc_unlock(target_proc);
wake_up_interruptible_sync(&target_thread->wait);
binder_free_transaction(in_reply_to);
} elseif (!(t->flags & TF_ONE_WAY)) {
BUG_ON(t->buffer->async_transaction != 0);
binder_inner_proc_lock(proc); /* * Defer the TRANSACTION_COMPLETE, so we don't return to * userspace immediately; this allows the target process to * immediately start processing this transaction, reducing * latency. We will then return the TRANSACTION_COMPLETE when * the target replies (or there is an error).
*/
binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete);
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
binder_inner_proc_unlock(proc);
return_error = binder_proc_transaction(t,
target_proc, target_thread); if (return_error) {
binder_inner_proc_lock(proc);
binder_pop_transaction_ilocked(thread, t);
binder_inner_proc_unlock(proc); goto err_dead_proc_or_thread;
}
} else {
BUG_ON(target_node == NULL);
BUG_ON(t->buffer->async_transaction != 1);
return_error = binder_proc_transaction(t, target_proc, NULL); /* * Let the caller know when async transaction reaches a frozen * process and is put in a pending queue, waiting for the target * process to be unfrozen.
*/ if (return_error == BR_TRANSACTION_PENDING_FROZEN)
tcomplete->type = BINDER_WORK_TRANSACTION_PENDING;
binder_enqueue_thread_work(thread, tcomplete); if (return_error &&
return_error != BR_TRANSACTION_PENDING_FROZEN) goto err_dead_proc_or_thread;
} if (target_thread)
binder_thread_dec_tmpref(target_thread);
binder_proc_dec_tmpref(target_proc); if (target_node)
binder_dec_node_tmpref(target_node); /* * write barrier to synchronize with initialization * of log entry
*/
smp_wmb();
WRITE_ONCE(e->debug_id_done, t_debug_id); return;
err_dead_proc_or_thread:
binder_txn_error("%d:%d dead process or thread\n",
thread->pid, proc->pid);
return_error_line = __LINE__;
binder_dequeue_work(proc, tcomplete);
err_translate_failed:
err_bad_object_type:
err_bad_offset:
err_bad_parent:
err_copy_data_failed:
binder_cleanup_deferred_txn_lists(&sgc_head, &pf_head);
binder_free_txn_fixups(t);
trace_binder_transaction_failed_buffer_release(t->buffer);
binder_transaction_buffer_release(target_proc, NULL, t->buffer,
buffer_offset, true); if (target_node)
binder_dec_node_tmpref(target_node);
target_node = NULL;
t->buffer->transaction = NULL;
binder_alloc_free_buf(&target_proc->alloc, t->buffer);
err_binder_alloc_buf_failed:
err_bad_extra_size: if (lsmctx.context)
security_release_secctx(&lsmctx);
err_get_secctx_failed:
kfree(tcomplete);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
err_alloc_tcomplete_failed: if (trace_binder_txn_latency_free_enabled())
binder_txn_latency_free(t);
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
err_alloc_t_failed:
err_bad_todo_list:
err_bad_call_stack:
err_empty_call_stack:
err_dead_binder:
err_invalid_target_handle: if (target_node) {
binder_dec_node(target_node, 1, 0);
binder_dec_node_tmpref(target_node);
}
if (!ref->freeze) {
binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification not active\n",
proc->pid, thread->pid);
binder_node_unlock(ref->node);
binder_proc_unlock(proc); return -EINVAL;
}
freeze = ref->freeze;
binder_inner_proc_lock(proc); if (freeze->cookie != handle_cookie->cookie) {
binder_user_error("%d:%d BC_CLEAR_FREEZE_NOTIFICATION freeze notification cookie mismatch %016llx != %016llx\n",
proc->pid, thread->pid, (u64)freeze->cookie,
(u64)handle_cookie->cookie);
binder_inner_proc_unlock(proc);
binder_node_unlock(ref->node);
binder_proc_unlock(proc); return -EINVAL;
}
ref->freeze = NULL; /* * Take the existing freeze object and overwrite its work type. There are three cases here: * 1. No pending notification. In this case just add the work to the queue. * 2. A notification was sent and is pending an ack from userspace. Once an ack arrives, we * should resend with the new work type. * 3. A notification is pending to be sent. Since the work is already in the queue, nothing * needs to be done here.
*/
freeze->work.type = BINDER_WORK_CLEAR_FREEZE_NOTIFICATION; if (list_empty(&freeze->work.entry)) {
binder_enqueue_work_ilocked(&freeze->work, &proc->todo);
binder_wakeup_proc_ilocked(proc);
} elseif (freeze->sent) {
freeze->resend = true;
}
binder_inner_proc_unlock(proc);
binder_node_unlock(ref->node);
binder_proc_unlock(proc); return 0;
}
staticint binder_wait_for_work(struct binder_thread *thread, bool do_proc_work)
{
DEFINE_WAIT(wait); struct binder_proc *proc = thread->proc; int ret = 0;
binder_inner_proc_lock(proc); for (;;) {
prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE|TASK_FREEZABLE); if (binder_has_work_ilocked(thread, do_proc_work)) break; if (do_proc_work)
list_add(&thread->waiting_thread_node,
&proc->waiting_threads);
binder_inner_proc_unlock(proc);
schedule();
binder_inner_proc_lock(proc);
list_del_init(&thread->waiting_thread_node); if (signal_pending(current)) {
ret = -EINTR; break;
}
}
finish_wait(&thread->wait, &wait);
binder_inner_proc_unlock(proc);
return ret;
}
/** * binder_apply_fd_fixups() - finish fd translation * @proc: binder_proc associated @t->buffer * @t: binder transaction with list of fd fixups * * Now that we are in the context of the transaction target * process, we can allocate and install fds. Process the * list of fds to translate and fixup the buffer with the * new fds first and only then install the files. * * If we fail to allocate an fd, skip the install and release * any fds that have already been allocated.
*/ staticint binder_apply_fd_fixups(struct binder_proc *proc, struct binder_transaction *t)
{ struct binder_txn_fd_fixup *fixup, *tmp; int ret = 0;
list_for_each_entry(fixup, &t->fd_fixups, fixup_entry) { int fd = get_unused_fd_flags(O_CLOEXEC);
if (weak && !has_weak_ref) {
node->has_weak_ref = 1;
node->pending_weak_ref = 1;
node->local_weak_refs++;
} if (strong && !has_strong_ref) {
node->has_strong_ref = 1;
node->pending_strong_ref = 1;
node->local_strong_refs++;
} if (!strong && has_strong_ref)
node->has_strong_ref = 0; if (!weak && has_weak_ref)
node->has_weak_ref = 0; if (!weak && !strong) {
binder_debug(BINDER_DEBUG_INTERNAL_REFS, "%d:%d node %d u%016llx c%016llx deleted\n",
proc->pid, thread->pid,
node_debug_id,
(u64)node_ptr,
(u64)node_cookie);
rb_erase(&node->rb_node, &proc->nodes);
binder_inner_proc_unlock(proc);
binder_node_lock(node); /* * Acquire the node lock before freeing the * node to serialize with other threads that * may have been holding the node lock while * decrementing this node (avoids race where * this thread frees while the other thread * is unlocking the node after the final * decrement)
*/
binder_node_unlock(node);
binder_free_node(node);
} else
binder_inner_proc_unlock(proc);
if (weak && !has_weak_ref)
ret = binder_put_node_cmd(
proc, thread, &ptr, node_ptr,
node_cookie, node_debug_id,
BR_INCREFS, "BR_INCREFS"); if (!ret && strong && !has_strong_ref)
ret = binder_put_node_cmd(
proc, thread, &ptr, node_ptr,
node_cookie, node_debug_id,
BR_ACQUIRE, "BR_ACQUIRE"); if (!ret && !strong && has_strong_ref)
ret = binder_put_node_cmd(
proc, thread, &ptr, node_ptr,
node_cookie, node_debug_id,
BR_RELEASE, "BR_RELEASE"); if (!ret && !weak && has_weak_ref)
ret = binder_put_node_cmd(
proc, thread, &ptr, node_ptr,
node_cookie, node_debug_id,
BR_DECREFS, "BR_DECREFS"); if (orig_ptr == ptr)
binder_debug(BINDER_DEBUG_INTERNAL_REFS, "%d:%d node %d u%016llx c%016llx state unchanged\n",
proc->pid, thread->pid,
node_debug_id,
(u64)node_ptr,
(u64)node_cookie); if (ret) return ret;
} break; case BINDER_WORK_DEAD_BINDER: case BINDER_WORK_DEAD_BINDER_AND_CLEAR: case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: { struct binder_ref_death *death;
uint32_t cmd;
binder_uintptr_t cookie;
death = container_of(w, struct binder_ref_death, work); if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE; else
cmd = BR_DEAD_BINDER;
cookie = death->cookie;
while (1) {
binder_inner_proc_lock(proc);
w = binder_dequeue_work_head_ilocked(list);
wtype = w ? w->type : 0;
binder_inner_proc_unlock(proc); if (!w) return;
switch (wtype) { case BINDER_WORK_TRANSACTION: { struct binder_transaction *t;
t = container_of(w, struct binder_transaction, work);
binder_inner_proc_lock(thread->proc); /* * take a ref on the proc so it survives * after we remove this thread from proc->threads. * The corresponding dec is when we actually * free the thread in binder_free_thread()
*/
proc->tmp_ref++; /* * take a ref on this thread to ensure it * survives while we are releasing it
*/
atomic_inc(&thread->tmp_ref);
rb_erase(&thread->rb_node, &proc->threads);
t = thread->transaction_stack; if (t) {
spin_lock(&t->lock); if (t->to_thread == thread)
send_reply = t;
} else {
__acquire(&t->lock);
}
thread->is_dead = true;
if (t->to_thread == thread) {
thread->proc->outstanding_txns--;
t->to_proc = NULL;
t->to_thread = NULL; if (t->buffer) {
t->buffer->transaction = NULL;
t->buffer = NULL;
}
t = t->to_parent;
} elseif (t->from == thread) {
t->from = NULL;
t = t->from_parent;
} else
BUG();
spin_unlock(&last_t->lock); if (t)
spin_lock(&t->lock); else
__acquire(&t->lock);
} /* annotation for sparse, lock not acquired in last iteration above */
__release(&t->lock);
/* * If this thread used poll, make sure we remove the waitqueue from any * poll data structures holding it.
*/ if (thread->looper & BINDER_LOOPER_STATE_POLL)
wake_up_pollfree(&thread->wait);
binder_inner_proc_unlock(thread->proc);
/* * This is needed to avoid races between wake_up_pollfree() above and * someone else removing the last entry from the queue for other reasons * (e.g. ep_remove_wait_queue() being called due to an epoll file * descriptor being closed). Such other users hold an RCU read lock, so * we can be sure they're done after we call synchronize_rcu().
*/ if (thread->looper & BINDER_LOOPER_STATE_POLL)
synchronize_rcu();
if (send_reply)
binder_send_failed_reply(send_reply, BR_DEAD_REPLY);
binder_release_work(proc, &thread->todo);
binder_thread_dec_tmpref(thread); return active_transactions;
}
if (info->strong_count || info->weak_count || info->reserved1 ||
info->reserved2 || info->reserved3) {
binder_user_error("%d BINDER_GET_NODE_INFO_FOR_REF: only handle may be non-zero.",
proc->pid); return -EINVAL;
}
/* This ioctl may only be used by the context manager */
mutex_lock(&context->context_mgr_node_lock); if (!context->binder_context_mgr_node ||
context->binder_context_mgr_node->proc != proc) {
mutex_unlock(&context->context_mgr_node_lock); return -EPERM;
}
mutex_unlock(&context->context_mgr_node_lock);
node = binder_get_node_from_ref(proc, handle, true, NULL); if (!node) return -EINVAL;
/* * Freezing the target. Prevent new transactions by * setting frozen state. If timeout specified, wait * for transactions to drain.
*/
binder_inner_proc_lock(target_proc);
target_proc->sync_recv = false;
target_proc->async_recv = false;
target_proc->is_frozen = true;
binder_inner_proc_unlock(target_proc);
if (info->timeout_ms > 0)
ret = wait_event_interruptible_timeout(
target_proc->freeze_wait,
(!target_proc->outstanding_txns),
msecs_to_jiffies(info->timeout_ms));
/* Check pending transactions that wait for reply */ if (ret >= 0) {
binder_inner_proc_lock(target_proc); if (binder_txns_pending_ilocked(target_proc))
ret = -EAGAIN;
binder_inner_proc_unlock(target_proc);
}
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2); if (ret) goto err_unlocked;
thread = binder_get_thread(proc); if (thread == NULL) {
ret = -ENOMEM; goto err;
}
switch (cmd) { case BINDER_WRITE_READ:
ret = binder_ioctl_write_read(filp, arg, thread); if (ret) goto err; break; case BINDER_SET_MAX_THREADS: {
u32 max_threads;
if (copy_from_user(&max_threads, ubuf, sizeof(max_threads))) {
ret = -EINVAL; goto err;
}
binder_inner_proc_lock(proc);
proc->max_threads = max_threads;
binder_inner_proc_unlock(proc); break;
} case BINDER_SET_CONTEXT_MGR_EXT: { struct flat_binder_object fbo;
if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
ret = -EINVAL; goto err;
}
ret = binder_ioctl_set_ctx_mgr(filp, &fbo); if (ret) goto err; break;
} case BINDER_SET_CONTEXT_MGR:
ret = binder_ioctl_set_ctx_mgr(filp, NULL); if (ret) goto err; break; case BINDER_THREAD_EXIT:
binder_debug(BINDER_DEBUG_THREADS, "%d:%d exit\n",
proc->pid, thread->pid);
binder_thread_release(proc, thread);
thread = NULL; break; case BINDER_VERSION: { struct binder_version __user *ver = ubuf;
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL; goto err;
} break;
} case BINDER_GET_NODE_INFO_FOR_REF: { struct binder_node_info_for_ref info;
if (copy_from_user(&info, ubuf, sizeof(info))) {
ret = -EFAULT; goto err;
}
ret = binder_ioctl_get_node_info_for_ref(proc, &info); if (ret < 0) goto err;
if (copy_to_user(ubuf, &info, sizeof(info))) {
ret = -EFAULT; goto err;
}
break;
} case BINDER_GET_NODE_DEBUG_INFO: { struct binder_node_debug_info info;
if (copy_from_user(&info, ubuf, sizeof(info))) {
ret = -EFAULT; goto err;
}
ret = binder_ioctl_get_node_debug_info(proc, &info); if (ret < 0) goto err;
if (copy_to_user(ubuf, &info, sizeof(info))) {
ret = -EFAULT; goto err;
} break;
} case BINDER_FREEZE: { struct binder_freeze_info info; struct binder_proc **target_procs = NULL, *target_proc; int target_procs_count = 0, i = 0;
ret = 0;
if (copy_from_user(&info, ubuf, sizeof(info))) {
ret = -EFAULT; goto err;
}
if (binder_debugfs_dir_entry_proc && !existing_pid) { char strbuf[11];
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); /* * proc debug entries are shared between contexts. * Only create for the first PID to avoid debugfs log spamming * The printing code will anyway print all contexts for a given * PID so this is not a problem.
*/
proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
binder_debugfs_dir_entry_proc,
(void *)(unsignedlong)proc->pid,
&proc_fops);
}
if (binder_binderfs_dir_entry_proc && !existing_pid) { char strbuf[11]; struct dentry *binderfs_entry;
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid); /* * Similar to debugfs, the process specific log file is shared * between contexts. Only create for the first PID. * This is ok since same as debugfs, the log file will contain * information on all contexts of a given PID.
*/
binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc,
strbuf, &proc_fops, (void *)(unsignedlong)proc->pid); if (!IS_ERR(binderfs_entry)) {
proc->binderfs_entry = binderfs_entry;
} else { int error;
error = PTR_ERR(binderfs_entry);
pr_warn("Unable to create file %s in binderfs (error %d)\n",
strbuf, error);
}
}
if (proc->binderfs_entry) {
simple_recursive_removal(proc->binderfs_entry, NULL);
proc->binderfs_entry = NULL;
}
binder_defer_work(proc, BINDER_DEFERRED_RELEASE);
return 0;
}
staticint binder_node_release(struct binder_node *node, int refs)
{ struct binder_ref *ref; int death = 0; struct binder_proc *proc = node->proc;
binder_release_work(proc, &node->async_todo);
binder_node_lock(node);
binder_inner_proc_lock(proc);
binder_dequeue_work_ilocked(&node->work); /* * The caller must have taken a temporary ref on the node,
*/
BUG_ON(!node->tmp_refs); if (hlist_empty(&node->refs) && node->tmp_refs == 1) {
binder_inner_proc_unlock(proc);
binder_node_unlock(node);
binder_free_node(node);
hlist_for_each_entry(ref, &node->refs, node_entry) {
refs++; /* * Need the node lock to synchronize * with new notification requests and the * inner lock to synchronize with queued * death notifications.
*/
binder_inner_proc_lock(ref->proc); if (!ref->death) {
binder_inner_proc_unlock(ref->proc); continue;
}
node = rb_entry(n, struct binder_node, rb_node);
nodes++; /* * take a temporary ref on the node before * calling binder_node_release() which will either * kfree() the node or call binder_put_node()
*/
binder_inc_node_tmpref_ilocked(node);
rb_erase(&node->rb_node, &proc->nodes);
binder_inner_proc_unlock(proc);
incoming_refs = binder_node_release(node, incoming_refs);
binder_inner_proc_lock(proc);
}
binder_inner_proc_unlock(proc);
/** * print_next_binder_node_ilocked() - Print binder_node from a locked list * @m: struct seq_file for output via seq_printf() * @proc: struct binder_proc we hold the inner_proc_lock to (if any) * @node: struct binder_node to print fields of * @prev_node: struct binder_node we hold a temporary reference to (if any) * @hash_ptrs: whether to hash @node's binder_uintptr_t fields * * Helper function to handle synchronization around printing a struct * binder_node while iterating through @proc->nodes or the dead nodes list. * Caller must hold either @proc->inner_lock (for live nodes) or * binder_dead_nodes_lock. This lock will be released during the body of this * function, but it will be reacquired before returning to the caller. * * Return: pointer to the struct binder_node we hold a tmpref on
*/ staticstruct binder_node *
print_next_binder_node_ilocked(struct seq_file *m, struct binder_proc *proc, struct binder_node *node, struct binder_node *prev_node, bool hash_ptrs)
{ /* * Take a temporary reference on the node so that isn't freed while * we print it.
*/
binder_inc_node_tmpref_ilocked(node); /* * Live nodes need to drop the inner proc lock and dead nodes need to * drop the binder_dead_nodes_lock before trying to take the node lock.
*/ if (proc)
binder_inner_proc_unlock(proc); else
spin_unlock(&binder_dead_nodes_lock); if (prev_node)
binder_put_node(prev_node);
binder_node_inner_lock(node);
print_binder_node_nilocked(m, node, hash_ptrs);
binder_node_inner_unlock(node); if (proc)
binder_inner_proc_lock(proc); else
spin_lock(&binder_dead_nodes_lock); return node;
}
BUILD_BUG_ON(ARRAY_SIZE(stats->bc) !=
ARRAY_SIZE(binder_command_strings)); for (i = 0; i < ARRAY_SIZE(stats->bc); i++) { int temp = atomic_read(&stats->bc[i]);
if (temp)
seq_printf(m, "%s%s: %d\n", prefix,
binder_command_strings[i], temp);
}
BUILD_BUG_ON(ARRAY_SIZE(stats->br) !=
ARRAY_SIZE(binder_return_strings)); for (i = 0; i < ARRAY_SIZE(stats->br); i++) { int temp = atomic_read(&stats->br[i]);
if (temp)
seq_printf(m, "%s%s: %d\n", prefix,
binder_return_strings[i], temp);
}
BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) !=
ARRAY_SIZE(binder_objstat_strings));
BUILD_BUG_ON(ARRAY_SIZE(stats->obj_created) !=
ARRAY_SIZE(stats->obj_deleted)); for (i = 0; i < ARRAY_SIZE(stats->obj_created); i++) { int created = atomic_read(&stats->obj_created[i]); int deleted = atomic_read(&stats->obj_deleted[i]);
if (created || deleted)
seq_printf(m, "%s%s: active %d total %d\n",
prefix,
binder_objstat_strings[i],
created - deleted,
created);
}
}
if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
strcmp(binder_devices_param, "") != 0) { /* * Copy the module_parameter string, because we don't want to * tokenize it in-place.
*/
device_names = kstrdup(binder_devices_param, GFP_KERNEL); if (!device_names) {
ret = -ENOMEM; goto err_alloc_device_names_failed;
}
device_tmp = device_names; while ((device_name = strsep(&device_tmp, ","))) {
ret = init_binder_device(device_name); if (ret) goto err_init_binder_device_failed;
}
}
ret = init_binderfs(); if (ret) goto err_init_binder_device_failed;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.158Bemerkung:
(vorverarbeitet am 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.