db845c: qcom: Add userspace tools to talk to dsp and modem

Add Qcom userspace tools and their respective sepolicy rules.

Userspace tools are downloaded from following github:

To trigger loading of wlan firmware on SDM845
git clone https://github.com/andersson/pd-mapper

Userspace reference for net/qrtr in the Linux kernel
git clone https://github.com/andersson/qrtr

Qualcomm Remote Filesystem Service Implementation
git clone https://github.com/andersson/rmtfs

Trivial File Transfer Protocol server over AF_QIPCRTR
git clone https://github.com/andersson/tqftpserv

Change-Id: Ic466af6fef010a9b71c90e38205f49a876b001e2
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Signed-off-by: John Stultz <john.stultz@linaro.org>
diff --git a/qcom/tqftpserv/Android.bp b/qcom/tqftpserv/Android.bp
new file mode 100644
index 0000000..b2c5090
--- /dev/null
+++ b/qcom/tqftpserv/Android.bp
@@ -0,0 +1,9 @@
+cc_binary {
+    name: "tqftpserv",
+    vendor: true,
+    srcs: [
+        "tqftpserv.c",
+        "translate.c",
+    ],
+    shared_libs: ["libqrtr"],
+}
diff --git a/qcom/tqftpserv/LICENSE b/qcom/tqftpserv/LICENSE
new file mode 100644
index 0000000..cd92517
--- /dev/null
+++ b/qcom/tqftpserv/LICENSE
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, Linaro Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
diff --git a/qcom/tqftpserv/Makefile b/qcom/tqftpserv/Makefile
new file mode 100644
index 0000000..60ad687
--- /dev/null
+++ b/qcom/tqftpserv/Makefile
@@ -0,0 +1,25 @@
+TQFTPSERV := tqftpserv
+
+CFLAGS := -Wall -g -O2
+LDFLAGS := -lqrtr
+
+prefix ?= /usr/local
+bindir := $(prefix)/bin
+servicedir := $(prefix)/lib/systemd/system
+
+SRCS := tqftpserv.c translate.c
+
+OBJS := $(SRCS:.c=.o)
+
+$(TQFTPSERV): $(OBJS)
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+tqftpserv.service: tqftpserv.service.in
+	@sed 's+TQFTPSERV_PATH+$(bindir)+g' $< > $@
+
+install: $(TQFTPSERV) tqftpserv.service
+	@install -D -m 755 $(TQFTPSERV) $(DESTDIR)$(bindir)/$(TQFTPSERV)
+	@install -D -m 644 tqftpserv.service $(DESTDIR)$(servicedir)/tqftpserv.service
+
+clean:
+	rm -f $(TQFTPSERV) $(OBJS) tqftpserv.service
diff --git a/qcom/tqftpserv/list.h b/qcom/tqftpserv/list.h
new file mode 100644
index 0000000..c3488e3
--- /dev/null
+++ b/qcom/tqftpserv/list.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016, Linaro Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef __LIST_H__
+#define __LIST_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({ \
+		const typeof(((type *)0)->member)*__mptr = (ptr);  \
+		(type *)((char *)__mptr - offsetof(type, member)); \
+		})
+#endif
+
+struct list_head {
+	struct list_head *prev;
+	struct list_head *next;
+};
+
+#define LIST_INIT(list) { &(list), &(list) }
+
+static inline void list_init(struct list_head *list)
+{
+	list->prev = list->next = list;
+}
+
+static inline bool list_empty(struct list_head *list)
+{
+	return list->next == list;
+}
+
+static inline void list_add(struct list_head *list, struct list_head *item)
+{
+	struct list_head *prev = list->prev;
+
+	item->next = list;
+	item->prev = prev;
+
+	prev->next = list->prev = item;
+}
+
+static inline void list_del(struct list_head *item)
+{
+	item->prev->next = item->next;
+	item->next->prev = item->prev;
+}
+
+#define list_for_each(item, list) \
+	for (item = (list)->next; item != list; item = item->next)
+
+#define list_for_each_safe(item, next, list) \
+	for (item = (list)->next, next = item->next; item != list; item = next, next = item->next)
+
+#define list_entry(item, type, member) \
+	container_of(item, type, member)
+
+#define list_entry_first(list, type, member) \
+	container_of((list)->next, type, member)
+
+#define list_entry_next(item, member) \
+	container_of((item)->member.next, typeof(*(item)), member)
+
+#define list_for_each_entry(item, list, member) \
+	for (item = list_entry_first(list, typeof(*(item)), member); \
+	     &item->member != list; \
+	     item = list_entry_next(item, member))
+
+#define list_for_each_entry_safe(item, next, list, member) \
+	for (item = list_entry_first(list, typeof(*(item)), member), \
+	     next = list_entry_next(item, member); \
+	     &item->member != list; \
+	     item = next, \
+	     next = list_entry_next(item, member)) \
+
+#endif
diff --git a/qcom/tqftpserv/tqftpserv.c b/qcom/tqftpserv/tqftpserv.c
new file mode 100644
index 0000000..55b667f
--- /dev/null
+++ b/qcom/tqftpserv/tqftpserv.c
@@ -0,0 +1,609 @@
+/*
+ * Copyright (c) 2018, Linaro Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <arpa/inet.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <libqrtr.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "list.h"
+#include "translate.h"
+
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+enum {
+	OP_RRQ = 1,
+	OP_WRQ,
+	OP_DATA,
+	OP_ACK,
+	OP_ERROR,
+	OP_OACK,
+};
+
+struct tftp_client {
+	struct list_head node;
+
+	struct sockaddr_qrtr sq;
+
+	int sock;
+	int fd;
+
+	size_t block;
+
+	size_t blksize;
+	size_t rsize;
+	size_t wsize;
+	unsigned int timeoutms;
+};
+
+static struct list_head readers = LIST_INIT(readers);
+static struct list_head writers = LIST_INIT(writers);
+
+static ssize_t tftp_send_data(struct tftp_client *client,
+			      unsigned int block, size_t offset)
+{
+	ssize_t len;
+	char *buf;
+	char *p;
+
+	buf = malloc(4 + client->blksize);
+	p = buf;
+
+	*p++ = 0;
+	*p++ = OP_DATA;
+
+	*p++ = (block >> 8) & 0xff;
+	*p++ = block & 0xff;
+
+	len = pread(client->fd, p, client->blksize, offset);
+	if (len <= 0) {
+		if (len < 0)
+			printf("[TQFTP] failed to read data\n");
+		free(buf);
+		return len;
+	}
+
+	p += len;
+
+	// printf("[TQFTP] Sending %zd bytes of DATA\n", p - buf);
+	len = send(client->sock, buf, p - buf, 0);
+
+	free(buf);
+
+	return len;
+}
+
+
+static int tftp_send_ack(int sock, int block)
+{
+	struct {
+		uint16_t opcode;
+		uint16_t block;
+	} ack = { htons(OP_ACK), htons(block) };
+
+	return send(sock, &ack, sizeof(ack), 0);
+}
+
+static int tftp_send_oack(int sock, size_t *blocksize, size_t *tsize,
+			  size_t *wsize, unsigned int *timeoutms, size_t *rsize)
+{
+	char buf[512];
+	char *p = buf;
+	int n;
+
+	*p++ = 0;
+	*p++ = OP_OACK;
+
+	if (blocksize) {
+		strcpy(p, "blksize");
+		p += 8;
+
+		n = sprintf(p, "%zd", *blocksize);
+		p += n;
+		*p++ = '\0';
+	}
+
+	if (timeoutms) {
+		strcpy(p, "timeoutms");
+		p += 10;
+
+		n = sprintf(p, "%d", *timeoutms);
+		p += n;
+		*p++ = '\0';
+	}
+
+	if (tsize && *tsize != -1) {
+		strcpy(p, "tsize");
+		p += 6;
+
+		n = sprintf(p, "%zd", *tsize);
+		p += n;
+		*p++ = '\0';
+	}
+
+	if (wsize) {
+		strcpy(p, "wsize");
+		p += 6;
+
+		n = sprintf(p, "%zd", *wsize);
+		p += n;
+		*p++ = '\0';
+	}
+
+	if (rsize) {
+		strcpy(p, "rsize");
+		p += 6;
+
+		n = sprintf(p, "%zd", *rsize);
+		p += n;
+		*p++ = '\0';
+	}
+
+	return send(sock, buf, p - buf, 0);
+}
+
+static int tftp_send_error(int sock, int code, const char *msg)
+{
+	size_t len;
+	char *buf;
+	int rc;
+
+	len = 4 + strlen(msg) + 1;
+
+	buf = calloc(1, len);
+	if (!buf)
+		return -1;
+
+	*(uint16_t*)buf = htons(OP_ERROR);
+	*(uint16_t*)(buf + 2) = htons(code);
+	strcpy(buf + 4, msg);
+
+	rc = send(sock, buf, len, 0);
+	free(buf);
+	return rc;
+}
+
+static void handle_rrq(const char *buf, size_t len, struct sockaddr_qrtr *sq)
+{
+	struct tftp_client *client;
+	const char *filename;
+	const char *value;
+	const char *mode;
+	const char *opt;
+	struct stat sb;
+	const char *p;
+	ssize_t tsize = -1;
+	size_t blksize = 512;
+	unsigned int timeoutms = 1000;
+	size_t rsize = 0;
+	size_t wsize = 0;
+	bool do_oack = false;
+	int sock;
+	int ret;
+	int fd;
+
+	p = buf + 2;
+
+	filename = p;
+	p += strlen(p) + 1;
+
+	mode = p;
+	p += strlen(p) + 1;
+
+	if (strcasecmp(mode, "octet")) {
+		/* XXX: error */
+		printf("[TQFTP] not octet, reject\n");
+		return;
+	}
+
+	printf("[TQFTP] RRQ: %s (%s)\n", filename, mode);
+
+	if (p < buf + len) {
+		do_oack = true;
+
+		while (p < buf + len) {
+			/* XXX: ensure we're not running off the end */
+			opt = p;
+			p += strlen(p) + 1;
+
+			/* XXX: ensure we're not running off the end */
+			value = p;
+			p += strlen(p) + 1;
+
+			if (!strcmp(opt, "blksize")) {
+				blksize = atoi(value);
+			} else if (!strcmp(opt, "timeoutms")) {
+				timeoutms = atoi(value);
+			} else if (!strcmp(opt, "tsize")) {
+				tsize = atoi(value);
+			} else if (!strcmp(opt, "rsize")) {
+				rsize = atoi(value);
+			} else if (!strcmp(opt, "wsize")) {
+				wsize = atoi(value);
+			} else {
+				printf("[TQFTP] Ignoring unknown option '%s'\n", opt);
+			}
+		}
+	}
+
+	sock = qrtr_open(0);
+	if (sock < 0) {
+		/* XXX: error */
+		printf("[TQFTP] unable to create new qrtr socket, reject\n");
+		return;
+	}
+
+	ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq));
+	if (ret < 0) {
+		/* XXX: error */
+		printf("[TQFTP] unable to connect new qrtr socket to remote\n");
+		return;
+	}
+
+	fd = translate_open(filename, O_RDONLY);
+	if (fd < 0) {
+		printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno);
+		tftp_send_error(sock, 1, "file not found");
+		return;
+	}
+
+	if (tsize != -1) {
+		fstat(fd, &sb);
+		tsize = sb.st_size;
+	}
+
+	client = calloc(1, sizeof(*client));
+	client->sq = *sq;
+	client->sock = sock;
+	client->fd = fd;
+	client->blksize = blksize;
+	client->rsize = rsize;
+	client->wsize = wsize;
+	client->timeoutms = timeoutms;
+
+	// printf("[TQFTP] new reader added\n");
+
+	list_add(&readers, &client->node);
+
+	if (do_oack) {
+		tftp_send_oack(client->sock, &blksize,
+			       tsize ? (size_t*)&tsize : NULL,
+			       wsize ? &wsize : NULL,
+			       &client->timeoutms,
+			       rsize ? &rsize: NULL);
+	} else {
+		tftp_send_data(client, 1, 0);
+	}
+}
+
+static void handle_wrq(const char *buf, size_t len, struct sockaddr_qrtr *sq)
+{
+	struct tftp_client *client;
+	const char *filename;
+	const char *mode;
+	int sock;
+	int ret;
+	int fd;
+
+	filename = buf + 2;
+	mode = buf + 2 + strlen(filename) + 1;
+
+	if (strcasecmp(mode, "octet")) {
+		/* XXX: error */
+		printf("[TQFTP] not octet, reject\n");
+		return;
+	}
+
+	printf("[TQFTP] WRQ: %s (%s)\n", filename, mode);
+
+	fd = translate_open(filename, O_WRONLY | O_CREAT);
+	if (fd < 0) {
+		/* XXX: error */
+		printf("[TQFTP] unable to open %s (%d), reject\n", filename, errno);
+		return;
+	}
+
+	sock = qrtr_open(0);
+	if (sock < 0) {
+		/* XXX: error */
+		printf("[TQFTP] unable to create new qrtr socket, reject\n");
+		return;
+	}
+
+	ret = connect(sock, (struct sockaddr *)sq, sizeof(*sq));
+	if (ret < 0) {
+		/* XXX: error */
+		printf("[TQFTP] unable to connect new qrtr socket to remote\n");
+		return;
+	}
+
+	client = calloc(1, sizeof(*client));
+	client->sq = *sq;
+	client->sock = sock;
+	client->fd = fd;
+
+	ret = tftp_send_ack(client->sock, 0);
+	if (ret < 0) {
+		printf("[TQFTP] unable to send ack\n");
+		close(sock);
+		close(fd);
+		free(client);
+		return;
+	}
+
+	// printf("[TQFTP] new writer added\n");
+
+	list_add(&writers, &client->node);
+}
+
+static int handle_reader(struct tftp_client *client)
+{
+	struct sockaddr_qrtr sq;
+	uint16_t block;
+	uint16_t last;
+	char buf[128];
+	socklen_t sl;
+	ssize_t len;
+	ssize_t n = 0;
+	int opcode;
+	int ret;
+
+	sl = sizeof(sq);
+	len = recvfrom(client->sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
+	if (len < 0) {
+		ret = -errno;
+		if (ret != -ENETRESET)
+			fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret);
+		return -1;
+	}
+
+	/* Drop unsolicited messages */
+	if (sq.sq_node != client->sq.sq_node ||
+	    sq.sq_port != client->sq.sq_port) {
+		printf("[TQFTP] Discarding spoofed message\n");
+		return -1;
+	}
+
+	opcode = buf[0] << 8 | buf[1];
+	if (opcode == OP_ERROR) {
+		buf[len] = '\0';
+		printf("[TQFTP] Remote returned an error: %s\n", buf + 4);
+		return -1;
+	} else if (opcode != OP_ACK) {
+		printf("[TQFTP] Expected ACK, got %d\n", opcode);
+		return -1;
+	}
+
+	last = buf[2] << 8 | buf[3];
+	// printf("[TQFTP] Got ack for %d\n", last);
+
+	for (block = last; block < last + client->wsize; block++) {
+		n = tftp_send_data(client, block + 1,
+				   block * client->blksize);
+		if (n < 0) {
+			printf("[TQFTP] Sent block %d failed: %zd\n", block + 1, n);
+			break;
+		}
+		// printf("[TQFTP] Sent block %d of %zd\n", block + 1, n);
+		if (n == 0)
+			break;
+	}
+
+	return 1;
+}
+
+static int handle_writer(struct tftp_client *client)
+{
+	struct sockaddr_qrtr sq;
+	uint16_t block;
+	size_t payload;
+	char buf[516];
+	socklen_t sl;
+	ssize_t len;
+	int opcode;
+	int ret;
+
+	sl = sizeof(sq);
+	len = recvfrom(client->sock, buf, sizeof(buf), 0, (void *)&sq, &sl);
+	if (len < 0) {
+		ret = -errno;
+		if (ret != -ENETRESET)
+			fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret);
+		return -1;
+	}
+
+	/* Drop unsolicited messages */
+	if (sq.sq_node != client->sq.sq_node ||
+	    sq.sq_port != client->sq.sq_port)
+		return -1;
+
+	opcode = buf[0] << 8 | buf[1];
+	block = buf[2] << 8 | buf[3];
+	if (opcode != OP_DATA) {
+		printf("[TQFTP] Expected DATA opcode, got %d\n", opcode);
+		tftp_send_error(client->sock, 4, "Expected DATA opcode");
+		return -1;
+	}
+
+	payload = len - 4;
+
+	ret = write(client->fd, buf + 4, payload);
+	if (ret < 0) {
+		/* XXX: report error */
+		printf("[TQFTP] failed to write data\n");
+		return -1;
+	}
+
+	tftp_send_ack(client->sock, block);
+
+	return payload == 512 ? 1 : 0;
+}
+
+static void client_close_and_free(struct tftp_client *client)
+{
+	list_del(&client->node);
+	close(client->sock);
+	close(client->fd);
+	free(client);
+}
+
+int main(int argc, char **argv)
+{
+	struct tftp_client *client;
+	struct tftp_client *next;
+	struct sockaddr_qrtr sq;
+	struct qrtr_packet pkt;
+	socklen_t sl;
+	ssize_t len;
+	char buf[4096];
+	fd_set rfds;
+	int nfds;
+	int opcode;
+	int ret;
+	int fd;
+
+	fd = qrtr_open(0);
+	if (fd < 0) {
+		fprintf(stderr, "failed to open qrtr socket\n");
+		exit(1);
+	}
+
+	ret = qrtr_publish(fd, 4096, 1, 0);
+	if (ret < 0) {
+		fprintf(stderr, "failed to publish service registry service\n");
+		exit(1);
+	}
+
+	for (;;) {
+		FD_ZERO(&rfds);
+		FD_SET(fd, &rfds);
+		nfds = fd;
+
+		list_for_each_entry(client, &writers, node) {
+			FD_SET(client->sock, &rfds);
+			nfds = MAX(nfds, client->sock);
+		}
+
+		list_for_each_entry(client, &readers, node) {
+			FD_SET(client->sock, &rfds);
+			nfds = MAX(nfds, client->sock);
+		}
+
+		ret = select(nfds + 1, &rfds, NULL, NULL, NULL);
+		if (ret < 0) {
+			if (errno == EINTR) {
+				continue;
+			} else {
+				fprintf(stderr, "select failed\n");
+				break;
+			}
+		}
+
+		list_for_each_entry_safe(client, next, &writers, node) {
+			if (FD_ISSET(client->sock, &rfds)) {
+				ret = handle_writer(client);
+				if (ret <= 0)
+					client_close_and_free(client);
+			}
+		}
+
+		list_for_each_entry_safe(client, next, &readers, node) {
+			if (FD_ISSET(client->sock, &rfds)) {
+				ret = handle_reader(client);
+				if (ret <= 0)
+					client_close_and_free(client);
+			}
+		}
+
+		if (FD_ISSET(fd, &rfds)) {
+			sl = sizeof(sq);
+			len = recvfrom(fd, buf, sizeof(buf), 0, (void *)&sq, &sl);
+			if (len < 0) {
+				ret = -errno;
+				if (ret != -ENETRESET)
+					fprintf(stderr, "[TQFTP] recvfrom failed: %d\n", ret);
+				return ret;
+			}
+
+			/* Ignore control messages */
+			if (sq.sq_port == QRTR_PORT_CTRL) {
+				ret = qrtr_decode(&pkt, buf, len, &sq);
+				if (ret < 0) {
+					fprintf(stderr, "[TQFTP] unable to decode qrtr packet\n");
+					return ret;
+				}
+
+				switch (pkt.type) {
+				case QRTR_TYPE_BYE:
+					// fprintf(stderr, "[TQFTP] got bye\n");
+					list_for_each_entry_safe(client, next, &writers, node) {
+						if (client->sq.sq_node == sq.sq_node)
+							client_close_and_free(client);
+					}
+					break;
+				case QRTR_TYPE_DEL_CLIENT:
+					// fprintf(stderr, "[TQFTP] got del_client\n");
+					list_for_each_entry_safe(client, next, &writers, node) {
+						if (!memcmp(&client->sq, &sq, sizeof(sq)))
+							client_close_and_free(client);
+					}
+					break;
+				}
+			} else {
+				if (len < 2)
+					continue;
+
+				opcode = buf[0] << 8 | buf[1];
+				switch (opcode) {
+				case OP_RRQ:
+					handle_rrq(buf, len, &sq);
+					break;
+				case OP_WRQ:
+					// printf("[TQFTP] write\n");
+					handle_wrq(buf, len, &sq);
+					break;
+				default:
+					printf("[TQFTP] unhandled op %d\n", opcode);
+					break;
+				}
+			}
+		}
+	}
+
+	close(fd);
+
+	return 0;
+}
diff --git a/qcom/tqftpserv/tqftpserv.service.in b/qcom/tqftpserv/tqftpserv.service.in
new file mode 100644
index 0000000..2cf828c
--- /dev/null
+++ b/qcom/tqftpserv/tqftpserv.service.in
@@ -0,0 +1,12 @@
+[Unit]
+Description=QRTR TFTP service
+Requires=qrtr-ns.service
+After=qrtr-ns.service
+
+[Service]
+ExecStart=TQFTPSERV_PATH/tqftpserv
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/qcom/tqftpserv/translate.c b/qcom/tqftpserv/translate.c
new file mode 100644
index 0000000..e95dee5
--- /dev/null
+++ b/qcom/tqftpserv/translate.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2019, Linaro Ltd.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "translate.h"
+
+#define READONLY_PATH	"/readonly/firmware/image/"
+#define READWRITE_PATH	"/readwrite/"
+
+#define FIRMWARE_BASE	"/lib/firmware/"
+
+/**
+ * translate_readonly() - open "file" residing with remoteproc firmware
+ * @file:	file requested, stripped of "/readonly/image/" prefix
+ *
+ * It is assumed that the readonly files requested by the client resides under
+ * /lib/firmware in the same place as its associated remoteproc firmware.  This
+ * function scans through all entries under /sys/class/remoteproc and read the
+ * dirname of each "firmware" file in an attempt to find, and open(2), the
+ * requested file.
+ *
+ * As these files are readonly, it's not possible to pass flags to open(2).
+ *
+ * Return: opened fd on success, -1 otherwise
+ */
+static int translate_readonly(const char *file)
+{
+	char firmware_value[PATH_MAX];
+	char firmware_attr[32];
+	char path[PATH_MAX];
+	struct dirent *de;
+	int firmware_fd;
+	DIR *class_dir;
+	int class_fd;
+	ssize_t n;
+	int fd = -1;
+
+	class_fd = open("/sys/class/remoteproc", O_RDONLY | O_DIRECTORY);
+	if (class_fd < 0) {
+		warn("failed to open remoteproc class");
+		return -1;
+	}
+
+	class_dir = fdopendir(class_fd);
+	if (!class_dir) {
+		warn("failed to opendir");
+		goto close_class;
+	}
+
+	while ((de = readdir(class_dir)) != NULL) {
+		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+			continue;
+
+		if (strlen(de->d_name) + sizeof("/firmware") > sizeof(firmware_attr))
+			continue;
+		strcpy(firmware_attr, de->d_name);
+		strcat(firmware_attr, "/firmware");
+
+		firmware_fd = openat(class_fd, firmware_attr, O_RDONLY);
+		if (firmware_fd < 0)
+			continue;
+
+		n = read(firmware_fd, firmware_value, sizeof(firmware_value));
+		close(firmware_fd);
+		if (n < 0) {
+			continue;
+		}
+
+		if (strlen(FIRMWARE_BASE) + strlen(firmware_value) + 1 +
+		    strlen(file) + 1 > sizeof(path))
+			continue;
+
+		strcpy(path, FIRMWARE_BASE);
+		strcat(path, dirname(firmware_value));
+		strcat(path, "/");
+		strcat(path, file);
+
+		fd = open(path, O_RDONLY);
+		if (fd >= 0)
+			break;
+
+		if (errno != ENOENT)
+			warn("failed to open %s", path);
+	}
+
+	closedir(class_dir);
+
+close_class:
+	close(class_fd);
+
+	return fd;
+}
+
+/**
+ * translate_readwrite() - open "file" from a temporary directory
+ * @file:	relative path of the requested file, with /readwrite/ stripped
+ * @flags:	flags to be passed to open(2)
+ *
+ * Return: opened fd on success, -1 otherwise
+ */
+static int translate_readwrite(const char *file, int flags)
+{
+	int base;
+	int ret;
+	int fd;
+
+	ret = mkdir("/tmp/tqftpserv", 0700);
+	if (ret < 0 && errno != EEXIST) {
+		warn("failed to create /tmp/tqftpserv");
+		return -1;
+	}
+
+	base = open("/tmp/tqftpserv", O_RDONLY | O_DIRECTORY);
+	if (base < 0) {
+		warn("failed top open /tmp/tqftpserv");
+		return -1;
+	}
+
+	fd = openat(base, file, flags, 0600);
+	close(base);
+	if (fd < 0)
+		warn("failed to open %s", file);
+
+	return fd;
+}
+
+/**
+ * translate_open() - open file after translating path
+ *
+
+ * Strips /readonly/firmware/image and search among remoteproc firmware.
+ * Replaces /readwrite with a temporary directory.
+
+ */
+int translate_open(const char *path, int flags)
+{
+	if (!strncmp(path, READONLY_PATH, strlen(READONLY_PATH)))
+		return translate_readonly(path + strlen(READONLY_PATH));
+	else if (!strncmp(path, READWRITE_PATH, strlen(READWRITE_PATH)))
+		return translate_readwrite(path + strlen(READWRITE_PATH), flags);
+
+	fprintf(stderr, "invalid path %s, rejecting\n", path);
+	errno = ENOENT;
+	return -1;
+}
diff --git a/qcom/tqftpserv/translate.h b/qcom/tqftpserv/translate.h
new file mode 100644
index 0000000..ade2d09
--- /dev/null
+++ b/qcom/tqftpserv/translate.h
@@ -0,0 +1,6 @@
+#ifndef __TRANSLATE_H__
+#define __TRANSLATE_H__
+
+int translate_open(const char *path, int flags);
+
+#endif