Initial commit.
This commit is contained in:
parent
1a1ccf4726
commit
7c4ed66e95
|
@ -0,0 +1,8 @@
|
||||||
|
p1load: p1load.c ploader.c osint_linux.c
|
||||||
|
cc -Wall -DMACOSX -o $@ p1load.c ploader.c osint_linux.c
|
||||||
|
|
||||||
|
run: p1load
|
||||||
|
./p1load blink.binary -r -t
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf p1load
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* @file osint.h
|
||||||
|
*
|
||||||
|
* Serial I/O functions used by PLoadLib.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 by John Steven Denson
|
||||||
|
* Modified in 2011 by David Michael Betz
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef __SERIAL_IO_H__
|
||||||
|
#define __SERIAL_IO_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* serial i/o definitions */
|
||||||
|
#define SERIAL_TIMEOUT -1
|
||||||
|
|
||||||
|
/* serial i/o routines */
|
||||||
|
void serial_use_rts_for_reset(int use_rts);
|
||||||
|
int serial_find(const char* prefix, int (*check)(const char* port, void* data), void* data);
|
||||||
|
int serial_init(const char *port, unsigned long baud);
|
||||||
|
int serial_baud(unsigned long baud);
|
||||||
|
void serial_done(void);
|
||||||
|
int tx(uint8_t* buff, int n);
|
||||||
|
int rx(uint8_t* buff, int n);
|
||||||
|
int rx_timeout(uint8_t* buff, int n, int timeout);
|
||||||
|
void hwreset(void);
|
||||||
|
|
||||||
|
/* terminal mode */
|
||||||
|
void terminal_mode(int check_for_exit, int pst_mode);
|
||||||
|
|
||||||
|
/* miscellaneous functions */
|
||||||
|
void msleep(int ms);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,467 @@
|
||||||
|
/**
|
||||||
|
* @file osint_linux.c
|
||||||
|
*
|
||||||
|
* Serial I/O functions used by PLoadLib.c
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 by John Steven Denson
|
||||||
|
* Modified in 2011 by David Michael Betz
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/timeb.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "osint.h"
|
||||||
|
|
||||||
|
typedef int HANDLE;
|
||||||
|
static HANDLE hSerial = -1;
|
||||||
|
static struct termios old_sparm;
|
||||||
|
|
||||||
|
/* normally we use DTR for reset but setting this variable to non-zero will use RTS instead */
|
||||||
|
static int use_rts_for_reset = 0;
|
||||||
|
|
||||||
|
void serial_use_rts_for_reset(int use_rts)
|
||||||
|
{
|
||||||
|
use_rts_for_reset = use_rts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chk(char *fun, int sts)
|
||||||
|
{
|
||||||
|
if (sts != 0)
|
||||||
|
printf("%s failed\n", fun);
|
||||||
|
}
|
||||||
|
|
||||||
|
int serial_find(const char* prefix, int (*check)(const char* port, void* data), void* data)
|
||||||
|
{
|
||||||
|
char path[PATH_MAX];
|
||||||
|
int prefixlen = strlen(prefix);
|
||||||
|
struct dirent *entry;
|
||||||
|
DIR *dirp;
|
||||||
|
|
||||||
|
if (!(dirp = opendir("/dev")))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
while ((entry = readdir(dirp)) != NULL) {
|
||||||
|
if (strncmp(entry->d_name, prefix, prefixlen) == 0) {
|
||||||
|
sprintf(path, "/dev/%s", entry->d_name);
|
||||||
|
if ((*check)(path, data) == 0) {
|
||||||
|
closedir(dirp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dirp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sigint_handler(int signum)
|
||||||
|
{
|
||||||
|
serial_done();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* open serial port
|
||||||
|
* @param port - COMn port name
|
||||||
|
* @param baud - baud rate
|
||||||
|
* @returns 1 for success and 0 for failure
|
||||||
|
*/
|
||||||
|
int serial_init(const char* port, unsigned long baud)
|
||||||
|
{
|
||||||
|
struct termios sparm;
|
||||||
|
|
||||||
|
/* open the port */
|
||||||
|
#ifdef MACOSX
|
||||||
|
hSerial = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||||
|
#else
|
||||||
|
hSerial = open(port, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
|
||||||
|
#endif
|
||||||
|
if(hSerial == -1) {
|
||||||
|
//printf("error: opening '%s' -- %s\n", port, strerror(errno));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGINT, sigint_handler);
|
||||||
|
|
||||||
|
/* set the terminal to exclusive mode */
|
||||||
|
if (ioctl(hSerial, TIOCEXCL) != 0) {
|
||||||
|
//printf("error: can't open terminal for exclusive access\n");
|
||||||
|
close(hSerial);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fcntl(hSerial, F_SETFL, 0);
|
||||||
|
|
||||||
|
/* set the baud rate */
|
||||||
|
if (!serial_baud(baud)) {
|
||||||
|
close(hSerial);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get the current options */
|
||||||
|
chk("tcgetattr", tcgetattr(hSerial, &old_sparm));
|
||||||
|
sparm = old_sparm;
|
||||||
|
|
||||||
|
/* set raw input */
|
||||||
|
#ifdef MACOSX
|
||||||
|
cfmakeraw(&sparm);
|
||||||
|
sparm.c_cc[VTIME] = 0;
|
||||||
|
sparm.c_cc[VMIN] = 1;
|
||||||
|
#else
|
||||||
|
memset(&sparm, 0, sizeof(sparm));
|
||||||
|
sparm.c_cflag = CS8 | CLOCAL | CREAD;
|
||||||
|
sparm.c_lflag = 0; // &= ~(ICANON | ECHO | ECHOE | ISIG);
|
||||||
|
sparm.c_oflag = 0; // &= ~OPOST;
|
||||||
|
|
||||||
|
sparm.c_iflag = IGNPAR | IGNBRK;
|
||||||
|
sparm.c_cc[VTIME] = 0;
|
||||||
|
sparm.c_cc[VMIN] = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* set the options */
|
||||||
|
chk("tcflush", tcflush(hSerial, TCIFLUSH));
|
||||||
|
chk("tcsetattr", tcsetattr(hSerial, TCSANOW, &sparm));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change the baud rate of the serial port
|
||||||
|
* @param baud - baud rate
|
||||||
|
* @returns 1 for success and 0 for failure
|
||||||
|
*/
|
||||||
|
int serial_baud(unsigned long baud)
|
||||||
|
{
|
||||||
|
struct termios sparm;
|
||||||
|
int tbaud = 0;
|
||||||
|
|
||||||
|
switch(baud) {
|
||||||
|
case 0: // default
|
||||||
|
tbaud = B115200;
|
||||||
|
break;
|
||||||
|
#ifdef B921600
|
||||||
|
case 921600:
|
||||||
|
tbaud = B921600;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef B576000
|
||||||
|
case 576000:
|
||||||
|
tbaud = B576000;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef B500000
|
||||||
|
case 500000:
|
||||||
|
tbaud = B500000;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef B460800
|
||||||
|
case 460800:
|
||||||
|
tbaud = B460800;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef B230400
|
||||||
|
case 230400:
|
||||||
|
tbaud = B230400;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case 115200:
|
||||||
|
tbaud = B115200;
|
||||||
|
break;
|
||||||
|
case 57600:
|
||||||
|
tbaud = B57600;
|
||||||
|
break;
|
||||||
|
case 38400:
|
||||||
|
tbaud = B38400;
|
||||||
|
break;
|
||||||
|
case 19200:
|
||||||
|
tbaud = B19200;
|
||||||
|
break;
|
||||||
|
case 9600:
|
||||||
|
tbaud = B9600;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Unsupported baudrate. Use ");
|
||||||
|
tbaud = baud; break;
|
||||||
|
#ifdef B921600
|
||||||
|
printf("921600, ");
|
||||||
|
#endif
|
||||||
|
#ifdef B576000
|
||||||
|
printf("576000, ");
|
||||||
|
#endif
|
||||||
|
#ifdef B500000
|
||||||
|
printf("500000, ");
|
||||||
|
#endif
|
||||||
|
#ifdef B460800
|
||||||
|
printf("460800, ");
|
||||||
|
#endif
|
||||||
|
#ifdef B230400
|
||||||
|
printf("230400, ");
|
||||||
|
#endif
|
||||||
|
printf("115200, 57600, 38400, 19200, or 9600\n");
|
||||||
|
serial_done();
|
||||||
|
exit(2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get the current options */
|
||||||
|
chk("tcgetattr", tcgetattr(hSerial, &sparm));
|
||||||
|
|
||||||
|
/* set raw input */
|
||||||
|
#ifdef MACOSX
|
||||||
|
chk("cfsetspeed", cfsetspeed(&sparm, tbaud));
|
||||||
|
#else
|
||||||
|
chk("cfsetispeed", cfsetispeed(&sparm, tbaud));
|
||||||
|
chk("cfsetospeed", cfsetospeed(&sparm, tbaud));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* set the options */
|
||||||
|
chk("tcflush", tcflush(hSerial, TCIFLUSH));
|
||||||
|
chk("tcsetattr", tcsetattr(hSerial, TCSANOW, &sparm));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close serial port
|
||||||
|
*/
|
||||||
|
void serial_done(void)
|
||||||
|
{
|
||||||
|
if (hSerial != -1) {
|
||||||
|
tcflush(hSerial, TCIOFLUSH);
|
||||||
|
tcsetattr(hSerial, TCSANOW, &old_sparm);
|
||||||
|
ioctl(hSerial, TIOCNXCL);
|
||||||
|
close(hSerial);
|
||||||
|
hSerial = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* receive a buffer
|
||||||
|
* @param buff - char pointer to buffer
|
||||||
|
* @param n - number of bytes in buffer to read
|
||||||
|
* @returns number of bytes read
|
||||||
|
*/
|
||||||
|
int rx(uint8_t* buff, int n)
|
||||||
|
{
|
||||||
|
ssize_t bytes = read(hSerial, buff, n);
|
||||||
|
if(bytes < 1) {
|
||||||
|
printf("Error reading port: %d\n", (int)bytes);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int)bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* transmit a buffer
|
||||||
|
* @param buff - char pointer to buffer
|
||||||
|
* @param n - number of bytes in buffer to send
|
||||||
|
* @returns zero on failure
|
||||||
|
*/
|
||||||
|
int tx(uint8_t* buff, int n)
|
||||||
|
{
|
||||||
|
ssize_t bytes;
|
||||||
|
#if 0
|
||||||
|
int j = 0;
|
||||||
|
while(j < n) {
|
||||||
|
printf("%02x ",buff[j++]);
|
||||||
|
}
|
||||||
|
printf("tx %d byte(s)\n",n);
|
||||||
|
#endif
|
||||||
|
bytes = write(hSerial, buff, n);
|
||||||
|
if(bytes != n) {
|
||||||
|
printf("Error writing port\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int)bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* receive a buffer with a timeout
|
||||||
|
* @param buff - char pointer to buffer
|
||||||
|
* @param n - number of bytes in buffer to read
|
||||||
|
* @param timeout - timeout in milliseconds
|
||||||
|
* @returns number of bytes read or SERIAL_TIMEOUT
|
||||||
|
*/
|
||||||
|
int rx_timeout(uint8_t* buff, int n, int timeout)
|
||||||
|
{
|
||||||
|
ssize_t bytes = 0;
|
||||||
|
struct timeval toval;
|
||||||
|
fd_set set;
|
||||||
|
|
||||||
|
FD_ZERO(&set);
|
||||||
|
FD_SET(hSerial, &set);
|
||||||
|
|
||||||
|
toval.tv_sec = timeout / 1000;
|
||||||
|
toval.tv_usec = (timeout % 1000) * 1000;
|
||||||
|
|
||||||
|
if (select(hSerial + 1, &set, NULL, NULL, &toval) > 0) {
|
||||||
|
if (FD_ISSET(hSerial, &set))
|
||||||
|
bytes = read(hSerial, buff, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(bytes > 0 ? bytes : SERIAL_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hwreset ... resets Propeller hardware using DTR or RTS
|
||||||
|
* @param sparm - pointer to DCB serial control struct
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
void hwreset(void)
|
||||||
|
{
|
||||||
|
int cmd = use_rts_for_reset ? TIOCM_RTS : TIOCM_DTR;
|
||||||
|
ioctl(hSerial, TIOCMBIS, &cmd); /* assert bit */
|
||||||
|
msleep(10);
|
||||||
|
ioctl(hSerial, TIOCMBIC, &cmd); /* clear bit */
|
||||||
|
msleep(90);
|
||||||
|
tcflush(hSerial, TCIFLUSH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sleep for ms milliseconds
|
||||||
|
* @param ms - time to wait in milliseconds
|
||||||
|
*/
|
||||||
|
void msleep(int ms)
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
volatile struct timeb t0, t1;
|
||||||
|
do {
|
||||||
|
ftime((struct timeb*)&t0);
|
||||||
|
do {
|
||||||
|
ftime((struct timeb*)&t1);
|
||||||
|
} while (t1.millitm == t0.millitm);
|
||||||
|
} while(ms-- > 0);
|
||||||
|
#else
|
||||||
|
usleep(ms * 1000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ESC 0x1b /* escape from terminal mode */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* simple terminal emulator
|
||||||
|
*/
|
||||||
|
void terminal_mode(int check_for_exit, int pst_mode)
|
||||||
|
{
|
||||||
|
struct termios oldt, newt;
|
||||||
|
char buf[128], realbuf[256]; // double in case buf is filled with \r in PST mode
|
||||||
|
ssize_t cnt;
|
||||||
|
fd_set set;
|
||||||
|
int exit_char = 0xdead; /* not a valid character */
|
||||||
|
int sawexit_char = 0;
|
||||||
|
int sawexit_valid = 0;
|
||||||
|
int exitcode = 0;
|
||||||
|
int continue_terminal = 1;
|
||||||
|
|
||||||
|
tcgetattr(STDIN_FILENO, &oldt);
|
||||||
|
newt = oldt;
|
||||||
|
newt.c_lflag &= ~(ICANON | ECHO | ISIG);
|
||||||
|
newt.c_iflag &= ~(ICRNL | INLCR);
|
||||||
|
newt.c_oflag &= ~OPOST;
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
||||||
|
|
||||||
|
if (check_for_exit)
|
||||||
|
{
|
||||||
|
exit_char = 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* make it possible to detect breaks */
|
||||||
|
tcgetattr(hSerial, &newt);
|
||||||
|
newt.c_iflag &= ~IGNBRK;
|
||||||
|
newt.c_iflag |= PARMRK;
|
||||||
|
tcsetattr(hSerial, TCSANOW, &newt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do {
|
||||||
|
FD_ZERO(&set);
|
||||||
|
FD_SET(hSerial, &set);
|
||||||
|
FD_SET(STDIN_FILENO, &set);
|
||||||
|
if (select(hSerial + 1, &set, NULL, NULL, NULL) > 0) {
|
||||||
|
if (FD_ISSET(hSerial, &set)) {
|
||||||
|
if ((cnt = read(hSerial, buf, sizeof(buf))) > 0) {
|
||||||
|
int i;
|
||||||
|
// check for breaks
|
||||||
|
ssize_t realbytes = 0;
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
if (sawexit_valid)
|
||||||
|
{
|
||||||
|
exitcode = buf[i];
|
||||||
|
continue_terminal = 0;
|
||||||
|
}
|
||||||
|
else if (sawexit_char) {
|
||||||
|
if (buf[i] == 0) {
|
||||||
|
sawexit_valid = 1;
|
||||||
|
} else {
|
||||||
|
realbuf[realbytes++] = exit_char;
|
||||||
|
realbuf[realbytes++] = buf[i];
|
||||||
|
sawexit_char = 0;
|
||||||
|
}
|
||||||
|
} else if (((int)buf[i] & 0xff) == exit_char) {
|
||||||
|
sawexit_char = 1;
|
||||||
|
} else {
|
||||||
|
realbuf[realbytes++] = buf[i];
|
||||||
|
if (pst_mode && buf[i] == '\r')
|
||||||
|
realbuf[realbytes++] = '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write(fileno(stdout), realbuf, realbytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FD_ISSET(STDIN_FILENO, &set)) {
|
||||||
|
if ((cnt = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < cnt; ++i) {
|
||||||
|
//printf("%02x\n", buf[i]);
|
||||||
|
if (buf[i] == ESC)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
write(hSerial, buf, cnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (continue_terminal);
|
||||||
|
|
||||||
|
done:
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
||||||
|
|
||||||
|
if (sawexit_valid)
|
||||||
|
{
|
||||||
|
exit(exitcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include "ploader.h"
|
||||||
|
#include "osint.h"
|
||||||
|
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE 1
|
||||||
|
#define FALSE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* port prefix */
|
||||||
|
#if defined(CYGWIN) || defined(WIN32) || defined(MINGW)
|
||||||
|
#define PORT_PREFIX ""
|
||||||
|
#elif defined(LINUX)
|
||||||
|
#define PORT_PREFIX "ttyUSB"
|
||||||
|
#elif defined(MACOSX)
|
||||||
|
#define PORT_PREFIX "cu.usbserial"
|
||||||
|
#else
|
||||||
|
#define PORT_PREFIX ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* defaults */
|
||||||
|
#define BAUD_RATE 115200
|
||||||
|
|
||||||
|
/* CheckPort state structure */
|
||||||
|
typedef struct {
|
||||||
|
int baud;
|
||||||
|
int verbose;
|
||||||
|
char *actualport;
|
||||||
|
} CheckPortInfo;
|
||||||
|
|
||||||
|
/* CheckPort result codes */
|
||||||
|
enum {
|
||||||
|
CHECK_PORT_OK,
|
||||||
|
CHECK_PORT_OPEN_FAILED,
|
||||||
|
CHECK_PORT_NO_PROPELLER
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cb_reset(void *data) { hwreset(); }
|
||||||
|
static int cb_tx(void *data, uint8_t* buf, int n) { return tx(buf, n); }
|
||||||
|
static int cb_rx_timeout(void *data, uint8_t* buf, int n, int timeout) { return rx_timeout(buf, n, timeout); }
|
||||||
|
|
||||||
|
static PL_serial serial = { cb_reset, cb_tx, cb_rx_timeout };
|
||||||
|
static PL_state state;
|
||||||
|
static int version;
|
||||||
|
|
||||||
|
static void Usage(void);
|
||||||
|
static uint8_t *ReadEntireFile(char *name, long *pSize);
|
||||||
|
static int ShowPort(const char* port, void* data);
|
||||||
|
static void ShowPorts(char *prefix);
|
||||||
|
static int CheckPort(const char* port, void* data);
|
||||||
|
static int InitPort(char *prefix, char *port, int baud, int verbose, char *actualport);
|
||||||
|
static int OpenPort(const char* port, int baud);
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
char actualPort[PATH_MAX], *port, *p;
|
||||||
|
int baudRate, baudRate2, verbose, terminalMode, pstMode, i;
|
||||||
|
char *file = NULL;
|
||||||
|
int loadType = 0;
|
||||||
|
long imageSize;
|
||||||
|
uint8_t *image;
|
||||||
|
|
||||||
|
/* initialize */
|
||||||
|
baudRate = baudRate2 = BAUD_RATE;
|
||||||
|
verbose = terminalMode = pstMode = FALSE;
|
||||||
|
port = NULL;
|
||||||
|
|
||||||
|
/* initialize the loader */
|
||||||
|
PL_Init(&state, &serial, NULL);
|
||||||
|
|
||||||
|
/* process the position-independent arguments */
|
||||||
|
for (i = 1; i < argc; ++i) {
|
||||||
|
|
||||||
|
/* handle switches */
|
||||||
|
if (argv[i][0] == '-') {
|
||||||
|
switch (argv[i][1]) {
|
||||||
|
case 'b':
|
||||||
|
if (argv[i][2])
|
||||||
|
p = &argv[i][2];
|
||||||
|
else if (++i < argc)
|
||||||
|
p = argv[i];
|
||||||
|
else
|
||||||
|
Usage();
|
||||||
|
if (*p != ':')
|
||||||
|
baudRate = baudRate2 = atoi(p);
|
||||||
|
if ((p = strchr(p, ':')) != NULL) {
|
||||||
|
if (*++p != ':' && *p != '\0')
|
||||||
|
baudRate2 = atoi(p);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
loadType |= LOAD_TYPE_EEPROM;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (argv[i][2])
|
||||||
|
port = &argv[i][2];
|
||||||
|
else if (++i < argc)
|
||||||
|
port = argv[i];
|
||||||
|
else
|
||||||
|
Usage();
|
||||||
|
#if defined(CYGWIN) || defined(WIN32) || defined(LINUX)
|
||||||
|
if (isdigit((int)port[0])) {
|
||||||
|
#if defined(CYGWIN) || defined(WIN32)
|
||||||
|
static char buf[10];
|
||||||
|
sprintf(buf, "COM%d", atoi(port));
|
||||||
|
port = buf;
|
||||||
|
#endif
|
||||||
|
#if defined(LINUX)
|
||||||
|
static char buf[64];
|
||||||
|
sprintf(buf, "/dev/ttyUSB%d", atoi(port));
|
||||||
|
port = buf;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(MACOSX)
|
||||||
|
if (port[0] != '/') {
|
||||||
|
static char buf[64];
|
||||||
|
sprintf(buf, "/dev/cu.usbserial-%s", port);
|
||||||
|
port = buf;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
ShowPorts(PORT_PREFIX);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
loadType |= LOAD_TYPE_RUN;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
pstMode = TRUE;
|
||||||
|
// fall through
|
||||||
|
case 't':
|
||||||
|
terminalMode = TRUE;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
verbose = TRUE;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
/* fall through */
|
||||||
|
default:
|
||||||
|
Usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle the input filename */
|
||||||
|
else {
|
||||||
|
if (file)
|
||||||
|
Usage();
|
||||||
|
file = argv[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (InitPort(PORT_PREFIX, port, baudRate, verbose, actualPort)) {
|
||||||
|
case CHECK_PORT_OK:
|
||||||
|
printf("Found propeller version %d on %s\n", version, actualPort);
|
||||||
|
break;
|
||||||
|
case CHECK_PORT_OPEN_FAILED:
|
||||||
|
printf("error: opening serial port '%s'\n", port);
|
||||||
|
perror("Error is ");
|
||||||
|
return 1;
|
||||||
|
case CHECK_PORT_NO_PROPELLER:
|
||||||
|
if (port)
|
||||||
|
printf("error: no propeller chip on port '%s'\n", port);
|
||||||
|
else
|
||||||
|
printf("error: can't find a port with a propeller chip\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
if ((image = ReadEntireFile(file, &imageSize)) != NULL) {
|
||||||
|
printf("Loading '%s' (%ld bytes)\n", file, imageSize);
|
||||||
|
if (PL_LoadSpinBinary(&state, loadType, image, imageSize) != 0)
|
||||||
|
printf("Load failed!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* enter terminal mode if requested */
|
||||||
|
if (terminalMode) {
|
||||||
|
printf("[ Entering terminal mode. Type ESC or Control-C to exit. ]\n");
|
||||||
|
fflush(stdout);
|
||||||
|
if (baudRate2 != baudRate)
|
||||||
|
serial_baud(baudRate2);
|
||||||
|
terminal_mode(FALSE, pstMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Usage - display a usage message and exit */
|
||||||
|
static void Usage(void)
|
||||||
|
{
|
||||||
|
printf("\
|
||||||
|
p1load - a simple loader for the propeller - version 0.009, 2014-08-10\n\
|
||||||
|
usage: p1load\n\
|
||||||
|
[ -b baud ] baud rate (default is %d)\n\
|
||||||
|
[ -p port ] serial port (default is to auto-detect the port)\n\
|
||||||
|
[ -e ] write a bootable image to EEPROM\n\
|
||||||
|
[ -P ] list available serial ports\n\
|
||||||
|
[ -r ] run the program after loading\n\
|
||||||
|
[ -t ] enter terminal mode after running the program\n\
|
||||||
|
[ -T ] enter PST-compatible terminal mode\n\
|
||||||
|
[ -v ] verbose output\n\
|
||||||
|
[ -? ] display a usage message and exit\n\
|
||||||
|
file[,addr]... files to load\n", BAUD_RATE);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ReadEntireFile - read an entire file into an allocated buffer */
|
||||||
|
static uint8_t *ReadEntireFile(char *name, long *pSize)
|
||||||
|
{
|
||||||
|
uint8_t *buf;
|
||||||
|
long size;
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
/* open the file in binary mode */
|
||||||
|
if (!(fp = fopen(name, "rb")))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* get the file size */
|
||||||
|
fseek(fp, 0L, SEEK_END);
|
||||||
|
size = ftell(fp);
|
||||||
|
fseek(fp, 0L, SEEK_SET);
|
||||||
|
|
||||||
|
/* allocate a buffer for the file contents */
|
||||||
|
if (!(buf = (uint8_t *)malloc(size))) {
|
||||||
|
fclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read the contents of the file into the buffer */
|
||||||
|
if (fread(buf, 1, size, fp) != size) {
|
||||||
|
free(buf);
|
||||||
|
fclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* close the file ad return the buffer containing the file contents */
|
||||||
|
*pSize = size;
|
||||||
|
fclose(fp);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ShowPort(const char* port, void* data)
|
||||||
|
{
|
||||||
|
printf("%s\n", port);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ShowPorts(char *prefix)
|
||||||
|
{
|
||||||
|
serial_find(prefix, ShowPort, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int CheckPort(const char* port, void* data)
|
||||||
|
{
|
||||||
|
CheckPortInfo* info = (CheckPortInfo*)data;
|
||||||
|
int rc;
|
||||||
|
if (info->verbose)
|
||||||
|
printf("Trying %s \n", port); fflush(stdout);
|
||||||
|
if ((rc = OpenPort(port, info->baud)) != CHECK_PORT_OK)
|
||||||
|
return rc;
|
||||||
|
if (info->actualport) {
|
||||||
|
strncpy(info->actualport, port, PATH_MAX - 1);
|
||||||
|
info->actualport[PATH_MAX - 1] = '\0';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int InitPort(char *prefix, char *port, int baud, int verbose, char *actualport)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
|
if (port) {
|
||||||
|
if (actualport) {
|
||||||
|
strncpy(actualport, port, PATH_MAX - 1);
|
||||||
|
actualport[PATH_MAX - 1] = '\0';
|
||||||
|
}
|
||||||
|
result = OpenPort(port, baud);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CheckPortInfo info;
|
||||||
|
info.baud = baud;
|
||||||
|
info.verbose = verbose;
|
||||||
|
info.actualport = actualport;
|
||||||
|
if (serial_find(prefix, CheckPort, &info) == 0)
|
||||||
|
result = CHECK_PORT_OK;
|
||||||
|
else
|
||||||
|
result = CHECK_PORT_NO_PROPELLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int OpenPort(const char* port, int baud)
|
||||||
|
{
|
||||||
|
/* open the port */
|
||||||
|
if (serial_init(port, baud) == 0)
|
||||||
|
return CHECK_PORT_OPEN_FAILED;
|
||||||
|
|
||||||
|
/* check for a propeller on this port */
|
||||||
|
if (!PL_HardwareFound(&state, &version)) {
|
||||||
|
serial_done();
|
||||||
|
return CHECK_PORT_NO_PROPELLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CHECK_PORT_OK;
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ploader.h"
|
||||||
|
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE 1
|
||||||
|
#define FALSE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ACK_TIMEOUT 20
|
||||||
|
|
||||||
|
static void SerialInit(PL_state *state);
|
||||||
|
static void TByte(PL_state *state, uint8_t x);
|
||||||
|
static void TLong(PL_state *state, uint32_t x);
|
||||||
|
static void TComm(PL_state *state);
|
||||||
|
static int RBit(PL_state *state, int timeout);
|
||||||
|
static int IterateLFSR(PL_state *state);
|
||||||
|
|
||||||
|
void PL_Init(PL_state *state, PL_serial *serial, void *data)
|
||||||
|
{
|
||||||
|
state->serial = serial;
|
||||||
|
state->serialData = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PL_LoadSpinBinary - load a spin binary using the rom loader */
|
||||||
|
int PL_LoadSpinBinary(PL_state *state, int loadType, uint8_t *image, int size)
|
||||||
|
{
|
||||||
|
int retries = 100;
|
||||||
|
uint8_t buf[1];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
TLong(state, loadType);
|
||||||
|
TLong(state, size / sizeof(uint32_t));
|
||||||
|
|
||||||
|
/* download the spin binary */
|
||||||
|
for (i = 0; i < size; i += 4) {
|
||||||
|
uint32_t data = image[i] | (image[i + 1] << 8) | (image[i + 2] << 16) | (image[i + 3] << 24);
|
||||||
|
TLong(state, data);
|
||||||
|
}
|
||||||
|
TComm(state);
|
||||||
|
usleep(1000);
|
||||||
|
|
||||||
|
/* wait for an ACK */
|
||||||
|
while (--retries >= 0) {
|
||||||
|
TByte(state, 0xf9);
|
||||||
|
TComm(state);
|
||||||
|
if ((*state->serial->rx_timeout)(state->serialData, buf, 1, ACK_TIMEOUT) <= 0)
|
||||||
|
continue;
|
||||||
|
if (buf[0] == 0xfe)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retries >= 0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this code is adapted from Chip Gracey's PNut IDE */
|
||||||
|
|
||||||
|
int PL_HardwareFound(PL_state *state, int *pVersion)
|
||||||
|
{
|
||||||
|
int version, i;
|
||||||
|
|
||||||
|
/* initialize the serial buffers */
|
||||||
|
SerialInit(state);
|
||||||
|
|
||||||
|
/* reset the propeller */
|
||||||
|
(*state->serial->reset)(state->serialData);
|
||||||
|
|
||||||
|
/* send the connect string + blanks for echoes */
|
||||||
|
TByte(state, 0xf9);
|
||||||
|
state->lfsr = 'P';
|
||||||
|
for (i = 0; i < 250; ++i)
|
||||||
|
TByte(state, IterateLFSR(state) | 0xfe);
|
||||||
|
for (i = 0; i < 250 + 8; ++i)
|
||||||
|
TByte(state, 0xf9);
|
||||||
|
TComm(state);
|
||||||
|
|
||||||
|
/* receive the connect string */
|
||||||
|
for (i = 0; i < 250; ++i)
|
||||||
|
if (RBit(state, 100) != IterateLFSR(state))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* receive the chip version */
|
||||||
|
for (version = i = 0; i < 8; ++i) {
|
||||||
|
int bit = RBit(state, 50);
|
||||||
|
if (bit < 0) {
|
||||||
|
return FALSE;
|
||||||
|
version = ((version >> 1) & 0x7f) | (bit << 7);
|
||||||
|
}
|
||||||
|
*pVersion = version;
|
||||||
|
|
||||||
|
/* return successfully */
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SerialInit - initialize the serial buffers */
|
||||||
|
static void SerialInit(PL_state *state)
|
||||||
|
{
|
||||||
|
state->txcnt = 0;
|
||||||
|
state->rxnext = 0;
|
||||||
|
state->rxcnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TByte - add a byte to the transmit buffer */
|
||||||
|
static void TByte(PL_state *state, uint8_t x)
|
||||||
|
{
|
||||||
|
state->txbuf[state->txcnt++] = x;
|
||||||
|
if (state->txcnt >= sizeof(state->txbuf))
|
||||||
|
TComm(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TLong - add a long to the transmit buffer */
|
||||||
|
static void TLong(PL_state *state, uint32_t x)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 11; ++i) {
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// p2 code
|
||||||
|
uint8_t byte = 0x92
|
||||||
|
| ((i == 10 ? -1 : 0) & 0x60)
|
||||||
|
| ((x >> 31) & 1)
|
||||||
|
| (((x >> 30) & 1) << 3)
|
||||||
|
| (((x >> 29) & 1) << 6);
|
||||||
|
TByte(state, byte);
|
||||||
|
x <<= 3;
|
||||||
|
#else
|
||||||
|
// p1 code
|
||||||
|
uint8_t byte = 0x92
|
||||||
|
| ((i == 10 ? -1 : 0) & 0x60)
|
||||||
|
| (x & 1)
|
||||||
|
| ((x & 2) << 2)
|
||||||
|
| ((x & 4) << 4);
|
||||||
|
TByte(state, byte);
|
||||||
|
x >>= 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TComm - write the transmit buffer to the port */
|
||||||
|
static void TComm(PL_state *state)
|
||||||
|
{
|
||||||
|
(*state->serial->tx)(state->serialData, state->txbuf, state->txcnt);
|
||||||
|
state->txcnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RBit - receive a bit with a timeout */
|
||||||
|
static int RBit(PL_state *state, int timeout)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
for (;;) {
|
||||||
|
if (state->rxnext >= state->rxcnt) {
|
||||||
|
state->rxcnt = (*state->serial->rx_timeout)(state->serialData, state->rxbuf, sizeof(state->rxbuf), timeout);
|
||||||
|
if (state->rxcnt <= 0) {
|
||||||
|
/* hardware lost */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
state->rxnext = 0;
|
||||||
|
}
|
||||||
|
result = state->rxbuf[state->rxnext++] - 0xfe;
|
||||||
|
if ((result & 0xfe) == 0)
|
||||||
|
return result;
|
||||||
|
/* checksum error */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IterateLFSR - get the next bit in the lfsr sequence */
|
||||||
|
static int IterateLFSR(PL_state *state)
|
||||||
|
{
|
||||||
|
uint8_t lfsr = state->lfsr;
|
||||||
|
int result = lfsr & 1;
|
||||||
|
state->lfsr = ((lfsr << 1) & 0xfe) | (((lfsr >> 7) ^ (lfsr >> 5) ^ (lfsr >> 4) ^ (lfsr >> 1)) & 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end of code adapted from Chip Gracey's PNut IDE */
|
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef __PLOADER_H__
|
||||||
|
#define __PLOADER_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOAD_TYPE_SHUTDOWN 0
|
||||||
|
#define LOAD_TYPE_RUN 1
|
||||||
|
#define LOAD_TYPE_EEPROM 2
|
||||||
|
#define LOAD_TYPE_EEPROM_RUN 3
|
||||||
|
|
||||||
|
/* serial port interface - filled in by the user prior to calling PL_Init */
|
||||||
|
typedef struct {
|
||||||
|
void (*reset)(void *data);
|
||||||
|
int (*tx)(void *data, uint8_t* buf, int n);
|
||||||
|
int (*rx_timeout)(void *data, uint8_t* buf, int n, int timeout);
|
||||||
|
} PL_serial;
|
||||||
|
|
||||||
|
/* loader state structure - filled in by the loader functions */
|
||||||
|
typedef struct {
|
||||||
|
PL_serial *serial;
|
||||||
|
void *serialData;
|
||||||
|
uint8_t txbuf[1024];
|
||||||
|
int txcnt;
|
||||||
|
uint8_t rxbuf[1024];
|
||||||
|
int rxnext;
|
||||||
|
int rxcnt;
|
||||||
|
uint8_t lfsr;
|
||||||
|
} PL_state;
|
||||||
|
|
||||||
|
/* PL_Init - Initializes the loader state structure. */
|
||||||
|
void PL_Init(PL_state *state, PL_serial *serial, void *data);
|
||||||
|
|
||||||
|
/* PL_HardwareFound - Sends the handshake sequence and returns non-zero if a Propeller
|
||||||
|
chip is found on the serial interface and also sets the version parameter to the
|
||||||
|
chip version.
|
||||||
|
*/
|
||||||
|
int PL_HardwareFound(PL_state *state, int *pVersion);
|
||||||
|
|
||||||
|
/* PL_LoadSpinBinary - Loads a Spin binary image. Must be called immediatel following a
|
||||||
|
successful call to PL_HardwareFound.
|
||||||
|
*/
|
||||||
|
int PL_LoadSpinBinary(PL_state *state, int loadType, uint8_t *image, int size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue