Bringing WiFi to the Cidco Mailstation
1/*
2 * WiFiStation
3 * Serial port program loader
4 *
5 * Copyright (c) 2019-2021 joshua stein <jcs@jcs.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <err.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <poll.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <signal.h>
28#include <termios.h>
29#include <unistd.h>
30#include <vis.h>
31#include <sys/ioctl.h>
32#include <sys/stat.h>
33
34static int debug = 0;
35static char vbuf[512];
36static int serial_fd = -1;
37
38void
39usage(void)
40{
41 fprintf(stderr, "usage: %s [-d] [-s serial speed] <serial device> "
42 "<file>\n",
43 getprogname());
44 exit(1);
45}
46
47void
48serial_setup(int fd, speed_t speed)
49{
50 struct termios tty;
51
52 if (ioctl(fd, TIOCEXCL) != 0)
53 err(1, "ioctl(TIOCEXCL)");
54 if (tcgetattr(fd, &tty) < 0)
55 err(1, "tcgetattr");
56
57 tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
58 tty.c_cflag &= ~CSIZE;
59 tty.c_cflag |= CS8; /* 8-bit characters */
60 tty.c_cflag &= ~PARENB; /* no parity bit */
61 tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
62
63 /* setup for non-canonical mode */
64 tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
65 tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
66 tty.c_oflag &= ~OPOST;
67
68 /* fetch bytes as they become available */
69 tty.c_cc[VMIN] = 1;
70 tty.c_cc[VTIME] = 1;
71
72 cfsetspeed(&tty, speed);
73
74 if (tcsetattr(fd, TCSAFLUSH, &tty) != 0)
75 err(1, "tcsetattr");
76}
77
78size_t
79serial_read(void *buf, size_t nbytes)
80{
81 size_t ret;
82
83 ret = read(serial_fd, buf, nbytes);
84
85 if (debug && ret > 0) {
86 strvisx(vbuf, buf, ret, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
87 printf("<<< %s\n", vbuf);
88 }
89
90 return ret;
91}
92
93size_t
94serial_write(const void *buf, size_t nbytes)
95{
96 if (debug) {
97 strvisx(vbuf, buf, nbytes, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
98 printf(">>> %s\n", vbuf);
99 }
100
101 return write(serial_fd, buf, nbytes);
102}
103
104int
105main(int argc, char *argv[])
106{
107 FILE *pFile;
108 struct stat sb;
109 struct pollfd pfd[1];
110 char *fn, *serial_dev = NULL;
111 unsigned char buf[128], b, cksum = 0, rcksum;
112 unsigned int sent = 0, size = 0;
113 int len, rlen, ch;
114 int serial_speed = B115200;
115
116 while ((ch = getopt(argc, argv, "ds:")) != -1) {
117 switch (ch) {
118 case 'd':
119 debug = 1;
120 break;
121 case 's':
122 serial_speed = (unsigned)strtol(optarg, NULL, 0);
123 if (errno)
124 err(1, "invalid serial port speed value");
125 break;
126 default:
127 usage();
128 }
129 }
130 argc -= optind;
131 argv += optind;
132
133 if (argc != 2)
134 usage();
135
136 serial_dev = argv[0];
137 serial_fd = open(serial_dev, O_RDWR|O_SYNC);
138 if (serial_fd < 0)
139 err(1, "open %s", serial_dev);
140
141 serial_setup(serial_fd, serial_speed);
142
143 fn = argv[1];
144 pFile = fopen(fn, "rb");
145 if (!pFile)
146 err(1, "open %s", fn);
147
148 if (fstat(fileno(pFile), &sb) != 0)
149 err(1, "fstat %s", fn);
150
151 /* we're never going to send huge files */
152 size = (unsigned int)sb.st_size;
153
154 if (debug)
155 printf("sending %s (%d bytes) via %s\n", fn, size, serial_dev);
156
157 /*
158 * spam some newlines since the TTL connection kinda sucks, and ^C in
159 * case the device is in AT$PINS? mode
160 */
161 serial_write("\r\n\r\n", 4);
162 b = 3;
163 serial_write(&b, 1);
164
165 /*
166 * send AT to get some output since sometimes the first character is
167 * lost and we'll just get 'T', and we need to see a full response to
168 * AT$UPLOAD later
169 */
170 serial_write("AT\r", 3);
171 pfd[0].fd = serial_fd;
172 pfd[0].events = POLLIN;
173 while (poll(pfd, 1, 100) > 0)
174 serial_read(buf, sizeof(buf));
175
176 len = snprintf(buf, sizeof(buf), "AT$UPLOAD %d\r", size);
177 serial_write(buf, len);
178 memset(buf, 0, sizeof(buf));
179
180 /* it will echo, along with an OK */
181 rlen = 0;
182 while (poll(pfd, 1, 100) > 0) {
183 len = serial_read(buf + rlen, sizeof(buf) - rlen);
184 if (sizeof(buf) - rlen <= 0)
185 break;
186 rlen += len;
187 }
188
189 len = 0;
190 if (sscanf(buf, "AT$UPLOAD %d\r\nOK%n", &rlen, &len) != 1 || len < 10) {
191 strvis(vbuf, buf, VIS_NL | VIS_CSTYLE | VIS_OCTAL);
192 errx(1, "bad response to AT$UPLOAD: %s", vbuf);
193 }
194
195 /* clear out any remaining response so we can read each byte echoed */
196 pfd[0].fd = serial_fd;
197 pfd[0].events = POLLIN;
198 while (poll(pfd, 1, 100) > 0)
199 serial_read(buf, sizeof(buf));
200
201 while (sent < size) {
202 b = fgetc(pFile);
203 if (debug)
204 printf("sending: %05d/%05d (0x%x)\n", sent, size, b);
205 serial_write(&b, 1);
206 cksum ^= b;
207 sent++;
208
209 if (sent % 32 == 0 || sent == size) {
210 if (poll(pfd, 1, 1000) < 1) {
211 printf("\n");
212 errx(1, "failed poll at byte %d/%d", sent,
213 size);
214 }
215
216 if (serial_read(&rcksum, 1) != 1) {
217 printf("\n");
218 errx(1, "failed read at byte %d/%d", sent,
219 size);
220 }
221
222 if (rcksum == cksum) {
223 if (debug)
224 printf("checksum 0x%x matches\n",
225 (cksum & 0xff));
226 } else {
227 printf("\n");
228 errx(1, "failed checksum of byte %d/%d "
229 "(expected 0x%x, received 0x%x)",
230 sent, size, cksum, rcksum);
231 }
232 }
233
234 if (!debug)
235 printf("\rsent: %05d/%05d", sent, size);
236
237 fflush(stdout);
238 }
239 fclose(pFile);
240
241 printf("\n");
242
243 /* wait for our final OK */
244 while (poll(pfd, 1, 200) > 0)
245 serial_read(buf, sizeof(buf));
246
247 close(serial_fd);
248
249 return 0;
250}