You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ac...@apache.org on 2020/11/08 16:12:01 UTC

[incubator-nuttx-apps] 02/02: system: add Android Debug Bridge daemon

This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx-apps.git

commit c1c488e835f7433ddec4de57024a4e3444359f96
Author: Simon Piriou <sp...@gmail.com>
AuthorDate: Mon Oct 26 17:18:48 2020 +0100

    system: add Android Debug Bridge daemon
---
 system/adb/Kconfig          | 158 +++++++++++++++++++++++++++++
 system/adb/Make.defs        |  23 +++++
 system/adb/Makefile         |  92 +++++++++++++++++
 system/adb/adb_banner.c     | 117 ++++++++++++++++++++++
 system/adb/adb_main.c       | 119 ++++++++++++++++++++++
 system/adb/logcat_service.c | 213 +++++++++++++++++++++++++++++++++++++++
 system/adb/shell_pipe.c     | 237 ++++++++++++++++++++++++++++++++++++++++++++
 system/adb/shell_pipe.h     |  56 +++++++++++
 system/adb/shell_service.c  | 209 ++++++++++++++++++++++++++++++++++++++
 9 files changed, 1224 insertions(+)

diff --git a/system/adb/Kconfig b/system/adb/Kconfig
new file mode 100644
index 0000000..83a8019
--- /dev/null
+++ b/system/adb/Kconfig
@@ -0,0 +1,158 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+menuconfig SYSTEM_ADBD
+	tristate "ADB daemon application"
+	default n
+	---help---
+		Enable support for adb daemon.
+
+if SYSTEM_ADBD
+
+config ADBD_PROGNAME
+	string "Program name"
+	default "adbd"
+	---help---
+		This is the name of the program that will be used.
+
+config ADBD_STACKSIZE
+	int "Stack size"
+	default DEFAULT_TASK_STACKSIZE
+	---help---
+		The size of stack allocated for the adb daemon task.
+
+config ADBD_PRIORITY
+	int "Task priority"
+	default 100
+	---help---
+		The priority of the adb daemon task.
+
+config ADBD_AUTHENTICATION
+	bool "Authentication support"
+	default n
+	---help---
+		Enable authentication for adb daemon.
+
+if ADBD_AUTHENTICATION
+
+config ADBD_AUTH_PUBKEY
+	bool "Public key authentication"
+	default n
+	---help---
+		Enable hook to accept new public keys.
+
+config ADBD_TOKEN_SIZE
+	int "Authentication token size"
+	default 20
+
+endif # ADBD_AUTHENTICATION
+
+if ! BOARDCTL_UNIQUEID
+config ADBD_DEVICE_ID
+	string "Default adb device id"
+	default ""
+endif # BOARDCTL_UNIQUEID
+
+config ADBD_PRODUCT_NAME
+	string "Default adb product name"
+	default "adb dev"
+
+config ADBD_PRODUCT_MODEL
+	string "Default adb product model"
+	default "adb board"
+
+config ADBD_PRODUCT_DEVICE
+	string "Default adb product device"
+	default "NuttX device"
+
+config ADBD_FEATURES
+	string "Default adb server features list"
+	default "cmd"
+
+config ADBD_PAYLOAD_SIZE
+	int "Normal ADB frame size"
+	default 64
+	---help---
+		Normal frame size in bytes.
+
+config ADBD_CNXN_PAYLOAD_SIZE
+	int "Connection frame size"
+	default 1024
+	---help---
+		Connection frame is bigger than others.
+		Can be between 128 to 256 bytes in most of the cases.
+		If authentication is enabled, frame size must bigger
+		to receive public key from host (around 1024 bytes).
+
+config ADBD_FRAME_MAX
+	int "Frame pool size"
+	default 1
+	---help---
+		ADB frame pool size.
+
+config ADBD_TCP_SERVER
+	bool "Network socket transport support"
+	depends on NET_TCP
+	default n
+	---help---
+		Run adb daemon on network socket.
+
+config ADBD_TCP_SERVER_PORT
+	int "Network socket transport port"
+	depends on ADBD_TCP_SERVER
+	default 5555
+	---help---
+		Port used by adb daemon socket server
+
+config ADBD_USB_SERVER
+	bool "USB transport support"
+	depends on USBADB
+	default n
+	---help---
+		Run adb daemon on USB bus
+
+config ADBD_LOGCAT_SERVICE
+	bool "ADB logcat support"
+	depends on RAMLOG_SYSLOG
+	default n
+	---help---
+		Enable "adb logcat" feature.
+
+config ADBD_FILE_SERVICE
+	bool "ADB file sync support"
+	default n
+	---help---
+		Enable "adb ls/push/pull" feature.
+
+ config ADBD_SHELL_SERVICE
+	bool "ADB shell support"
+	depends on NSH_CONSOLE
+	default n
+	---help---
+		Enable "adb shell" feature.
+
+config ADBD_FILE_SYMLINK
+	bool "File service symlink support"
+	default n
+	depends on PSEUDOFS_SOFTLINKS
+	---help---
+		Enable fs symlink support.
+
+config ADBD_BOARD_INIT
+	bool "Board initialization"
+	depends on LIB_BOARDCTL
+	default n
+	---help---
+		Setup board before running adb daemon.
+
+config ADBD_NET_INIT
+    bool "Network initialization"
+    default n
+    depends on NET
+    select NETUTILS_NETINIT
+    ---help---
+        This option enables/disables all network initialization in ADB server.
+
+endif # SYSTEM_ADBD
diff --git a/system/adb/Make.defs b/system/adb/Make.defs
new file mode 100644
index 0000000..ead70b7
--- /dev/null
+++ b/system/adb/Make.defs
@@ -0,0 +1,23 @@
+############################################################################
+# system/adb/Make.defs
+#
+# 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.
+#
+############################################################################
+
+ifneq ($(CONFIG_SYSTEM_ADBD),)
+CONFIGURED_APPS += $(APPDIR)/system/adb
+endif
diff --git a/system/adb/Makefile b/system/adb/Makefile
new file mode 100644
index 0000000..f5e7778
--- /dev/null
+++ b/system/adb/Makefile
@@ -0,0 +1,92 @@
+############################################################################
+# system/adb/Makefile
+#
+# 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.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+ADB_DIR := $(APPDIR)/system/adb
+CONFIG_ADBD_URL ?= "https://github.com/spiriou/microADB.git"
+CONFIG_ADBD_VERSION ?= bbd1e74bd795aa2fc53eae2b76bff993d6ccaa37
+
+ADB_UNPACKNAME := microADB
+ADB_UNPACKDIR := $(ADB_DIR)/$(ADB_UNPACKNAME)
+
+$(ADB_UNPACKDIR):
+	@echo "Downloading: $(ADB_UNPACKNAME)"
+	$(call DELDIR, "$@")
+	$(Q) mkdir "$@"
+	$(Q) cd "$@" && git init && \
+	git remote add origin "$(CONFIG_ADBD_URL)" && \
+	git fetch origin $(CONFIG_ADBD_VERSION) --depth=1 && \
+	git reset --hard FETCH_HEAD
+
+# adb server app
+
+PROGNAME  := $(CONFIG_ADBD_PROGNAME)
+PRIORITY  := $(CONFIG_ADBD_PRIORITY)
+STACKSIZE := $(CONFIG_ADBD_STACKSIZE)
+MODULE    := $(CONFIG_ADB_SERVER)
+
+# Files
+
+MAINSRC := adb_main.c
+CSRCS += adb_banner.c
+CSRCS += $(ADB_UNPACKNAME)/adb_client.c
+CSRCS += $(ADB_UNPACKNAME)/adb_frame.c
+
+CSRCS += $(ADB_UNPACKNAME)/hal/hal_uv.c
+CSRCS += $(ADB_UNPACKNAME)/hal/hal_uv_packet.c
+
+CFLAGS += -D__NUTTX__=1
+CFLAGS += -I$(ADB_UNPACKNAME)
+
+ifeq ($(CONFIG_ADBD_TCP_SERVER),y)
+CSRCS += $(ADB_UNPACKNAME)/hal/hal_uv_client_tcp.c
+endif
+
+ifeq ($(CONFIG_ADBD_USB_SERVER),y)
+CSRCS += $(ADB_UNPACKNAME)/hal/hal_uv_client_usb.c
+endif
+
+ifeq ($(CONFIG_ADBD_AUTHENTICATION),y)
+CSRCS += $(ADB_UNPACKNAME)/adb_auth_key.c
+endif
+
+ifeq ($(CONFIG_ADBD_FILE_SERVICE),y)
+CSRCS += $(ADB_UNPACKNAME)/file_sync_service.c
+endif
+
+ifeq ($(CONFIG_ADBD_LOGCAT_SERVICE),y)
+CSRCS += logcat_service.c
+endif
+
+ifeq ($(CONFIG_ADBD_SHELL_SERVICE),y)
+CSRCS += shell_service.c
+CSRCS += shell_pipe.c
+endif
+
+context:: $(ADB_UNPACKDIR)
+
+clean::
+	$(call DELFILE, $(OBJS))
+
+distclean::
+	$(call DELDIR, $(ADB_UNPACKDIR))
+
+include $(APPDIR)/Application.mk
diff --git a/system/adb/adb_banner.c b/system/adb/adb_banner.c
new file mode 100644
index 0000000..e446756
--- /dev/null
+++ b/system/adb/adb_banner.c
@@ -0,0 +1,117 @@
+/****************************************************************************
+ * system/adb/adb_main.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
+ ****************************************************************************/
+
+#include "adb.h"
+#ifdef CONFIG_BOARDCTL_UNIQUEID
+#include <sys/boardctl.h>
+#include <string.h>
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int adb_fill_connect_data(char *buf, size_t bufsize)
+{
+  size_t len;
+  size_t remaining = bufsize;
+
+#ifdef CONFIG_BOARDCTL_UNIQUEID
+  /* Get board id */
+
+  int ret;
+  uint8_t board_id[CONFIG_BOARDCTL_UNIQUEID_SIZE];
+
+  memset(board_id, 0, CONFIG_BOARDCTL_UNIQUEID_SIZE);
+  ret = boardctl(BOARDIOC_UNIQUEID, (uintptr_t)board_id);
+
+  if (ret)
+    {
+      /* Failed to get board id */
+
+      adb_log("failed to get board id\n");
+      len = snprintf(buf, remaining, "device::");
+    }
+  else
+    {
+      /* FIXME only keep first 4 bytes */
+
+      len = snprintf(buf, remaining, "device:%x:", *(uint32_t *)board_id);
+    }
+#else
+  len = snprintf(buf, remaining, "device:" CONFIG_ADBD_DEVICE_ID ":");
+#endif
+
+  if (len >= remaining)
+    {
+      return -1;
+    }
+
+#ifdef CONFIG_ADBD_PRODUCT_NAME
+  remaining -= len;
+  buf += len;
+  len = snprintf(buf, remaining,
+                 "ro.product.name=" CONFIG_ADBD_PRODUCT_NAME ";");
+
+  if (len >= remaining)
+    {
+      return bufsize;
+    }
+#endif
+
+#ifdef CONFIG_ADBD_PRODUCT_MODEL
+  remaining -= len;
+  buf += len;
+  len = snprintf(buf, remaining,
+                 "ro.product.model=" CONFIG_ADBD_PRODUCT_MODEL ";");
+
+  if (len >= remaining)
+    {
+      return bufsize;
+    }
+#endif
+
+#ifdef CONFIG_ADBD_PRODUCT_DEVICE
+  remaining -= len;
+  buf += len;
+  len = snprintf(buf, remaining,
+                 "ro.product.device=" CONFIG_ADBD_PRODUCT_DEVICE ";");
+
+  if (len >= remaining)
+    {
+      return bufsize;
+    }
+#endif
+
+  remaining -= len;
+  buf += len;
+  len = snprintf(buf, remaining, "features=" CONFIG_ADBD_FEATURES);
+
+  if (len >= remaining)
+    {
+      return bufsize;
+    }
+
+  return bufsize - remaining + len;
+}
diff --git a/system/adb/adb_main.c b/system/adb/adb_main.c
new file mode 100644
index 0000000..d80c746
--- /dev/null
+++ b/system/adb/adb_main.c
@@ -0,0 +1,119 @@
+/****************************************************************************
+ * system/adb/adb_main.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
+ ****************************************************************************/
+
+#include "adb.h"
+
+#ifdef CONFIG_ADBD_BOARD_INIT
+#include <sys/boardctl.h>
+#endif
+
+#ifdef CONFIG_ADBD_NET_INIT
+#include "netutils/netinit.h"
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+void adb_log_impl(FAR const char *func, int line, FAR const char *fmt, ...)
+{
+  va_list ap;
+  fprintf(stderr, "%s (%d): ", func, line);
+
+  va_start(ap, fmt);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+}
+
+int main(int argc, FAR char **argv)
+{
+  UNUSED(argc);
+  UNUSED(argv);
+
+  adb_context_t *ctx;
+
+#ifdef CONFIG_ADBD_BOARD_INIT
+{
+  boardctl(BOARDIOC_INIT, 0);
+
+#if defined(CONFIG_ADBD_USB_SERVER) && \
+    defined(CONFIG_USBDEV_COMPOSITE) && \
+    defined (CONFIG_BOARDCTL_USBDEVCTRL)
+
+  /* Setup composite USB device */
+
+  struct boardioc_usbdev_ctrl_s ctrl;
+  int ret;
+  FAR void *handle;
+
+  /* Perform architecture-specific initialization */
+
+  ctrl.usbdev   = BOARDIOC_USBDEV_COMPOSITE;
+  ctrl.action   = BOARDIOC_USBDEV_INITIALIZE;
+  ctrl.instance = 0;
+  ctrl.config   = 0;
+  ctrl.handle   = NULL;
+
+  ret = boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl);
+  if (ret < 0)
+    {
+      printf("boardctl(BOARDIOC_USBDEV_CONTROL) failed: %d\n", ret);
+      return 1;
+    }
+
+  /* Initialize the USB composite device device */
+
+  ctrl.usbdev   = BOARDIOC_USBDEV_COMPOSITE;
+  ctrl.action   = BOARDIOC_USBDEV_CONNECT;
+  ctrl.instance = 0;
+  ctrl.config   = 0;
+  ctrl.handle   = &handle;
+
+  ret = boardctl(BOARDIOC_USBDEV_CONTROL, (uintptr_t)&ctrl);
+  if (ret < 0)
+    {
+      printf("boardctl(BOARDIOC_USBDEV_CONTROL) failed: %d\n", ret);
+      return 1;
+    }
+#endif /* ADBD_USB_SERVER && USBDEV_COMPOSITE && BOARDCTL_USBDEVCTRL */
+}
+
+#endif /* CONFIG_ADBD_BOARD_INIT */
+
+#ifdef CONFIG_ADBD_NET_INIT
+  /* Bring up the network */
+
+  netinit_bringup();
+#endif
+
+  ctx = adb_hal_create_context();
+  if (!ctx)
+    {
+      return -1;
+    }
+
+  adb_hal_run(ctx);
+  adb_hal_destroy_context(ctx);
+  return 0;
+}
diff --git a/system/adb/logcat_service.c b/system/adb/logcat_service.c
new file mode 100644
index 0000000..c65f3de
--- /dev/null
+++ b/system/adb/logcat_service.c
@@ -0,0 +1,213 @@
+/****************************************************************************
+ * system/adb/logcat_service_uv.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
+ ****************************************************************************/
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include <nuttx/syslog/ramlog.h>
+#include <unistd.h>
+
+#include "adb.h"
+#include "logcat_service.h"
+#include "hal/hal_uv_priv.h"
+
+/****************************************************************************
+ * Private types
+ ****************************************************************************/
+
+typedef struct alog_service_s
+{
+  adb_service_t service;
+  uv_poll_t poll;
+  int wait_ack;
+} alog_service_t;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void logcat_on_data_available(uv_poll_t * handle,
+                                     int status, int events);
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int alog_on_write(adb_service_t *service, apacket *p)
+{
+  UNUSED(p);
+  UNUSED(service);
+  return -1;
+}
+
+static void alog_on_kick(struct adb_service_s *service)
+{
+  alog_service_t *svc = container_of(service, alog_service_t, service);
+  if (!svc->wait_ack)
+    {
+      int ret;
+      ret = uv_poll_start(&svc->poll, UV_READABLE, logcat_on_data_available);
+      assert(ret == 0);
+    }
+}
+
+static int alog_on_ack(adb_service_t *service, apacket *p)
+{
+  UNUSED(p);
+  alog_service_t *svc = container_of(service, alog_service_t, service);
+  svc->wait_ack = 0;
+  alog_on_kick(service);
+  return 0;
+}
+
+static void close_cb(uv_handle_t *handle)
+{
+  alog_service_t *service = container_of(handle, alog_service_t, poll);
+  free(service);
+}
+
+static void alog_close(struct adb_service_s *service)
+{
+  int fd;
+  int ret;
+  alog_service_t *svc = container_of(service, alog_service_t, service);
+
+  ret = uv_fileno((uv_handle_t *)&svc->poll, &fd);
+  assert(ret == 0);
+
+  close(fd);
+  uv_close((uv_handle_t *)&svc->poll, close_cb);
+}
+
+static const adb_service_ops_t logcat_ops =
+{
+  .on_write_frame = alog_on_write,
+  .on_ack_frame   = alog_on_ack,
+  .on_kick        = alog_on_kick,
+  .close          = alog_close
+};
+
+static void logcat_on_data_available(uv_poll_t * handle,
+                                     int status, int events)
+{
+  int ret;
+  int fd;
+  apacket_uv_t *ap;
+  alog_service_t *service = container_of(handle, alog_service_t, poll);
+  adb_client_uv_t *client = (adb_client_uv_t *)handle->data;
+
+  ap = adb_uv_packet_allocate(client, 0);
+  if (ap == NULL)
+    {
+      uv_poll_stop(handle);
+      return;
+    }
+
+  if (status)
+    {
+      adb_log("status error %d\n", status);
+
+      /* Fatal error, stop service */
+
+      goto exit_stop_service;
+    }
+
+  assert(uv_fileno((uv_handle_t *)handle, &fd) == 0);
+  ret = read(fd, ap->p.data, CONFIG_ADBD_PAYLOAD_SIZE);
+
+  if (ret < 0)
+    {
+      adb_log("frame read failed %d %d\n", ret, errno);
+      if (errno == EAGAIN)
+        {
+          /* TODO this should never happen */
+
+          goto exit_release_packet;
+        }
+
+      /* Fatal error, stop service */
+
+      goto exit_stop_service;
+    }
+
+  if (ret == 0)
+    {
+      goto exit_release_packet;
+    }
+
+  service->wait_ack = 1;
+  uv_poll_stop(handle);
+
+  ap->p.write_len = ret;
+  ap->p.msg.arg0 = service->service.id;
+  ap->p.msg.arg1 = service->service.peer_id;
+  adb_send_data_frame(&client->client, &ap->p);
+  return;
+
+exit_release_packet:
+  adb_hal_apacket_release(&client->client, &ap->p);
+  return;
+
+exit_stop_service:
+  adb_service_close(&client->client, &service->service, &ap->p);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+adb_service_t * logcat_service(adb_client_t *client, const char *params)
+{
+  int ret;
+  alog_service_t *service =
+      (alog_service_t *)malloc(sizeof(alog_service_t));
+
+  if (service == NULL)
+    {
+      return NULL;
+    }
+
+  service->service.ops = &logcat_ops;
+  service->wait_ack = 0;
+
+  /* TODO parse params string to extract logcat parameters */
+
+  ret = open(CONFIG_SYSLOG_DEVPATH, O_RDONLY | O_CLOEXEC);
+
+  if (ret < 0)
+    {
+      adb_log("failed to open %s (%d)\n", CONFIG_SYSLOG_DEVPATH, errno);
+      free(service);
+      return NULL;
+    }
+
+  uv_handle_t *handle = adb_uv_get_client_handle(client);
+  ret = uv_poll_init(handle->loop, &service->poll, ret);
+  assert(ret == 0);
+
+  service->poll.data = client;
+  alog_on_kick(&service->service);
+
+  return &service->service;
+}
diff --git a/system/adb/shell_pipe.c b/system/adb/shell_pipe.c
new file mode 100644
index 0000000..ad59b71
--- /dev/null
+++ b/system/adb/shell_pipe.c
@@ -0,0 +1,237 @@
+/****************************************************************************
+ * system/adb/shell_pipe.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
+ ****************************************************************************/
+
+#include <uv.h>
+#include <unistd.h>
+#include "adb.h"
+#include "shell_pipe.h"
+#include "hal/hal_uv_priv.h"
+
+#include <nshlib/nshlib.h>
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static void shell_on_data_available(uv_poll_t * handle,
+                                    int status, int events)
+{
+  int ret;
+  apacket_uv_t *ap;
+  shell_pipe_t *pipe = container_of(handle, shell_pipe_t, handle);
+
+  adb_client_t *client = (adb_client_t *)pipe->handle.data;
+
+  if (status)
+    {
+      adb_log("status error %d\n", status);
+
+      /* FIXME missing logic here */
+
+      pipe->on_data_cb(pipe, NULL);
+      return;
+    }
+
+  ap = adb_uv_packet_allocate((adb_client_uv_t *)client, 0);
+  if (ap == NULL)
+    {
+      /* frame allocation failed. Try again later */
+
+      uv_poll_stop(&pipe->handle);
+      return;
+    }
+
+  int nread = 0;
+  do
+    {
+      ret = read(handle->io_watcher.fd, &ap->p.data[nread], 1);
+
+      if (ret == 0)
+        {
+          /* EOF */
+
+          break;
+        }
+
+      if (ret < 0)
+        {
+          /* Revisit. EAGAIN should not happen but it happens a lot */
+
+          if (errno == EAGAIN)
+            {
+              if (nread <= 0)
+                {
+                  adb_hal_apacket_release(
+                    (adb_client_t *)pipe->handle.data, &ap->p);
+                  return;
+                }
+              break;
+            }
+
+          /* Release packet and forward error */
+
+          adb_hal_apacket_release((adb_client_t *)pipe->handle.data, &ap->p);
+          pipe->on_data_cb(pipe, NULL);
+          return;
+        }
+
+      /* FIXME CR LF conversion */
+
+      if (ap->p.data[nread++] == '\n')
+        {
+          ap->p.data[nread++] = '\r';
+        }
+    }
+  while (nread < CONFIG_ADBD_PAYLOAD_SIZE - 1);
+
+  ap->p.msg.data_length = nread;
+  pipe->on_data_cb(pipe, &ap->p);
+}
+
+static void shell_pipe_close_callback(uv_handle_t *handle)
+{
+  shell_pipe_t *pipe = container_of(handle, shell_pipe_t, handle);
+
+  /* Notify caller pipe is closed */
+
+  pipe->close_cb(pipe);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int shell_pipe_setup(adb_client_t *client, shell_pipe_t *apipe)
+{
+  apipe->handle.data = client;
+  return 0;
+}
+
+void shell_pipe_destroy(shell_pipe_t *pipe, void (*close_cb)(shell_pipe_t *))
+{
+  pipe->close_cb = close_cb;
+  close(pipe->write_fd);
+  uv_close((uv_handle_t *)&pipe->handle, shell_pipe_close_callback);
+}
+
+int shell_pipe_write(shell_pipe_t *pipe, const void *buf, size_t count)
+{
+  /* TODO revisit */
+
+  return write(pipe->write_fd, buf, count);
+}
+
+int shell_pipe_start(shell_pipe_t *pipe,
+                     void (*on_data_cb)(shell_pipe_t *, apacket *))
+{
+  pipe->on_data_cb = on_data_cb;
+  return uv_poll_start(&pipe->handle, UV_READABLE, shell_on_data_available);
+}
+
+int shell_pipe_exec(char * const argv[], shell_pipe_t *apipe,
+                    void (*on_data_cb)(shell_pipe_t *, apacket *))
+{
+  int ret;
+  int in_fds[2];
+  int out_fds[2];
+
+  adb_client_uv_t *client = (adb_client_uv_t *)apipe->handle.data;
+
+  /* Create pipe for stdin */
+
+  ret = pipe(in_fds);
+  assert(ret == 0);
+  ret = pipe(out_fds);
+  assert(ret == 0);
+
+  /* TODO check return code */
+
+  apipe->write_fd = in_fds[1];
+
+  /* Setup stdout (read: adb, write: child) */
+
+  ret = dup2(out_fds[1], 1);
+  assert(ret == 0);
+
+  ret = close(out_fds[1]);
+  assert(ret == 0);
+
+  ret = fcntl(out_fds[0], F_GETFD);
+  assert(ret >= 0);
+  ret = fcntl(out_fds[0], F_SETFD, ret | FD_CLOEXEC);
+  assert(ret == 0);
+  ret = fcntl(out_fds[0], F_GETFL);
+  assert(ret >= 0);
+  ret = fcntl(out_fds[0], F_SETFL, ret | O_NONBLOCK);
+  assert(ret >= 0);
+
+  /* Setup stdin */
+
+  ret = dup2(in_fds[0], 0);
+  assert(ret == 0);
+
+  ret = close(in_fds[0]);
+  assert(ret == 0);
+
+  ret = fcntl(in_fds[1], F_GETFD);
+  assert(ret >= 0);
+  ret = fcntl(in_fds[1], F_SETFD, ret | FD_CLOEXEC);
+  assert(ret == 0);
+  ret = fcntl(in_fds[1], F_GETFL);
+  assert(ret >= 0);
+  ret = fcntl(in_fds[1], F_SETFL, ret | O_NONBLOCK);
+  assert(ret == 0);
+
+  ret = uv_poll_init(
+      adb_uv_get_client_handle(client)->loop,
+      &apipe->handle, out_fds[0]);
+
+  /* TODO check return code */
+
+  assert(ret == 0);
+
+  /* Create shell process */
+
+  ret = task_create("ADB shell", CONFIG_SYSTEM_NSH_PRIORITY,
+                    CONFIG_SYSTEM_NSH_STACKSIZE, nsh_consolemain,
+                    argv);
+
+  /* Close stdin and stdout */
+
+  dup2(2, 0);
+  dup2(2, 1);
+
+  /* TODO check return code */
+
+  assert(ret >= 0);
+
+  /* Start listening shell process stdout */
+
+  ret = shell_pipe_start(apipe, on_data_cb);
+
+  /* TODO check return code */
+
+  assert(ret == 0);
+  return 0;
+}
diff --git a/system/adb/shell_pipe.h b/system/adb/shell_pipe.h
new file mode 100644
index 0000000..16ca80f
--- /dev/null
+++ b/system/adb/shell_pipe.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+ * system/adb/shell_pipe.h
+ *
+ * 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
+ ****************************************************************************/
+
+#include <uv.h>
+#include "adb.h"
+
+/****************************************************************************
+ * Private types
+ ****************************************************************************/
+
+struct shell_pipe_s
+{
+  uv_poll_t handle;
+  int write_fd;
+  void (*close_cb)(struct shell_pipe_s *);
+  void (*on_data_cb)(struct shell_pipe_s *, struct apacket_s *);
+};
+
+typedef struct shell_pipe_s shell_pipe_t;
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+int shell_pipe_setup(adb_client_t *client, shell_pipe_t *pipe);
+int shell_pipe_start(shell_pipe_t *pipe,
+                     void (*on_data_cb)(shell_pipe_t *, apacket *));
+void shell_pipe_destroy(shell_pipe_t *pipe,
+                        void (*close_cb)(shell_pipe_t *));
+int shell_pipe_write(shell_pipe_t *pipe, const void *buf, size_t count);
+
+int shell_pipe_exec(char * const argv[], shell_pipe_t *pipe,
+                    void (*on_data_cb)(shell_pipe_t *, apacket *));
+int shell_exec_builtin(const char *appname, FAR char *const *argv,
+                       shell_pipe_t *apipe);
diff --git a/system/adb/shell_service.c b/system/adb/shell_service.c
new file mode 100644
index 0000000..04bdfeb
--- /dev/null
+++ b/system/adb/shell_service.c
@@ -0,0 +1,209 @@
+/****************************************************************************
+ * system/adb/shell_service.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
+ ****************************************************************************/
+
+#include <stdlib.h>
+
+#include "adb.h"
+#include "shell_service.h"
+#include "shell_pipe.h"
+
+/****************************************************************************
+ * Private types
+ ****************************************************************************/
+
+typedef struct ash_service_s
+{
+  adb_service_t service;
+  shell_pipe_t pipe;
+  adb_client_t *client;
+} ash_service_t;
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static void exec_on_data_available(shell_pipe_t * pipe, apacket * p);
+
+static int shell_ack(adb_service_t *service, apacket *p);
+static int shell_write(adb_service_t *service, apacket *p);
+static void shell_close(struct adb_service_s *service);
+static void shell_kick(adb_service_t *service);
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static void exec_on_data_available(shell_pipe_t * pipe, apacket * p)
+{
+  ash_service_t *service = container_of(pipe, ash_service_t, pipe);
+
+  if (p->msg.data_length <= 0)
+    {
+      /* Got EOF */
+
+      adb_service_close(service->client, &service->service, p);
+      return;
+    }
+
+  p->write_len = p->msg.data_length;
+  p->msg.arg0 = service->service.id;
+  p->msg.arg1 = service->service.peer_id;
+  adb_send_data_frame(service->client, p);
+}
+
+static int shell_write(adb_service_t *service, apacket *p)
+{
+  int ret;
+  ash_service_t *svc = container_of(service, ash_service_t, service);
+  UNUSED(svc);
+
+  if (p->msg.data_length <= 0)
+    {
+      return -1;
+    }
+
+  ret = shell_pipe_write(&svc->pipe, p->data, p->msg.data_length);
+
+  if (ret < 0)
+    {
+      /* Shell process terminated, close service */
+
+      return -1;
+    }
+
+  assert(ret == p->msg.data_length);
+  return 0;
+}
+
+static int shell_ack(adb_service_t *service, apacket *p)
+{
+  UNUSED(service);
+  UNUSED(p);
+  return 0;
+}
+
+static void shell_kick(adb_service_t *service)
+{
+  int ret;
+  ash_service_t *svc = container_of(service, ash_service_t, service);
+  ret = shell_pipe_start(&svc->pipe, exec_on_data_available);
+
+  /* TODO handle return code */
+
+  assert(ret == 0);
+}
+
+static void shell_on_close(shell_pipe_t *pipe)
+{
+  ash_service_t *svc = container_of(pipe, ash_service_t, pipe);
+  free(svc);
+}
+
+static void shell_close(adb_service_t *service)
+{
+  ash_service_t *svc = container_of(service, ash_service_t, service);
+
+  /* FIXME missing logic here if shell process is still running */
+
+  shell_pipe_destroy(&svc->pipe, shell_on_close);
+}
+
+static const adb_service_ops_t shell_ops =
+{
+  .on_write_frame = shell_write,
+  .on_ack_frame   = shell_ack,
+  .on_kick        = shell_kick,
+  .close          = shell_close
+};
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+adb_service_t * shell_service(adb_client_t *client, const char *params)
+{
+  UNUSED(params);
+  UNUSED(client);
+
+  int ret;
+  char **argv;
+  const char *target;
+  ash_service_t *service =
+      (ash_service_t *)malloc(sizeof(ash_service_t));
+
+  if (service == NULL)
+    {
+      return NULL;
+    }
+
+  service->client = client;
+  service->service.ops = &shell_ops;
+
+  ret = shell_pipe_setup(client, &service->pipe);
+
+  /* TODO check return code */
+
+  assert(ret == 0);
+
+  /* Check parameters after "shell:" */
+
+  target = &params[6];
+
+  if (target[0] != 0)
+    {
+      /* Build argv: <nsh -c "command">
+       * argv[0] => "-c"
+       * argv[1] => command
+       * argv[2] => NULL
+       *
+       * malloc content:
+       * - x3 argv pointers
+       * - 3 characters: "-c\0"
+       * - space for command string
+       */
+
+      argv = malloc(sizeof(char *) * 3 + 3 + (strlen(target)+1));
+
+      argv[0] = (char *)&argv[3];
+      argv[1] = &((char *)&argv[3])[3];
+      argv[2] = NULL;
+      strcpy(argv[0], "-c");
+      strcpy(argv[1], target);
+    }
+  else
+    {
+      argv = NULL;
+    }
+
+  ret = shell_pipe_exec(argv, &service->pipe,
+                        exec_on_data_available);
+
+  /* TODO check return code */
+
+  assert(ret == 0);
+
+  free(argv);
+
+  return &service->service;
+}