* drivers/binder/binder_thread.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#define LOG_TAG "BinderThread"
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <poll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <debug.h>
#include <sched.h>
#include <nuttx/fs/fs.h>
#include <nuttx/android/binder.h>
#include <nuttx/mutex.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/semaphore.h>
#include <nuttx/wqueue.h>
#include "binder_internal.h"
* Pre-processor Definitions
****************************************************************************/
* Private Function
****************************************************************************/
static FAR struct binder_work *binder_dequeue_work_head_ilocked(
FAR struct list_node *list)
{
FAR struct binder_work *w;
w = list_first_entry_or_null(list, struct binder_work, entry_node);
if (w)
{
list_delete_init(&w->entry_node);
}
return w;
}
static inline bool binder_has_work_ilocked(FAR struct binder_thread *thread,
bool do_proc_work)
{
return thread->process_todo || thread->looper_need_return ||
(do_proc_work &&
!binder_worklist_empty_ilocked(&thread->proc->todo_list));
}
static void binder_wakeup_poll_threads_ilocked(FAR struct binder_proc *proc,
bool sync)
{
FAR struct binder_thread *thread;
list_for_every_entry(&proc->threads, thread, struct binder_thread,
thread_node)
{
if (thread->looper & BINDER_LOOPER_STATE_POLL &&
binder_available_for_proc_work_ilocked(thread))
{
if (sync)
{
wait_wake_up(&thread->wait, 1);
}
else
{
wait_wake_up(&thread->wait, 0);
}
}
}
}
static int binder_wake_function(FAR void * arg, unsigned mode)
{
FAR struct wait_queue_entry *wq_entry;
FAR sem_t *waitsem;
int ret;
wq_entry = (FAR struct wait_queue_entry *)arg;
waitsem = (FAR sem_t *)wq_entry->private;
ret = nxsem_post(waitsem);
binder_debug(BINDER_DEBUG_SCHED, "wq_entry=%p, ret=%d\n", wq_entry, ret);
return ret;
}
static int binder_wait_for_work(FAR struct binder_thread *thread,
bool do_proc_work)
{
FAR struct binder_proc *proc = thread->proc;
int ret = 0;
sem_t waitsem;
struct wait_queue_entry wait;
nxsem_init(&waitsem, 0, 0);
init_waitqueue_entry(&wait, (void *)&waitsem, binder_wake_function);
binder_inner_proc_lock(proc);
for (; ; )
{
prepare_to_wait(&thread->wait, &wait);
if (binder_has_work_ilocked(thread, do_proc_work))
{
break;
}
if (do_proc_work)
{
list_add_tail(&proc->waiting_threads,
&thread->waiting_thread_node);
}
binder_debug(BINDER_DEBUG_THREADS,
"%d:%d enter wait do_proc_work=%s\n",
proc->pid, thread->tid,
do_proc_work? "true":"false");
binder_inner_proc_unlock(proc);
ret = nxsem_wait(&waitsem);
binder_inner_proc_lock(proc);
list_delete_init(&thread->waiting_thread_node);
binder_debug(BINDER_DEBUG_THREADS, "%d:%d finish wait ret=%d\n",
proc->pid, thread->tid, ret);
if (ret < 0)
{
if (ret != -EINTR)
{
binder_debug(BINDER_DEBUG_ERROR,
"error with binder_wait_for_work, ret=%d\n",
ret);
}
break;
}
}
finish_wait(&wait);
binder_inner_proc_unlock(proc);
return ret;
}
* Name: binder_free_buf
*
* Description:
* free the specified buffer.
* If buffer for an async transaction, enqueue the next async
* transaction from the node.
* Cleanup buffer and free it.
*
* Input Parameters:
* proc - binder proc that owns buffer
* buffer - buffer to be freed
* is_failure - failed to send transaction
*
****************************************************************************/
static void binder_free_buf(FAR struct binder_proc *proc,
FAR struct binder_thread *thread,
FAR struct binder_buffer *buffer,
bool is_failure)
{
binder_inner_proc_lock(proc);
if (buffer->transaction)
{
buffer->transaction->buffer = NULL;
buffer->transaction = NULL;
}
binder_inner_proc_unlock(proc);
if (buffer->async_transaction && buffer->target_node)
{
FAR struct binder_node *buf_node;
FAR struct binder_work *w;
buf_node = buffer->target_node;
binder_node_inner_lock(buf_node);
BUG_ON(!buf_node->has_async_transaction);
BUG_ON(buf_node->proc != proc);
w = binder_dequeue_work_head_ilocked(&buf_node->async_todo);
if (!w)
{
buf_node->has_async_transaction = false;
}
else
{
binder_enqueue_work_ilocked(w, &proc->todo_list);
binder_wakeup_proc_ilocked(proc);
}
binder_node_inner_unlock(buf_node);
}
binder_release_entire_buffer(proc, thread, buffer, is_failure);
binder_alloc_free_buf(&proc->alloc, buffer);
}
* Name: binder_get_txn_from
*
* Description:
* safely extract the "from" thread in transaction.
*
* 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.
*
* Input Parameters:
* t - binder transaction for t->from
*
* Returned Value:
* the value of t->from
*
****************************************************************************/
static struct binder_thread * binder_get_txn_from(
FAR struct binder_transaction *t)
{
FAR struct binder_thread *from;
nxmutex_lock(&t->lock);
from = t->from;
if (from)
{
binder_inner_proc_lock(from->proc);
from->tmp_ref++;
binder_inner_proc_unlock(from->proc);
}
nxmutex_unlock(&t->lock);
return from;
}
static void binder_free_proc(FAR struct binder_proc *proc)
{
FAR struct binder_device *device;
BUG_ON(!list_is_empty(&proc->todo_list));
BUG_ON(!list_is_empty(&proc->delivered_death));
if (proc->outstanding_txns)
{
binder_debug(BINDER_DEBUG_WARNING,
"Unexpected outstanding_txns %d\n",
proc->outstanding_txns);
}
device = container_of(proc->context, struct binder_device, context);
nxmutex_lock(&device->binder_procs_lock);
device->ref_count--;
if (device->ref_count == 0)
{
nxmutex_unlock(&device->binder_procs_lock);
kmm_free(device);
}
else
{
nxmutex_unlock(&device->binder_procs_lock);
}
binder_alloc_deferred_release(&proc->alloc);
kmm_free(proc);
}
static void binder_free_thread(FAR struct binder_thread *thread)
{
BUG_ON(!list_is_empty(&thread->todo));
binder_proc_dec_tmpref(thread->proc);
kmm_free(thread);
}
static int binder_put_node_cmd(FAR struct binder_proc *proc,
FAR struct binder_thread *thread,
FAR void **ptrp,
binder_uintptr_t node_ptr,
binder_uintptr_t node_cookie,
int node_debug_id, uint32_t cmd,
const char *cmd_name)
{
FAR void *ptr = *ptrp;
put_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
put_value(node_ptr, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
put_value(node_cookie, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
binder_debug(BINDER_DEBUG_THREADS, "Send %s", BINDER_BR_STR(cmd));
binder_debug(BINDER_DEBUG_USER_REFS, "%s %d %" PRIx64 " %" PRIx64 "\n",
cmd_name, node_debug_id, node_ptr, node_cookie);
*ptrp = ptr;
return 0;
}
* Name: binder_apply_fd_fixups
*
* Description:
* finish fd translation.
*
* 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.
*
* If we fail to allocate an fd, then free the resources by
* fput'ing files that have not been processed and ksys_close'ing
* any fds that have already been allocated.
*
* Input Parameters:
* proc - binder_proc associated t->buffer
* t - binder transaction with list of fd fixups
*
* Returned Value:
* the value of t->from
*
****************************************************************************/
static int binder_apply_fd_fixups(FAR struct binder_proc *proc,
FAR struct binder_transaction *t)
{
FAR struct binder_txn_fd_fixup *fixup;
FAR struct binder_txn_fd_fixup *tmp;
int ret = 0;
int fd;
int err;
list_for_every_entry(&t->fd_fixups, fixup, struct binder_txn_fd_fixup,
fixup_entry)
{
fd = file_dup(&fixup->file, 0, 0);
file_close(&fixup->file);
if (fd < 0)
{
binder_debug(BINDER_DEBUG_TRANSACTION,
"failed fd fixup txn %d fd %d\n", t->debug_id, fd);
ret = -ENOMEM;
break;
}
binder_debug(BINDER_DEBUG_TRANSACTION, "fd fixup txn %d fd %d\n",
t->debug_id, fd);
if (binder_alloc_copy_to_buffer(&proc->alloc, t->buffer, fixup->offset,
&fd, sizeof(uint32_t)))
{
ret = -EINVAL;
break;
}
}
list_for_every_entry_safe(&t->fd_fixups, fixup, tmp,
struct binder_txn_fd_fixup, fixup_entry)
{
if (ret)
{
err = binder_alloc_copy_from_buffer(&proc->alloc, &fd, t->buffer,
fixup->offset, sizeof(fd));
WARN_ON(err);
if (!err)
{
binder_deferred_fd_close(fd);
}
}
list_delete_init(&fixup->fixup_entry);
kmm_free(fixup);
}
return ret;
}
* Public Functions
****************************************************************************/
bool binder_has_work(FAR struct binder_thread *thread, bool do_proc_work)
{
bool has_work;
binder_inner_proc_lock(thread->proc);
has_work = binder_has_work_ilocked(thread, do_proc_work);
binder_inner_proc_unlock(thread->proc);
return has_work;
}
* Name: binder_dequeue_work
*
* Description:
* Removes an item from the work list
*
* Removes the specified work item from whatever list it is on.
* Can safely be called if work is not on any list.
*
* Input Parameters:
* proc - binder_proc associated with list
* work - struct binder_work to remove from list
*
****************************************************************************/
void binder_dequeue_work(FAR struct binder_proc *proc,
FAR struct binder_work *work)
{
binder_inner_proc_lock(proc);
binder_dequeue_work_ilocked(work);
binder_inner_proc_unlock(proc);
}
* Name: binder_select_thread_ilocked
*
* Description:
* selects a thread for doing proc work.
*
* Input Parameters:
* proc - process to select a thread from
*
* Returned Value:
* If there's a thread currently waiting for process work,
* returns that thread. Otherwise returns NULL.
*
****************************************************************************/
FAR struct binder_thread *binder_select_thread_ilocked(
FAR struct binder_proc *proc)
{
FAR struct binder_thread *thread;
binder_inner_proc_assert_locked(proc);
thread = list_first_entry_or_null(&proc->waiting_threads,
struct binder_thread,
waiting_thread_node);
* is polling after received EAGAIN, then:
* - thread is not NULL
* - binder_available_for_proc_work_ilocked(thread) is false
*
* In this case, set thread to NULL. That is, there is no thread
* currently waiting for process work.
*/
if (thread && binder_available_for_proc_work_ilocked(thread))
{
list_delete_init(&thread->waiting_thread_node);
}
else
{
thread = NULL;
}
return thread;
}
* Name: binder_wakeup_thread_ilocked
*
* Description:
* wakes up a thread for doing proc work.
*
* 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.
*
* Input Parameters:
* 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
*
****************************************************************************/
void binder_wakeup_thread_ilocked(FAR struct binder_proc *proc,
FAR struct binder_thread *thread,
bool sync)
{
binder_inner_proc_assert_locked(proc);
binder_debug(BINDER_DEBUG_THREADS, "wake up %d:%d sync:%s\n", proc->pid,
thread != NULL ? thread->tid : 0, sync ? "true":"false");
if (thread)
{
if (sync)
{
wait_wake_up(&thread->wait, 1);
}
else
{
wait_wake_up(&thread->wait, 0);
}
return;
}
* 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);
}
* Name: binder_enqueue_thread_work
*
* Description:
* Adds the work to the todo list of the thread, and enables processing
* of the todo queue.
*
* Input Parameters:
* thread - thread to queue work to
* work - struct binder_work to add to list
*
****************************************************************************/
void binder_enqueue_thread_work(FAR struct binder_thread *thread,
FAR struct binder_work *work)
{
binder_inner_proc_lock(thread->proc);
binder_enqueue_thread_work_ilocked(thread, work);
binder_inner_proc_unlock(thread->proc);
}
void binder_wakeup_proc_ilocked(FAR struct binder_proc *proc)
{
FAR struct binder_thread *thread = binder_select_thread_ilocked(proc);
binder_wakeup_thread_ilocked(proc, thread, false);
}
* Name: binder_proc_dec_tmpref
*
* Description:
* 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 currenly in-use to process a transaction).
*
* Input Parameters:
* proc - proc to decrement
*
****************************************************************************/
void binder_proc_dec_tmpref(FAR struct binder_proc *proc)
{
binder_inner_proc_lock(proc);
proc->tmp_ref--;
if (proc->is_dead && list_is_empty(&proc->threads) && !proc->tmp_ref)
{
binder_inner_proc_unlock(proc);
binder_free_proc(proc);
return;
}
binder_inner_proc_unlock(proc);
}
* Name: binder_thread_dec_tmpref
*
* Description:
* 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)
*
* Input Parameters:
* thread - thread to decrement
*
****************************************************************************/
void binder_thread_dec_tmpref(FAR struct binder_thread *thread)
{
* it cannot reach zero or thread->is_dead is false
*/
binder_inner_proc_lock(thread->proc);
thread->tmp_ref--;
if (thread->is_dead && thread->tmp_ref == 0)
{
binder_inner_proc_unlock(thread->proc);
binder_free_thread(thread);
return;
}
binder_inner_proc_unlock(thread->proc);
}
FAR static struct binder_thread *binder_get_thread_ilocked(
FAR struct binder_proc *proc,
FAR struct binder_thread *new_thread)
{
FAR struct binder_thread *itr = NULL;
FAR struct binder_thread *thread = NULL;
pid_t thread_pid = gettid();
list_for_every_entry(&proc->threads, itr,
struct binder_thread, thread_node)
{
if (itr->tid == thread_pid)
{
thread = itr;
return thread;
}
}
if (!new_thread)
{
return NULL;
}
thread = new_thread;
thread->proc = proc;
thread->tid = thread_pid;
thread->tmp_ref = 0;
list_initialize(&thread->thread_node);
list_initialize(&thread->wait);
list_initialize(&thread->todo);
list_initialize(&thread->waiting_thread_node);
thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
thread->return_error.cmd = BR_OK;
list_initialize(&thread->return_error.work.entry_node);
thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
thread->reply_error.cmd = BR_OK;
list_initialize(&thread->reply_error.work.entry_node);
list_add_head(&proc->threads, &thread->thread_node);
return thread;
}
FAR struct binder_thread *binder_get_thread(FAR struct binder_proc *proc)
{
FAR struct binder_thread *thread;
FAR struct binder_thread *new_thread;
binder_inner_proc_lock(proc);
thread = binder_get_thread_ilocked(proc, NULL);
binder_inner_proc_unlock(proc);
if (thread == NULL)
{
new_thread = kmm_zalloc(sizeof(struct binder_thread));
if (new_thread == NULL)
{
return NULL;
}
binder_inner_proc_lock(proc);
thread = binder_get_thread_ilocked(proc, new_thread);
binder_inner_proc_unlock(proc);
if (thread != new_thread)
{
kmm_free(new_thread);
}
}
return thread;
}
int binder_thread_write(FAR struct binder_proc *proc,
FAR struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
FAR binder_size_t *consumed)
{
uint32_t cmd;
FAR struct binder_context *context = proc->context;
FAR void *buffer = (FAR void *)(uintptr_t)binder_buffer;
FAR void *ptr = buffer + *consumed;
FAR void *end = buffer + size;
while (ptr < end && thread->return_error.cmd == BR_OK)
{
int ret;
get_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
binder_debug(BINDER_DEBUG_THREADS,
"Handling %s\n", BINDER_BC_STR(cmd));
switch (cmd)
{
case BC_INCREFS:
case BC_ACQUIRE:
case BC_RELEASE:
case BC_DECREFS:
{
uint32_t target;
FAR const char *debug_string;
bool strong = (cmd == BC_ACQUIRE || cmd == BC_RELEASE);
bool increment = (cmd == BC_INCREFS || cmd == BC_ACQUIRE);
struct binder_ref_data rdata;
UNUSED(debug_string);
get_value(target, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
ret = -1;
if (increment && !target)
{
FAR struct binder_node *ctx_mgr_node;
nxmutex_lock(&context->context_lock);
ctx_mgr_node = context->mgr_node;
if (ctx_mgr_node)
{
if (ctx_mgr_node->proc == proc)
{
binder_debug(BINDER_DEBUG_ERROR, "context manager "
"tried to acquire desc 0\n");
nxmutex_unlock(&context->context_lock);
return -EINVAL;
}
ret = binder_inc_ref_for_node(proc, ctx_mgr_node, strong,
NULL, &rdata);
}
nxmutex_unlock(&context->context_lock);
}
if (ret != 0)
{
ret = binder_update_ref_for_handle(proc, target, increment,
strong, &rdata);
}
if (!ret && rdata.desc != target)
{
binder_debug(BINDER_DEBUG_ERROR,
"tried to acquire reference to desc %"PRId32", "
"got %"PRId32" instead\n",
target, rdata.desc);
}
switch (cmd)
{
case BC_INCREFS:
{
debug_string = "IncRefs";
break;
}
case BC_ACQUIRE:
{
debug_string = "Acquire";
break;
}
case BC_RELEASE:
{
debug_string = "Release";
break;
}
case BC_DECREFS:
default:
{
debug_string = "DecRefs";
break;
}
}
if (ret)
{
binder_debug(BINDER_DEBUG_ERROR,
"%s %d refcount change on invalid"
" ref %"PRId32" ret %d\n",
debug_string, strong, target, ret);
break;
}
binder_debug(BINDER_DEBUG_USER_REFS,
"%s ref %d desc %" PRId32 " strong %d weak %d\n",
debug_string, rdata.debug_id,
rdata.desc, rdata.strong, rdata.weak);
break;
}
case BC_INCREFS_DONE:
case BC_ACQUIRE_DONE:
{
binder_uintptr_t node_ptr;
binder_uintptr_t cookie;
FAR struct binder_node *node;
get_value(node_ptr, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
get_value(cookie, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
node = binder_get_node(proc, node_ptr);
if (node == NULL)
{
binder_debug(BINDER_DEBUG_ERROR, "%s %" PRIx64 " no match\n",
cmd == BC_INCREFS_DONE ?
"BC_INCREFS_DONE" :"BC_ACQUIRE_DONE",
node_ptr);
break;
}
if (cookie != node->cookie)
{
binder_debug(BINDER_DEBUG_ERROR,
"%s %"PRIx64" node %d cookie mismatch"
" %"PRIx64" != %"PRIx64"\n",
cmd == BC_INCREFS_DONE ?
"BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
node_ptr, node->debug_id, cookie,
node->cookie);
binder_dec_node_tmpref(node);
break;
}
binder_node_inner_lock(node);
if (cmd == BC_ACQUIRE_DONE)
{
if (node->pending_strong_ref == 0)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_ACQUIRE_DONE node %d has no "
"pending acquire request\n",
node->debug_id);
binder_node_inner_unlock(node);
binder_dec_node_tmpref(node);
break;
}
node->pending_strong_ref = 0;
}
else
{
if (node->pending_weak_ref == 0)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_INCREFS_DONE node %d has no pending "
"increfs request\n",
node->debug_id);
binder_node_inner_unlock(node);
binder_dec_node_tmpref(node);
break;
}
node->pending_weak_ref = 0;
}
WARN_ON(binder_dec_node_nilocked(node, cmd == BC_ACQUIRE_DONE, 0));
binder_debug(BINDER_DEBUG_USER_REFS,
"%s node %d ls %d lw %d tr %d\n",
BINDER_BC_STR(cmd),
node->debug_id, node->local_strong_refs,
node->local_weak_refs, node->tmp_refs);
binder_node_inner_unlock(node);
binder_dec_node_tmpref(node);
break;
}
case BC_ATTEMPT_ACQUIRE:
case BC_ACQUIRE_RESULT:
{
binder_debug(BINDER_DEBUG_ERROR, "not supported\n");
return -EINVAL;
}
case BC_FREE_BUFFER:
{
binder_uintptr_t data_ptr;
FAR struct binder_buffer *b_buffer;
get_value(data_ptr, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
b_buffer = binder_alloc_prepare_to_free(&proc->alloc, data_ptr);
if (b_buffer == NULL)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_FREE_BUFFER %"PRIx64" no match\n",
data_ptr);
break;
}
binder_debug(BINDER_DEBUG_FREE_BUFFER,
"BC_FREE_BUFFER %"PRIx64" found buffer %d "
"for %s transaction\n",
data_ptr, b_buffer->debug_id,
b_buffer->transaction ? "active" : "finished");
binder_free_buf(proc, thread, b_buffer, false);
break;
}
case BC_TRANSACTION:
case BC_REPLY:
{
FAR struct binder_transaction_data *binder_tr;
binder_tr = (struct binder_transaction_data *)ptr;
ptr += sizeof(struct binder_transaction_data);
binder_transaction(proc, thread, binder_tr, cmd == BC_REPLY);
break;
}
case BC_REGISTER_LOOPER:
{
binder_inner_proc_lock(proc);
if (thread->looper & BINDER_LOOPER_STATE_ENTERED)
{
thread->looper |= BINDER_LOOPER_STATE_INVALID;
binder_debug(BINDER_DEBUG_ERROR,
"ERROR: BC_REGISTER_LOOPER called"
"after BC_ENTER_LOOPER\n");
}
else if (proc->requested_threads == 0)
{
thread->looper |= BINDER_LOOPER_STATE_INVALID;
binder_debug(BINDER_DEBUG_ERROR,
"ERROR: BC_REGISTER_LOOPER called"
"without request\n");
}
else
{
proc->requested_threads--;
proc->requested_threads_started++;
}
thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
binder_inner_proc_unlock(proc);
break;
}
case BC_ENTER_LOOPER:
{
if (thread->looper & BINDER_LOOPER_STATE_REGISTERED)
{
thread->looper |= BINDER_LOOPER_STATE_INVALID;
binder_debug(BINDER_DEBUG_ERROR,
"ERROR: BC_ENTER_LOOPER called"
"after BC_REGISTER_LOOPER\n");
}
thread->looper |= BINDER_LOOPER_STATE_ENTERED;
break;
}
case BC_EXIT_LOOPER:
{
thread->looper |= BINDER_LOOPER_STATE_EXITED;
break;
}
case BC_REQUEST_DEATH_NOTIFICATION:
case BC_CLEAR_DEATH_NOTIFICATION:
{
uint32_t target;
binder_uintptr_t cookie;
FAR struct binder_ref *ref;
FAR struct binder_ref_death *death = NULL;
get_value(target, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
get_value(cookie, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
if (cmd == BC_REQUEST_DEATH_NOTIFICATION)
{
* before taking lock
*/
death = kmm_zalloc(sizeof(struct binder_ref_death));
if (death == NULL)
{
WARN_ON(thread->return_error.cmd != BR_OK);
thread->return_error.cmd = BR_ERROR;
binder_enqueue_thread_work(thread,
&thread->return_error.work);
binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
"BC_REQUEST_DEATH_NOTIFICATION failed\n");
break;
}
list_initialize(&death->work.entry_node);
}
binder_proc_lock(proc);
ref = binder_get_ref_olocked(proc, target, false);
if (ref == NULL)
{
binder_debug(BINDER_DEBUG_ERROR, "%s invalid ref %"PRId32"\n",
BINDER_BC_STR(cmd), target);
binder_proc_unlock(proc);
kmm_free(death);
break;
}
binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
"%s %"PRIx64" ref %d desc %"PRId32" "
"s %d w %d for node %d\n",
BINDER_BC_STR(cmd),
cookie, ref->data.debug_id, ref->data.desc,
ref->data.strong, ref->data.weak,
ref->node->debug_id);
binder_node_lock(ref->node);
if (cmd == BC_REQUEST_DEATH_NOTIFICATION)
{
if (ref->death)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_REQUEST_DEATH_NOTIFICATION death"
"notification already set\n");
binder_node_unlock(ref->node);
binder_proc_unlock(proc);
kmm_free(death);
break;
}
death->cookie = cookie;
ref->death = death;
if (ref->node->proc == NULL)
{
ref->death->work.type = BINDER_WORK_DEAD_BINDER;
binder_inner_proc_lock(proc);
binder_enqueue_work_ilocked(&ref->death->work,
&proc->todo_list);
binder_wakeup_proc_ilocked(proc);
binder_inner_proc_unlock(proc);
}
}
else
{
if (ref->death == NULL)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_CLEAR_DEATH_NOTIFICATION death "
"notification not active\n");
binder_node_unlock(ref->node);
binder_proc_unlock(proc);
break;
}
death = ref->death;
if (death->cookie != cookie)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_CLEAR_DEATH_NOTIFICATION death "
"notification cookie mismatch"
" %"PRIx64" != %"PRIx64"\n",
death->cookie, cookie);
binder_node_unlock(ref->node);
binder_proc_unlock(proc);
break;
}
ref->death = NULL;
binder_inner_proc_lock(proc);
if (list_is_empty(&death->work.entry_node))
{
death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
if (thread->looper &
(BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))
{
binder_enqueue_thread_work_ilocked(thread,
&death->work);
}
else
{
binder_enqueue_work_ilocked(&death->work,
&proc->todo_list);
binder_wakeup_proc_ilocked(proc);
}
}
else
{
BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER);
death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR;
}
binder_inner_proc_unlock(proc);
}
binder_node_unlock(ref->node);
binder_proc_unlock(proc);
break;
}
case BC_DEAD_BINDER_DONE:
{
FAR struct binder_work *w;
binder_uintptr_t cookie;
FAR struct binder_ref_death *death = NULL;
get_value(cookie, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
binder_inner_proc_lock(proc);
list_for_every_entry(&proc->delivered_death, w,
struct binder_work, entry_node)
{
struct binder_ref_death *tmp_death =
container_of(w, struct binder_ref_death, work);
if (tmp_death->cookie == cookie)
{
death = tmp_death;
break;
}
}
binder_debug(BINDER_DEBUG_DEAD_BINDER,
"BC_DEAD_BINDER_DONE %" PRIx64 " found %p\n",
cookie, death);
if (death == NULL)
{
binder_debug(BINDER_DEBUG_ERROR,
"BC_DEAD_BINDER_DONE %" PRIx64 " not found\n",
cookie);
binder_inner_proc_unlock(proc);
break;
}
binder_dequeue_work_ilocked(&death->work);
if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR)
{
death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
if (thread->looper &
(BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))
{
binder_enqueue_thread_work_ilocked(thread, &death->work);
}
else
{
binder_enqueue_work_ilocked(&death->work,
&proc->todo_list);
binder_wakeup_proc_ilocked(proc);
}
}
binder_inner_proc_unlock(proc);
break;
}
default:
{
binder_debug(BINDER_DEBUG_ERROR, "unknown command %" PRId32 "\n",
cmd);
return -EINVAL;
}
}
*consumed = ptr - buffer;
}
return 0;
}
int binder_thread_read(FAR struct binder_proc *proc,
FAR struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
FAR binder_size_t *consumed, int non_block)
{
FAR void *buffer = (void *)(uintptr_t)binder_buffer;
FAR void *ptr = buffer + *consumed;
FAR void *end = buffer + size;
int ret = 0;
int wait_for_proc_work;
if (*consumed == 0)
{
put_value(BR_NOOP, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
binder_debug(BINDER_DEBUG_THREADS, "Send BR_NOOP\n");
}
retry:
binder_inner_proc_lock(proc);
wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
binder_inner_proc_unlock(proc);
thread->looper |= BINDER_LOOPER_STATE_WAITING;
if (wait_for_proc_work)
{
if (!(thread->looper &
(BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)))
{
binder_debug(BINDER_DEBUG_ERROR,
"ERROR: Thread waiting for process "
"work before calling BC_REGISTER_LOOPER "
"or BC_ENTER_LOOPER (state %x)\n",
thread->looper);
}
binder_set_priority(thread, &proc->default_priority);
}
if (non_block)
{
if (!binder_has_work(thread, wait_for_proc_work))
{
ret = -EAGAIN;
}
}
else
{
ret = binder_wait_for_work(thread, wait_for_proc_work);
}
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
if (ret != 0)
{
return ret;
}
while (1)
{
struct binder_transaction_data_secctx tr;
FAR struct binder_transaction_data *trd = &tr.transaction_data;
FAR struct binder_work *w = NULL;
FAR struct list_node *list = NULL;
FAR struct binder_transaction *t = NULL;
FAR struct binder_thread *t_from;
size_t trsize = sizeof(*trd);
uint32_t cmd;
memset(&tr, 0x0, sizeof(struct binder_transaction_data_secctx));
binder_inner_proc_lock(proc);
if (!binder_worklist_empty_ilocked(&thread->todo))
{
list = &thread->todo;
}
else if (!binder_worklist_empty_ilocked(&proc->todo_list) &&
wait_for_proc_work)
{
list = &proc->todo_list;
}
else
{
binder_inner_proc_unlock(proc);
if (ptr - buffer == 4 && !thread->looper_need_return)
{
goto retry;
}
if (ptr - buffer == 4 && thread->looper_need_return)
{
put_value(BR_FINISHED, (FAR uint32_t *)buffer);
binder_debug(BINDER_DEBUG_THREADS, "Send BR_FINISHED\n");
}
break;
}
if (end - ptr < sizeof(tr) + 4)
{
binder_inner_proc_unlock(proc);
break;
}
w = binder_dequeue_work_head_ilocked(list);
if (binder_worklist_empty_ilocked(&thread->todo))
{
thread->process_todo = false;
}
switch (w->type)
{
case BINDER_WORK_TRANSACTION:
{
binder_inner_proc_unlock(proc);
t = container_of(w, struct binder_transaction, work);
}
break;
case BINDER_WORK_RETURN_ERROR:
{
FAR struct binder_error *e =
container_of(w, struct binder_error, work);
WARN_ON(e->cmd == BR_OK);
binder_inner_proc_unlock(proc);
put_value(e->cmd, (uint32_t *)ptr);
cmd = e->cmd;
e->cmd = BR_OK;
ptr += sizeof(uint32_t);
binder_debug(BINDER_DEBUG_THREADS,
"Send %s\n", BINDER_BR_STR(cmd));
}
break;
case BINDER_WORK_TRANSACTION_COMPLETE:
case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT:
{
cmd = BR_TRANSACTION_COMPLETE;
binder_inner_proc_unlock(proc);
kmm_free(w);
put_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
binder_debug(BINDER_DEBUG_THREADS,
"Send BR_TRANSACTION_COMPLETE\n");
}
break;
case BINDER_WORK_NODE:
{
FAR struct binder_node *node =
container_of(w, struct binder_node, work);
int strong;
int weak;
binder_uintptr_t node_ptr = node->ptr;
binder_uintptr_t node_cookie = node->cookie;
int node_debug_id = node->debug_id;
int has_weak_ref;
int has_strong_ref;
FAR void *orig_ptr = ptr;
BUG_ON(proc != node->proc);
strong = node->internal_strong_refs || node->local_strong_refs;
weak = !list_is_empty(&node->refs) ||
node->local_weak_refs || node->tmp_refs || strong;
has_strong_ref = node->has_strong_ref;
has_weak_ref = node->has_weak_ref;
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 %" PRIx64 " %" PRIx64 " deleted\n",
proc->pid, thread->tid, node_debug_id, node_ptr,
node_cookie);
list_delete_init(&node->rb_node);
binder_inner_proc_unlock(proc);
binder_node_lock(node);
* 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,
"node %d %"PRIx64" %"PRIx64" state unchanged\n",
node_debug_id, node_ptr, 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:
{
FAR struct binder_ref_death *death;
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;
binder_debug(BINDER_DEBUG_THREADS,
"Send %s\n", BINDER_BR_STR(cmd));
binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION, "%s %"PRIx64"\n",
BINDER_BR_STR(cmd), cookie);
if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
{
binder_inner_proc_unlock(proc);
kmm_free(death);
}
else
{
binder_enqueue_work_ilocked(w, &proc->delivered_death);
binder_inner_proc_unlock(proc);
}
put_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
put_value(cookie, (binder_unaligned_uintptr_t *)ptr);
ptr += sizeof(binder_uintptr_t);
if (cmd == BR_DEAD_BINDER)
{
goto done;
}
}
break;
default:
{
binder_inner_proc_unlock(proc);
binder_debug(BINDER_DEBUG_ERROR, "bad work type %d\n", w->type);
break;
}
}
if (!t)
{
continue;
}
BUG_ON(t->buffer == NULL);
if (t->buffer->target_node)
{
FAR struct binder_node *target_node = t->buffer->target_node;
trd->target.ptr = target_node->ptr;
trd->cookie = target_node->cookie;
binder_transaction_priority(thread, t, target_node);
cmd = BR_TRANSACTION;
}
else
{
trd->target.ptr = 0;
trd->cookie = 0;
cmd = BR_REPLY;
}
binder_debug(BINDER_DEBUG_THREADS, "Send %s", BINDER_BR_STR(cmd));
trd->code = t->code;
trd->flags = t->flags;
trd->sender_euid = geteuid();
t_from = binder_get_txn_from(t);
if (t_from)
{
trd->sender_pid = t_from->tid;
}
else
{
trd->sender_pid = 0;
}
ret = binder_apply_fd_fixups(proc, t);
if (ret)
{
struct binder_buffer *bbuffer = t->buffer;
if (t_from)
{
binder_thread_dec_tmpref(t_from);
}
bbuffer->transaction = NULL;
binder_cleanup_transaction(t, "fd fixups failed", BR_FAILED_REPLY);
binder_free_buf(proc, thread, bbuffer, true);
binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
"%stransaction %d fd fixups failed %d/%d, line %d\n",
(!!(t->flags & TF_ONE_WAY)) ? "async " :
(cmd == BR_REPLY ? "reply " : ""),
t->debug_id, BR_FAILED_REPLY, ret, __LINE__);
if (cmd == BR_REPLY)
{
cmd = BR_FAILED_REPLY;
put_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
binder_debug(BINDER_DEBUG_THREADS, "Send BR_FAILED_REPLY\n");
break;
}
continue;
}
trd->data_size = t->buffer->data_size;
trd->offsets_size = t->buffer->offsets_size;
trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data;
trd->data.ptr.offsets = trd->data.ptr.buffer +
ALIGN(t->buffer->data_size, sizeof(void *));
tr.secctx = t->security_ctx;
if (t->security_ctx)
{
cmd = BR_TRANSACTION_SEC_CTX;
trsize = sizeof(tr);
binder_debug(BINDER_DEBUG_THREADS,
"Send BR_TRANSACTION_SEC_CTX\n");
}
put_value(cmd, (uint32_t *)ptr);
ptr += sizeof(uint32_t);
memcpy(ptr, &tr, trsize);
ptr += trsize;
binder_debug(BINDER_DEBUG_TRANSACTION,
"%s %d %d:%d, size"
" %d-%d ptr %"PRIx64"-%"PRIx64"\n",
(cmd == BR_TRANSACTION_SEC_CTX) ?
"BR_TRANSACTION_SEC_CTX" : BINDER_BR_STR(cmd),
t->debug_id, t_from ? t_from->proc->pid : 0,
t_from ? t_from->tid : 0,
t->buffer->data_size, t->buffer->offsets_size,
trd->data.ptr.buffer, trd->data.ptr.offsets);
if (t_from)
{
binder_thread_dec_tmpref(t_from);
}
t->buffer->allow_user_free = 1;
if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY))
{
binder_inner_proc_lock(thread->proc);
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
binder_inner_proc_unlock(thread->proc);
}
else
{
binder_free_transaction(t);
}
break;
}
done:
*consumed = ptr - buffer;
binder_inner_proc_lock(proc);
if (proc->requested_threads == 0 &&
list_is_empty(&thread->proc->waiting_threads) &&
proc->requested_threads_started < proc->max_threads &&
(thread->looper &
(BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)))
{
proc->requested_threads++;
binder_inner_proc_unlock(proc);
put_value(BR_SPAWN_LOOPER, (uint32_t *)buffer);
binder_debug(BINDER_DEBUG_THREADS, "Send BR_SPAWN_LOOPER\n");
}
else
{
binder_inner_proc_unlock(proc);
}
return 0;
}
void binder_release_work(FAR struct binder_proc *proc,
FAR struct list_node *list)
{
FAR struct binder_work *w;
while (1)
{
binder_inner_proc_lock(proc);
w = binder_dequeue_work_head_ilocked(list);
binder_inner_proc_unlock(proc);
if (!w)
{
return;
}
switch (w->type)
{
case BINDER_WORK_TRANSACTION:
{
FAR struct binder_transaction *t;
t = container_of(w, struct binder_transaction, work);
binder_cleanup_transaction(t, "process died.", BR_DEAD_REPLY);
}
break;
case BINDER_WORK_RETURN_ERROR:
{
#ifdef CONFIG_BINDER_DRIVER_DEBUG
struct binder_error *e =
container_of(w, struct binder_error, work);
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered TRANSACTION_ERROR: %s\n",
BINDER_BR_STR(e->cmd));
#endif
}
break;
case BINDER_WORK_TRANSACTION_COMPLETE:
{
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered TRANSACTION_COMPLETE\n");
kmm_free(w);
}
break;
case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
case BINDER_WORK_CLEAR_DEATH_NOTIFICATION:
{
FAR struct binder_ref_death *death;
death = container_of(w, struct binder_ref_death, work);
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered death notification, %" PRIx64 "\n",
death->cookie);
kmm_free(death);
}
break;
case BINDER_WORK_NODE:
{
break;
}
default:
{
binder_debug(BINDER_DEBUG_ERROR,
"unexpected work type, %d, not freed\n", w->type);
break;
}
}
}
}
int binder_thread_release(FAR struct binder_proc *proc,
FAR struct binder_thread *thread)
{
FAR struct binder_transaction *t;
FAR struct binder_transaction *send_reply = NULL;
int active_transactions = 0;
FAR struct binder_transaction *last_t = NULL;
binder_inner_proc_lock(thread->proc);
* 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++;
* survives while we are releasing it
*/
thread->tmp_ref++;
list_delete_init(&thread->thread_node);
t = thread->transaction_stack;
if (t)
{
nxmutex_lock(&t->lock);
if (t->to_thread == thread)
{
send_reply = t;
}
}
thread->is_dead = true;
while (t)
{
last_t = t;
active_transactions++;
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"release %d:%d transaction %d %s, still active\n",
proc->pid, thread->tid, t->debug_id,
(t->to_thread == thread) ? "in" : "out");
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;
}
else if (t->from == thread)
{
t->from = NULL;
t = t->from_parent;
}
else
{
BUG_ON(1);
}
nxmutex_unlock(&last_t->lock);
if (t)
{
nxmutex_lock(&t->lock);
}
}
* poll data structures holding it.
*/
if (thread->looper & BINDER_LOOPER_STATE_POLL)
{
wake_up_pollfree(thread);
}
binder_inner_proc_unlock(thread->proc);
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;
}