-
Notifications
You must be signed in to change notification settings - Fork 3
/
pk2serial.c
444 lines (363 loc) · 13.1 KB
/
pk2serial.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
/* pk2serial (c) 2009 Tom Schouten
I hereby grant the rights for any use of this software, given that
this copyright notice is preserved.
Connect to PK2 and start a serial console on stdio. Use this in
conjunction with socat for pty/socket/... emulation.
*/
/* Enhancements by Majenko Technologies */
#include <usb.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <termios.h>
#include <pty.h>
/* Program options */
int verbose = 0;
int usec = 100000;
int output_fd = 1;
int input_fd = 0;
int baud_rate = 9600;
int vendor = 0x04d8;
int product = 0x0033;
int exit_on_eof = 0;
int latency_bytes = 32;
int power_target = 0; // (1)
int reset_target = 0;
int pty_mode = 0;
/* (1) Manual says not to power target in UART mode. I found this to
work but somehow be less reliable.. Note that VDD does need to be
connected to target power for proper signal clamping. (You need -p
with loopback testing!) */
#define ENDPOINT_IN 0x81
#define ENDPOINT_OUT 0x01
#define BUFFER_SIZE 64
#define TIMEOUT 5000
// PK2 commands
#define GET_STATUS 0xA2
#define EXECUTE_SCRIPT 0xA6
#define CLR_DOWNLOAD_BFR 0xA7
#define CLR_UPLOAD_BFR 0xA9
#define DOWNLOAD_DATA 0xA8
#define UPLOAD_DATA 0xAA
#define ENTER_UART_MODE 0xB3
#define EXIT_UART_MODE 0xB4
#define SET_VDD 0xA0
#define RESET 0xAE
#define END_OF_BUFFER 0xAD
// PK2 script instructions
#define VDD_GND_ON 0xFD
#define VDD_ON 0xFF
#define VDD_GND_OFF 0xFC
#define VDD_OFF 0xFE
#define MCLR_GND_ON 0xF7
#define MCLR_GND_OFF 0xF6
#define SET_ISP_PINS 0xF3
usb_dev_handle *handle = NULL; // only one device per program instance.
#define LOGL(level, ...) { if (verbose >= level) fprintf(stderr, __VA_ARGS__); }
#define LOG(...) { LOGL(1, __VA_ARGS__); }
#define LOG_ERROR(...) { fprintf(stderr, __VA_ARGS__); }
#define RAISE_ERROR(...) { LOG_ERROR(__VA_ARGS__); exit(1); }
void log_buf(char *kind, unsigned char *data, int len) {
int i;
if (len) {
LOGL(2, "%s: ", kind);
for(i=0; i<len; i++) { LOGL(2, "%02X ", data[i]); }
LOGL(2, "\r\n");
}
}
/* lowlevel transfer */
void usb_send_buf(void *buffer) {
int rv;
if (BUFFER_SIZE != (rv = usb_interrupt_write(handle, ENDPOINT_OUT, buffer, BUFFER_SIZE, TIMEOUT))) {
RAISE_ERROR("Invalid size request: %d bytes.\r\n", rv);
}
}
void usb_receive_buf(void *buffer) {
int rv;
if (BUFFER_SIZE != (rv = usb_interrupt_read(handle, ENDPOINT_IN, buffer, BUFFER_SIZE, TIMEOUT))) {
RAISE_ERROR("Invalid size response: %d bytes.\r\n", rv);
}
}
/* buffer formatting based on data length */
void usb_send(void *data, int len) {
char buffer[BUFFER_SIZE];
log_buf("W", data, len);
memset(buffer, END_OF_BUFFER, BUFFER_SIZE);
memcpy(buffer, data, len);
usb_send_buf(buffer);
}
void usb_receive(void *data, int len) {
char buffer[BUFFER_SIZE];
usb_receive_buf(buffer);
memcpy(data, buffer, len);
log_buf("R", data, len);
}
/* one command cycle */
void usb_call(void *cmd, int cmd_len,
void *reply, int reply_len) {
usb_send(cmd, cmd_len);
if (reply_len) usb_receive(reply, reply_len); // PK2 doesn't send empty buffers
}
#define TRANSACTION(reply_type, ...) ({ \
char cmd[] = { __VA_ARGS__ }; \
reply_type reply; \
usb_call(cmd, sizeof(cmd), &reply, sizeof(reply_type)); \
reply; })
#define COMMAND(...) { \
char cmd[] = { __VA_ARGS__ }; \
usb_call(cmd, sizeof(cmd), NULL, 0); }
#define DEFINE_TRANSACTION(return_type, name, ...) return_type name (void) { \
return TRANSACTION(return_type, __VA_ARGS__); }
DEFINE_TRANSACTION(short int, get_status, GET_STATUS)
void set_vdd(float vdd) {
float tolerance = 0.80;
float vfault = tolerance * vdd;
int vddi = ((32.0f * vdd) + 10.5f) * 64.0f;
int vfi = (vfault / 5.0) * 255.0f;
COMMAND(SET_VDD, vddi & 0xFF, (vddi >> 8) & 0xFF, vfi);
}
void reset_pk2(void) { COMMAND(RESET); }
void reset_hold(int rst) { COMMAND(EXECUTE_SCRIPT, 1, rst ? MCLR_GND_ON : MCLR_GND_OFF); }
void reset_release(void) { COMMAND(EXECUTE_SCRIPT, 1, MCLR_GND_OFF); }
void target_off(int pwr) { COMMAND(EXECUTE_SCRIPT, 2, VDD_OFF, pwr ? VDD_GND_ON : VDD_GND_OFF); }
void target_on(int pwr) { COMMAND(EXECUTE_SCRIPT, 2, VDD_GND_OFF, pwr ? VDD_ON : VDD_OFF); usleep(50000); }
void uart_off(void) { COMMAND(EXIT_UART_MODE); }
void uart_on(float rate) {
int irate = (65536.0f - (((1.0f / rate) - .000003f) / .000000167f));
COMMAND(CLR_UPLOAD_BFR);
COMMAND(CLR_DOWNLOAD_BFR);
COMMAND(ENTER_UART_MODE, irate & 0xFF, (irate >> 8) & 0xFF);
}
void dots(int n) { while (n--) LOG("."); }
/* Transfer data between fds and the PK2. These use the lower level
transfer functions. */
int transfer_input(int fd, int max_tx) {
char buf[BUFFER_SIZE];
memset(buf, END_OF_BUFFER, BUFFER_SIZE);
buf[0] = DOWNLOAD_DATA;
buf[1] = read(fd, buf+2, max_tx);
if (-1 == buf[1]) return buf[1];
LOG("I %2d\r\n", buf[1]);
usb_send_buf(buf);
return buf[1];
}
void transfer_output(int fd) { // from PK2 -> host
char buf[BUFFER_SIZE];
char cmd[] = {UPLOAD_DATA};
int written;
usb_send(cmd, 1);
usb_receive_buf(buf);
LOG(" O %2d\r\n", buf[0]);
if (buf[0]) {
written = write(fd, buf + 1, buf[0]);
if (written != buf[0]) {
RAISE_ERROR("output buffer overrun!\r\n");
}
}
}
/* Signal handler */
int stop = 0;
void handler(int signal) { LOG("got signal %d\r\n", signal); stop = 1; }
/* Event loop */
void start_serial(void) {
int eof = 0;
/* Compute optimal buffer size depending on desired latency. */
int usec_per_byte = (10 * (1000000 / baud_rate));
LOG("latency = %d bytes\r\n", latency_bytes);
/* Setup PK2 with target off. FIXME: connect to running target! */
reset_hold(reset_target);
target_off(power_target);
if (power_target) set_vdd(5.0);
/* Start target. */
target_on(power_target);
reset_release();
/* Check if votage is ok. */
LOG("status %04X\r\n", get_status());
/* Go */
if (-1 == fcntl(input_fd, F_SETFL, O_NONBLOCK)) {
RAISE_ERROR("Can't set input_fd O_NONBLOCK.\r\n");
}
struct termios options;
struct termios savedOptions;
tcgetattr(input_fd, &savedOptions);
tcgetattr(input_fd, &options);
cfmakeraw(&options);
options.c_cflag |= (CLOCAL | CREAD);
options.c_lflag |= ISIG;
if (tcsetattr(input_fd, TCSANOW, &options) != 0) {
fprintf(stderr, "Can't set up console\r\n");
}
uart_on(baud_rate);
fprintf(stderr, "Connected to PICkit2 Serial.\r\n");
if (pty_mode == 1) {
fprintf(stderr, "CTRL-C to exit.\r\n");
} else {
fprintf(stderr, "CTRL-C to exit, CTRL-D to disconnect input.\r\n");
}
while(!stop) {
if (eof) {
/* If input is closed we cn just poll the output. */
LOG("EOF\r\n");
usleep(usec_per_byte);
transfer_output(output_fd);
}
else {
/* When there's room in the buffer, always respond immediately
to input data. */
struct timeval timeout;
fd_set inset;
timeout.tv_sec = 0;
timeout.tv_usec = usec_per_byte * latency_bytes;
FD_ZERO(&inset);
FD_SET(input_fd, &inset);
if (-1 == (select(1+input_fd, &inset, NULL, NULL, &timeout))) {
if (errno != EINTR) {
RAISE_ERROR("select() error\r\n");
}
}
/* If there's data, read as much as possible. */
if (FD_ISSET(input_fd, &inset)) {
int rv;
rv = transfer_input(input_fd, latency_bytes);
if (rv == -1) {
if (errno == EAGAIN) break;
RAISE_ERROR("read(): %s\r\n",strerror(errno));
}
if (rv == 0) {
eof = 1;
if (exit_on_eof) break;
}
/* Simple rate limiting mechanism: per transfer, wait for
as long as it takes to transfer the bytes. Note that
this can be made more efficient when we track an
estimate of the number of elements in the upload
buffer, keeping the buffer full instead of empty. */
while(rv--) {
if (stop) break;
usleep(usec_per_byte);
}
}
}
transfer_output(output_fd);
}
if (tcsetattr(input_fd, TCSANOW, &savedOptions) != 0) {
fprintf(stderr, "Can't clean up console\r\n");
}
/* Cleanup */
uart_off();
reset_pk2();
}
void usage(void){
fprintf(stderr,
"usage: pk2serial [<option> ...]\r\n"
" options:\r\n"
" -b rate Transmission rate (9600 baud)\r\n"
" -l bufsize Latency (32 bytes)\r\n"
" -e Exit on EOF\r\n"
" -r Reset target\r\n"
" -p Power target (Spec says not to!)\r\n"
" -t PTY mode\r\n"
" -v Verbose output\r\n"
" -h This help text.\r\n"
);
}
void usage_exit(void){
usage();
exit(1);
}
char limit_fmt[] = "limited %s to %f";
#define LIMIT(variable, op, value) { if (variable op value) variable = value; }
int main(int argc, char **argv) {
struct usb_bus *busses;
int c;
opterr = 0;
while ((c = getopt(argc, argv, "rptevhb:l:")) != -1) {
switch(c) {
case 'h': usage(); exit(0);
case 'p': power_target = 1; break;
case 'r': reset_target = 1; break;
case 'v': verbose++; break;
case 'e': exit_on_eof = 1; break;
case 't': pty_mode = 1; break;
case 'b':
baud_rate = strtol(optarg, NULL, 10);
LIMIT(baud_rate, <, 10); // arbitrary limit at 1 byte / second.
LIMIT(baud_rate, >, 60000); // tested with loopback
break;
case 'l':
latency_bytes = strtol(optarg, NULL, 10);
LIMIT(latency_bytes, <, 1);
LIMIT(latency_bytes, >, 60);
break;
case '?':
if ((optopt == 'b') || (optopt == 'l'))
fprintf (stderr, "Option -%c requires an argument.\r\n",
optopt);
else if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\r\n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'.\r\n",
optopt);
default:
usage_exit();
}
}
LOG("baud rate = %d\r\n", baud_rate);
signal(SIGINT, handler);
usb_init();
usb_find_busses();
usb_find_devices();
busses = usb_get_busses();
struct usb_bus *bus;
for (bus = busses; bus; bus = bus->next) {
struct usb_device *dev;
for (dev = bus->devices; dev; dev = dev->next) {
/* Find first PK2 */
if ((dev->descriptor.idVendor == vendor)
&& (dev->descriptor.idProduct == product)) {
if (!(handle = usb_open(dev))) {
RAISE_ERROR("Can't open device.\r\n");
}
/* FIXME: Detach if necessary
usb_get_driver_np
usb_detach_kernel_driver_np
*/
if (-1 == usb_set_configuration(handle, 2)) { // Try vendor config (not HID!)
RAISE_ERROR("Can't set vendor config.\r\n");
}
if (-1 == usb_claim_interface(handle, 0)) {
RAISE_ERROR("Can't claim interface.\r\n");
}
int pty_master_fd;
int pty_slave_fd;
if (pty_mode == 1) {
char dev[100];
if (openpty(&pty_master_fd, &pty_slave_fd, dev, NULL, NULL) == -1) {
RAISE_ERROR("Cannot allocate PTY.\r\n");
}
input_fd = pty_master_fd;
output_fd = pty_master_fd;
fprintf(stderr, "Opened PTY on %s\r\n", dev);
}
start_serial();
if (pty_mode == 1) {
close(pty_master_fd);
close(pty_slave_fd);
}
if (-1 == usb_release_interface(handle, 0)) {
RAISE_ERROR("Can't release interface.\r\n");
}
if (-1 == usb_close(handle)) {
RAISE_ERROR("Can't close device.\r\n");
}
}
}
}
return 0;
}