aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Thompson <daniel.thompson@linaro.org>2017-07-30 08:09:38 +0100
committerDaniel Thompson <daniel.thompson@linaro.org>2017-07-31 17:26:28 +0100
commitd6c0026e2fe557ceb282b89b257003fbf4707104 (patch)
tree2f60f7fd0ea46148f1f91f71c06aaa389adb24cb
parent3e0af17f4d561c703a3148556eb76aef6f7fa779 (diff)
pdl: common: RDA support
-rw-r--r--pdl/common/Makefile48
-rw-r--r--pdl/common/packet.c301
-rw-r--r--pdl/common/pdl.c28
-rw-r--r--pdl/common/pdl_channel.c39
-rw-r--r--pdl/common/pdl_channel_usb.c85
-rw-r--r--pdl/common/pdl_debug.c48
-rw-r--r--pdl/common/pdl_engine.c141
7 files changed, 690 insertions, 0 deletions
diff --git a/pdl/common/Makefile b/pdl/common/Makefile
new file mode 100644
index 0000000000..527445b73a
--- /dev/null
+++ b/pdl/common/Makefile
@@ -0,0 +1,48 @@
+#
+# (C) Copyright 2000-2007
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# See file CREDITS for list of people who contributed to this
+# project.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+# MA 02111-1307 USA
+#
+
+include $(TOPDIR)/config.mk
+
+CPPFLAGS += -I ../include/
+CFLAGS += -I ../include -mno-unaligned-access
+LIB := $(obj)libpdl_common.o
+
+COBJS-y := packet.o pdl_channel.o pdl_channel_usb.o pdl_debug.o pdl_engine.o pdl.o
+
+COBJS := $(COBJS-y)
+SRCS := $(COBJS:.o=.c)
+OBJS := $(addprefix $(obj),$(COBJS))
+
+all: $(LIB)
+
+$(LIB): $(obj).depend $(OBJS)
+ $(call cmd_link_o_target, $(OBJS))
+
+#########################################################################
+
+# defines $(obj).depend target
+include $(SRCTREE)/rules.mk
+
+sinclude $(obj).depend
+
+#########################################################################
diff --git a/pdl/common/packet.c b/pdl/common/packet.c
new file mode 100644
index 0000000000..669d83cc52
--- /dev/null
+++ b/pdl/common/packet.c
@@ -0,0 +1,301 @@
+//#define DEBUG
+
+#include <asm/types.h>
+#include <common.h>
+#include "packet.h"
+#include "pdl_channel.h"
+#include <linux/string.h>
+#include "pdl_debug.h"
+#include "cmd_defs.h"
+#include <asm/unaligned.h>
+
+static enum {
+ PKT_IDLE = 0,
+ PKT_HEAD,
+ PKT_DATA,
+ PKT_ERROR
+} pkt_state;
+
+const static char *pdl_device_rsp[] = {
+ [ACK] = "device ack",
+ [PACKET_ERROR] = "packet error",
+ [INVALID_CMD] = "invalid cmd",
+ [UNKNOWN_CMD] = "unknown cmd",
+ [INVALID_ADDR] = "invalid address",
+ [INVALID_BAUDRATE] = "invalid baudrate",
+ [INVALID_PARTITION] = "invalid partition",
+ [INVALID_SIZE] = "invalid size",
+ [WAIT_TIMEOUT] = "wait timeout",
+ [VERIFY_ERROR] = "verify error",
+ [CHECKSUM_ERROR] = "checksum error",
+ [OPERATION_FAILED] = "operation failed",
+ [DEVICE_ERROR] = "device error",
+ [NO_MEMORY] = "no memory",
+ [DEVICE_INCOMPATIBLE] = "device incompatible",
+ [HW_TEST_ERROR] = "hardware test error",
+ [MD5_ERROR] = "md5 error",
+ [ACK_AGAIN_ERASE] = "ack again erase",
+ [ACK_AGAIN_FLASH] = "ack again flash",
+ [MAX_RSP] = "max response",
+};
+
+static int pkt_error = 0;
+static unsigned char cmd_buf[PDL_MAX_PKT_SIZE] __attribute__((__aligned__(32)));
+static struct pdl_channel *channel;
+
+int pdl_init_packet_channel(void)
+{
+ channel = pdl_get_channel();
+ return 0;
+}
+
+/*
+ * return value: 1, get packet ok;
+ * 0, not a host packet;
+ * -1, something error, check pkt_error for true cause;
+ * -2, don't get any packet
+ */
+int pdl_get_cmd_packet(struct pdl_packet *pkt)
+{
+ u32 pkt_size = 0;
+ u32 hdrsz = sizeof(struct packet_header);
+ u32 cmd_hdrsz = sizeof(struct command_header);
+ u32 bufsz = 0, totalsz = 0;
+ int i, wait = 0;
+
+ if (!pkt) {
+ pdl_error("packet error\n");
+ return -1;
+ }
+
+ if (!channel->tstc())
+ return -2;
+
+ /* looking for HOST_PACKET_TAG 0xAE */
+ while (1) {
+ bufsz = channel->tstc();
+ if (!bufsz)
+ return -2;
+ channel->read(cmd_buf, bufsz);
+ if (cmd_buf[0] == HOST_PACKET_TAG) {
+ /*pdl_info("Found host packet tag, %u bytes\n", bufsz);*/
+ break;
+ } else {
+ pdl_dbg("Drop a %u bytes packet:", bufsz);
+ for(i = 0; i < 8; i++)
+ pdl_dbg(" %02x", cmd_buf[i]);
+ pdl_dbg("\n");
+ }
+ }
+ totalsz = bufsz;
+
+ /* is a valid packet header? */
+ if (totalsz < hdrsz) {
+ pdl_error("Invaild packet, it's too small (%u bytes)\n", totalsz);
+ pkt_state = PKT_IDLE;
+ return -2;
+ }
+
+ /* packet header check */
+ pkt_state = PKT_HEAD;
+ memcpy(&pkt_size, &cmd_buf[1], 4);
+ if (cmd_buf[5] != HOST_PACKET_FLOWID ||
+ pkt_size > PDL_MAX_PKT_SIZE ||
+ pkt_size < sizeof(u32)) {
+ pdl_error("Invalid packet, flowid 0x%x, pkt_size %u\n",
+ cmd_buf[5], pkt_size);
+ pdl_dbg("packet data:");
+ for (i = 0; i < 8; i++)
+ pdl_dbg(" %02x", cmd_buf[i]);
+ pdl_dbg("\n");
+
+ pkt_state = PKT_IDLE;
+ return -2;
+ }
+ /*pdl_info("Got command packet, packet size %u bytes\n", pkt_size);*/
+
+ /* get all data */
+ if (pkt_size > totalsz - hdrsz) {
+ wait = 0;
+ while (1) {
+ bufsz = channel->tstc();
+ if (bufsz) {
+ u32 last = pkt_size - (totalsz - hdrsz);
+ bufsz = (bufsz > last) ? last : bufsz;
+ channel->read(&cmd_buf[totalsz], bufsz);
+ totalsz += bufsz;
+ wait = 0;
+ if (bufsz == last)
+ break;
+ } else {
+ if (wait++ >= 1000) {
+ pdl_error("wait packet data timeout, got %u bytes, need %u\n",
+ totalsz - hdrsz, pkt_size);
+ pkt_error = PACKET_ERROR;
+ return -1;
+ }
+ udelay(1000);
+ }
+ }
+ }
+
+ pkt_state = PKT_DATA;
+ memset(pkt, 0, sizeof(struct pdl_packet));
+ if (pkt_size < cmd_hdrsz) {
+ memcpy(pkt, &cmd_buf[hdrsz], pkt_size);
+ } else {
+ memcpy(pkt, &cmd_buf[hdrsz], cmd_hdrsz);
+ pkt->data = &cmd_buf[hdrsz+cmd_hdrsz];
+
+ /* packet data crc checking, if have 4 bytes crc in tail */
+ if (pkt_size >= cmd_hdrsz + pkt->cmd_header.data_size + sizeof(u32)) {
+ i = hdrsz + cmd_hdrsz + pkt->cmd_header.data_size;
+ u32 crc = 0, crc_now = 0;
+ memcpy(&crc, &cmd_buf[i], sizeof(u32));
+ crc_now = crc32(0, pkt->data, pkt->cmd_header.data_size);
+ if (crc != crc_now) {
+ pdl_error("packet data crc verify failed, expect %#x, got %#x\n", crc, crc_now);
+ pkt_error = CHECKSUM_ERROR;
+ return -1;
+ }
+ }
+ }
+ return 1;
+}
+
+
+int pdl_get_connect_packet(struct pdl_packet *pkt, u32 timeout)
+{
+ char ch = 0;
+ u8 header_buf[sizeof(struct packet_header)];
+ u32 pkt_size = 0;
+ u32 cnt = timeout; //timeout is in ms;
+
+ while (cnt) {
+ if(!channel->tstc()) {
+ mdelay(1);
+ //pdl_error("try to get\n");
+ cnt--;
+ continue;
+ }
+
+ ch = channel->getchar();
+ if (ch == HOST_PACKET_TAG) {
+ pkt_state = PKT_HEAD;
+ break;
+ } else {
+ pdl_info("oops, get 0x%x\n", ch);
+ continue;
+ }
+ }
+
+ if (cnt == 0 ) {
+ pkt_error = WAIT_TIMEOUT;
+ pdl_error("get connect timeout\n");
+ return -1;
+ }
+ header_buf[0] = ch;
+ while (1) {
+ u8 flowid;
+
+ switch (pkt_state) {
+ case PKT_IDLE:
+ break;
+ case PKT_HEAD:
+ channel->read(&header_buf[1], 5);
+ flowid = header_buf[5];
+ pkt_size = 0;
+ if (flowid == HOST_PACKET_FLOWID) {
+ //ok, this is the header
+ memcpy(&pkt_size, &header_buf[1], 4);
+ pkt_size = le32_to_cpu(pkt_size);
+ pkt_state = PKT_DATA;
+ pdl_vdbg("header right data_size 0x%x\n", pkt_size);
+ } else {
+ pkt_state = PKT_IDLE;
+ return 0;
+ }
+ break;
+ case PKT_DATA:
+ //diff the command data size and packet data size,check if it correct;
+ if (pkt_size > (sizeof(struct command_header) + 1)) {
+ pkt_error = INVALID_SIZE;
+ return -1;
+ }
+ channel->read(cmd_buf, pkt_size);
+ memcpy(pkt, cmd_buf, sizeof(struct command_header));
+
+ pdl_vdbg("pkt cmd type 0x%x data start 0x%x, size %x\n",
+ pkt->cmd_header.cmd_type,
+ pkt->cmd_header.data_addr,
+ pkt->cmd_header.data_size);
+ return 1;
+ case PKT_ERROR:
+ break;
+ }
+ }
+ return 1;
+}
+/*
+static void pdl_put_packet(struct packet *pkt)
+{
+}
+*/
+void pdl_put_cmd_packet(struct pdl_packet *pkt)
+{
+ memset(pkt, 0, sizeof (struct pdl_packet));
+ pkt_state = PKT_IDLE;
+ pkt_error = 0;
+}
+
+int pdl_get_packet_error(void)
+{
+ return pkt_error;
+}
+
+static int pdl_send_data(const u8 *data, u32 size, u8 flowid)
+{
+ struct packet_header *header = (struct packet_header *)cmd_buf;
+ int hdr_sz = sizeof(struct packet_header);
+
+ if(size > PDL_MAX_PKT_SIZE - hdr_sz) {
+ pdl_error("packet size is too large.\n");
+ return -1;
+ }
+
+ memset(cmd_buf, PDL_MAX_PKT_SIZE, 0);
+ memcpy(&cmd_buf[hdr_sz], data, size);
+
+ header->tag = PACKET_TAG;
+ header->flowid = flowid;
+ /* carefully, header->pkt_size may be unaligned */
+ put_unaligned(size, &header->pkt_size); /* header->pkt_size = size; */
+
+ channel->write(cmd_buf, hdr_sz + size);
+ return 0;
+}
+
+void pdl_send_rsp(int rsp)
+{
+ /*
+ * if wait timeout, the client may be exit, we dont send
+ * this error, for the new client will get a extra error
+ * response
+ */
+ if (rsp == WAIT_TIMEOUT)
+ return;
+
+ if (rsp != ACK) {
+ if (rsp < ACK || rsp > MAX_RSP)
+ rsp = DEVICE_ERROR;
+ pdl_info("send rsp '%s'\n", pdl_device_rsp[rsp]);
+ }
+
+ pdl_send_data((const u8 *)&rsp, sizeof(int), (rsp == ACK) ? FLOWID_ACK : FLOWID_ERROR);
+ pdl_dbg("send rsp '%s'\n", pdl_device_rsp[rsp]);
+}
+
+int pdl_send_pkt(const u8 *data, u32 size)
+{
+ return pdl_send_data(data, size, FLOWID_DATA);
+}
diff --git a/pdl/common/pdl.c b/pdl/common/pdl.c
new file mode 100644
index 0000000000..e3bbe27cba
--- /dev/null
+++ b/pdl/common/pdl.c
@@ -0,0 +1,28 @@
+#include <common.h>
+#include <asm/arch/hwcfg.h>
+#include <asm/arch/rda_sys.h>
+#include <pdl.h>
+
+int pdl_dbg_pdl = 0;
+int pdl_vdbg_pdl = 0;
+int pdl_dbg_usb_ep0 = 0;
+int pdl_dbg_usb_serial = 0;
+int pdl_dbg_rw_check = 0;
+int pdl_dbg_factory_part = 0;
+
+/*
+ check if should start pdl utilities
+*/
+int pdl_mode_get(void)
+{
+ u16 swcfg = rda_swcfg_get();
+
+ /*
+ if hwconfig is in force download and swcofing is not
+ in calibration mode
+ */
+ if (rda_bm_is_download() && !(swcfg & RDA_SW_CFG_BIT_4))
+ return 1;
+ else
+ return 0;
+}
diff --git a/pdl/common/pdl_channel.c b/pdl/common/pdl_channel.c
new file mode 100644
index 0000000000..313535423c
--- /dev/null
+++ b/pdl/common/pdl_channel.c
@@ -0,0 +1,39 @@
+
+#include "pdl_channel.h"
+#include "pdl_debug.h"
+
+static struct pdl_channel *serial_channel = NULL;
+
+void pdl_channel_register(struct pdl_channel *channel)
+{
+ if (channel) {
+ serial_channel = channel;
+ } else {
+ pdl_error("channel cannot register\n");
+ }
+
+ return;
+}
+
+struct pdl_channel *pdl_get_channel(void)
+{
+ return serial_channel;
+}
+
+static struct pdl_channel *debug_channel = NULL;
+
+void pdl_debug_channel_register(struct pdl_channel *channel)
+{
+ if (channel) {
+ debug_channel = channel;
+ } else {
+ pdl_error("debug channel cannot register\n");
+ }
+
+ return;
+}
+
+struct pdl_channel *pdl_get_debug_channel(void)
+{
+ return debug_channel;
+}
diff --git a/pdl/common/pdl_channel_usb.c b/pdl/common/pdl_channel_usb.c
new file mode 100644
index 0000000000..1bd7d55527
--- /dev/null
+++ b/pdl/common/pdl_channel_usb.c
@@ -0,0 +1,85 @@
+#include <usb/usbserial.h>
+#include "pdl_channel.h"
+#include "pdl_debug.h"
+
+static int usbser_ch0_tstc (void)
+{
+ return usbser_tstc(USB_ACM_CHAN_0);
+}
+
+static int usbser_ch0_getc (void)
+{
+ return usbser_getc(USB_ACM_CHAN_0);
+}
+
+static void usbser_ch0_putc (const char c)
+{
+ usbser_putc(USB_ACM_CHAN_0, c);
+}
+
+static int usbser_ch0_read(unsigned char *_buf, unsigned int len)
+{
+ return usbser_read(USB_ACM_CHAN_0, _buf, len);
+}
+
+static int usbser_ch0_write(const unsigned char *_buf, unsigned int len)
+{
+ return usbser_write(USB_ACM_CHAN_0, _buf, len);
+}
+
+static struct pdl_channel usb_channel = {
+ .getchar = usbser_ch0_getc,
+ .putchar = usbser_ch0_putc,
+ .tstc = usbser_ch0_tstc,
+ .read = usbser_ch0_read,
+ .write = usbser_ch0_write,
+ .priv = NULL
+};
+
+#ifdef CONFIG_USB_ACM_TWO_CHANS
+
+static int usbser_ch1_tstc (void)
+{
+ return usbser_tstc(USB_ACM_CHAN_1);
+}
+
+static int usbser_ch1_getc (void)
+{
+ return usbser_getc(USB_ACM_CHAN_1);
+}
+
+static void usbser_ch1_putc (const char c)
+{
+ usbser_putc(USB_ACM_CHAN_1, c);
+}
+
+static int usbser_ch1_read(unsigned char *_buf, unsigned int len)
+{
+ return usbser_read(USB_ACM_CHAN_1, _buf, len);
+}
+
+static int usbser_ch1_write(const unsigned char *_buf, unsigned int len)
+{
+ return usbser_write(USB_ACM_CHAN_1, _buf, len);
+}
+
+static struct pdl_channel usb_channel2 = {
+ .getchar = usbser_ch1_getc,
+ .putchar = usbser_ch1_putc,
+ .tstc = usbser_ch1_tstc,
+ .read = usbser_ch1_read,
+ .write = usbser_ch1_write,
+ .priv = NULL,
+};
+
+#endif //CONFIG_USB_ACM_TWO_CHANS
+
+void pdl_usb_channel_register(void)
+{
+#ifdef CONFIG_USB_ACM_TWO_CHANS
+ pdl_debug_channel_register(&usb_channel2);
+#endif
+
+ pdl_channel_register(&usb_channel);
+}
+
diff --git a/pdl/common/pdl_debug.c b/pdl/common/pdl_debug.c
new file mode 100644
index 0000000000..07b812bce0
--- /dev/null
+++ b/pdl/common/pdl_debug.c
@@ -0,0 +1,48 @@
+#include <common.h>
+#include <malloc.h>
+#include <stdio_dev.h>
+#include "pdl.h"
+#include "pdl_debug.h"
+#include "pdl_channel.h"
+
+char *pdl_log_buff = NULL;
+static void pdl_serial_puts(const char *s)
+{
+ struct pdl_channel *dbg_ch = pdl_get_debug_channel();
+
+ /* save print string to log buffer */
+ if(pdl_log_buff) {
+ int a = strlen(pdl_log_buff);
+ int b = strlen(s);
+
+ if(a + b < PDL_LOG_MAX_SIZE)
+ strcpy(&pdl_log_buff[a], s);
+ }
+
+ serial_puts(s);
+
+ if(pdl_dbg_usb_serial && dbg_ch)
+ dbg_ch->write((const unsigned char *)s, strlen(s)+1);
+}
+
+struct stdio_dev *search_device(int flags, const char *name);
+void pdl_init_serial(void)
+{
+ struct stdio_dev *dev = search_device (DEV_FLAGS_OUTPUT, "serial");
+ if(!dev)
+ return;
+ dev->puts = pdl_serial_puts;
+
+ if(!pdl_log_buff) {
+ pdl_log_buff = malloc(PDL_LOG_MAX_SIZE);
+ pdl_log_buff[0] = '\0';
+ }
+}
+void pdl_release_serial(void)
+{
+ struct stdio_dev *dev = search_device (DEV_FLAGS_OUTPUT, "serial");
+ if(!dev)
+ return;
+ dev->puts = serial_puts;
+}
+
diff --git a/pdl/common/pdl_engine.c b/pdl/common/pdl_engine.c
new file mode 100644
index 0000000000..2e4c5d3745
--- /dev/null
+++ b/pdl/common/pdl_engine.c
@@ -0,0 +1,141 @@
+//#define DEBUG
+#include <common.h>
+#include <asm/types.h>
+#include "cmd_defs.h"
+#include <asm/arch/rda_sys.h>
+#include "packet.h"
+#include "pdl_engine.h"
+#include "pdl_debug.h"
+
+static struct pdl_cmd cmd_handler_table[MAX_CMD];
+
+#define cmd_is_valid(cmd) ((cmd >= MIN_CMD) && (cmd < MAX_CMD))
+
+static const char *pdl_commands[MAX_CMD] = {
+ [CONNECT] = "connect",
+ [ERASE_FLASH] = "erase flash",
+ [ERASE_PARTITION] = "erase partition",
+ [ERASE_ALL] = "erase all",
+ [START_DATA] = "start data",
+ [MID_DATA] = "midst data",
+ [END_DATA] = "end data",
+ [EXEC_DATA] = "execute data",
+ [READ_FLASH] = "read flash",
+ [READ_PARTITION] = "read partition",
+ [NORMAL_RESET] = "normal reset",
+ [READ_CHIPID] = "read chipid",
+ [SET_BAUDRATE] = "set baudrate",
+ [FORMAT_FLASH] = "format flash",
+ [READ_PARTITION_TABLE] = "read partition table",
+ [READ_IMAGE_ATTR] = "read image attribute",
+ [GET_VERSION] = "get version",
+ [SET_FACTMODE] = "set factory mode",
+ [SET_CALIBMODE] = "set calibration mode",
+ [SET_PDL_DBG] = "set pdl debuglevel",
+ [CHECK_PARTITION_TABLE] = "check partition table",
+ [POWER_OFF] = "power off",
+ [IMAGE_LIST] = "download image list",
+ [GET_SECURITY] = "get security capabilities",
+ [HW_TEST] = "hardware test",
+ [GET_PDL_LOG] = "get pdl log",
+ [DOWNLOAD_FINISH] = "download finish",
+};
+
+int pdl_cmd_register(int cmd_type, pdl_cmd_handler handler)
+{
+ if (!cmd_is_valid(cmd_type))
+ return -1;
+
+ cmd_handler_table[cmd_type].handler = handler;
+ return 0;
+}
+
+int pdl_handle_connect(u32 timeout)
+{
+ struct pdl_packet pkt;
+ int cmd;
+ int res;
+
+ res = pdl_get_connect_packet(&pkt, timeout);
+ if (res != 1) {
+ return -1;
+ }
+
+ cmd = pkt.cmd_header.cmd_type;
+ if (cmd == CONNECT)
+ cmd_handler_table[cmd].handler(&pkt, 0);
+ else
+ goto err;
+
+ pdl_put_cmd_packet(&pkt);
+ return 0;
+err:
+ pdl_put_cmd_packet(&pkt);
+ return -1;
+}
+
+#define CONNECT_TIMEOUT 180000 /*3 minutes*/
+static void pdl_command_loop(void)
+{
+ struct pdl_packet pkt;
+ int res;
+ u32 cmd;
+ int error;
+ int cnt = CONNECT_TIMEOUT;
+ int check_connect = 1;
+
+ while (1) {
+ res = pdl_get_cmd_packet(&pkt);
+ if (res == -2) {
+ if (!check_connect) {
+ mdelay(1);
+ continue;
+ } else if (--cnt > 0) {
+ mdelay(1);
+ continue;
+ }
+
+ if (cnt <= 0) {
+ pdl_error("PDL get command timeout...\n");
+ break;
+ }
+ }
+ if (check_connect)
+ check_connect = 0;
+
+ if (res == 0)
+ continue;
+
+ //if error send response to pc.
+ if (res == -1) {
+ error = pdl_get_packet_error();
+ pdl_send_rsp(error);
+ pdl_put_cmd_packet(&pkt);
+ continue;
+ }
+ cmd = pkt.cmd_header.cmd_type;
+
+ if (!cmd_is_valid(cmd)) {
+ pdl_send_rsp(INVALID_CMD);
+ pdl_put_cmd_packet(&pkt);
+ continue;
+ }
+
+ pdl_dbg("get and exec cmd '%s'\n", pdl_commands[cmd]);
+
+ if (cmd_handler_table[cmd].handler)
+ res = cmd_handler_table[cmd].handler(&pkt, 0);
+ pdl_put_cmd_packet(&pkt);
+ }
+
+ pdl_info("shutdown machine...\n");
+ shutdown_system();
+}
+
+int pdl_handler(void *arg)
+{
+ for (;;) {
+ pdl_command_loop();
+ }
+ return 0;
+}