Compare commits

...

51 Commits

Author SHA1 Message Date
Greg Shuflin
c1913d9943 Bump Rust edition to 2021 2022-04-23 16:36:33 -07:00
Greg Shuflin
b76a115054 Some in-progress work 2021-11-14 23:52:00 -08:00
Greg Shuflin
c56fa32b2c Use rust serial crate for reading from serial
Incomplete - can successfully initiate contact w/ radio but can't read
data
2021-03-10 02:52:59 -08:00
Greg Shuflin
963dc072c9 Port more serial stuff to Rust 2021-03-04 19:07:51 -08:00
Greg Shuflin
23fc703abd Move ident manipulation into isolated block 2021-03-04 01:25:26 -08:00
Greg Shuflin
9015d9f8e0 Remove serial_init C code 2021-03-04 00:54:54 -08:00
Greg Shuflin
fc45f65a52 Port serial_init 2021-03-04 00:44:52 -08:00
Greg Shuflin
fc15b1d1db Remove radio.c 2021-03-03 01:19:14 -08:00
Greg Shuflin
3f4b18368b parse_config port to rust 2021-03-03 01:17:50 -08:00
Greg Shuflin
5658759cf3 Move active pointer code to Rust 2021-03-01 23:32:38 -08:00
Greg Shuflin
0acf55a971 Move static radio_mem to rust 2021-03-01 23:25:54 -08:00
Greg Shuflin
8430209828 Port radio_write_csv 2021-03-01 22:20:28 -08:00
Greg Shuflin
f7aa60df21 Port radio_print_config 2021-03-01 21:14:53 -08:00
Greg Shuflin
00570e0e91 Consolidate separate print_config functions 2021-03-01 20:46:53 -08:00
Greg Shuflin
fed7cea057 Add chrono crate 2021-03-01 20:36:42 -08:00
Greg Shuflin
98eaa63740 Port radio_read_image to rust 2021-03-01 19:43:01 -08:00
Greg Shuflin
7a39fd1ae2 Move radio_is_compatible to Rust 2021-03-01 03:18:39 -08:00
Greg Shuflin
ad5b0547e4 Put radio table into rust 2021-03-01 03:03:54 -08:00
Greg Shuflin
4f082277d8 Use rust idiom for listing radios 2021-03-01 02:57:53 -08:00
Greg Shuflin
7793870f59 Radio table in rust 2021-03-01 02:54:19 -08:00
Greg Shuflin
ea9ff0a534 Use static radio_device_t vars 2021-03-01 02:29:53 -08:00
Greg Shuflin
c921cc54d6 radio_save_image port 2021-03-01 02:02:45 -08:00
Greg Shuflin
ca27d4035f Transfer print_version, verify_config 2021-03-01 01:52:49 -08:00
Greg Shuflin
8fd7e82951 Port upload 2021-03-01 01:06:01 -08:00
Greg Shuflin
25571023a3 Use Radio wrapper type 2021-03-01 00:54:36 -08:00
Greg Shuflin
61544fd23f Move download to rust 2021-03-01 00:43:25 -08:00
Greg Shuflin
cccbee30a8 Remove global radio_progress
Change to function-specific counters
2021-03-01 00:28:25 -08:00
Greg Shuflin
2a7c32924c move radio_connect to rust 2021-03-01 00:11:41 -08:00
Greg Shuflin
b1116daba2 WIP radio connect 2021-02-28 23:52:22 -08:00
Greg Shuflin
7fbcba54fd Don't directly read static device 2021-02-28 23:52:03 -08:00
Greg Shuflin
8f544cfa95 Compute radio list in rust 2021-02-28 19:41:21 -08:00
Greg Shuflin
e8099fc681 Use bindgen for rust bindings 2021-02-28 12:27:27 -08:00
Greg Shuflin
cece963e91 Remove radio disconnect 2021-02-28 11:33:05 -08:00
Greg Shuflin
2315ee4a74 Remove most of the code referencing the static device var
Replace it with passing pointers around
2021-02-28 10:48:44 -08:00
Greg Shuflin
c2471516fd Move C header comments to Rust doc comments 2021-02-28 02:53:19 -08:00
Greg Shuflin
05b7842053 make radio_upload not use static device 2021-02-28 02:40:00 -08:00
Greg Shuflin
863f14b0c2 radio::upload 2021-02-28 02:38:07 -08:00
Greg Shuflin
cd74a703c1 Modify C code to not use static radio_device_t*
Note: this changes program semantics slightly
2021-02-28 02:33:53 -08:00
Greg Shuflin
4183e0afc5 Move code to radio module 2021-02-28 01:31:29 -08:00
Greg Shuflin
48faf189c8 Cleanup from removing most of main.c 2021-02-28 01:26:03 -08:00
Greg Shuflin
48552c64f6 Remove old_c_main entirely 2021-02-28 01:19:31 -08:00
Greg Shuflin
24d7a6b495 Port write functionality 2021-02-28 00:22:06 -08:00
Greg Shuflin
c44e3e15c5 Pass version in from build 2021-02-28 00:00:31 -08:00
Greg Shuflin
d41d257650 More options logic 2021-02-27 23:50:31 -08:00
Greg Shuflin
1c81834b62 More options processing in Rust 2021-02-27 23:37:41 -08:00
Greg Shuflin
88e8fb48d8 Create radio module 2021-02-27 23:16:01 -08:00
Greg Shuflin
c7c746a383 Move radio_list() to Rust 2021-02-27 22:59:06 -08:00
Greg Shuflin
2a91efd07f Start adding Rust getopts 2021-02-27 22:53:16 -08:00
Greg Shuflin
61a142d570 Remove print_message_from_rust 2021-02-27 21:27:55 -08:00
Greg Shuflin
b64211f27c Port usage() 2021-02-27 21:27:19 -08:00
Greg Shuflin
d55f8fd5ed Hello world rust 2021-02-27 21:01:44 -08:00
18 changed files with 1074 additions and 678 deletions

6
.gitignore vendored
View File

@ -2,3 +2,9 @@
*.hd *.hd
dmrconfig dmrconfig
dmrconfig.exe dmrconfig.exe
# Added by cargo
/target
Cargo.lock

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "dmrconfig"
version = "0.1.0"
authors = ["Greg Shuflin <greg.shuflin@protonmail.com>"]
edition = "2021"
[lib]
crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2"
getopts = "0.2"
chrono = "0.4"
serial = "0.4"
[build-dependencies]
bindgen = "0.53.1"

View File

@ -3,8 +3,8 @@ CC ?= gcc
VERSION = $(shell git describe --tags --abbrev=0) VERSION = $(shell git describe --tags --abbrev=0)
GITCOUNT = $(shell git rev-list HEAD --count) GITCOUNT = $(shell git rev-list HEAD --count)
UNAME = $(shell uname) UNAME = $(shell uname)
VERSION_STR = '$(VERSION).$(GITCOUNT)'
OBJS = main.o util.o radio.o dfu-libusb.o uv380.o md380.o rd5r.o \ OBJS = main.o util.o dfu-libusb.o uv380.o md380.o rd5r.o \
gd77.o hid.o serial.o d868uv.o dm1801.o gd77.o hid.o serial.o d868uv.o dm1801.o
CFLAGS ?= -g -O -Wall -Werror CFLAGS ?= -g -O -Wall -Werror
CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \ CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \
@ -12,6 +12,8 @@ CFLAGS += -DVERSION='"$(VERSION).$(GITCOUNT)"' \
LDFLAGS ?= -g LDFLAGS ?= -g
LIBS = $(shell pkg-config --libs --static libusb-1.0) LIBS = $(shell pkg-config --libs --static libusb-1.0)
RUST_RELEASE := target/release/libdmrconfig.a
# #
# Make sure pkg-config is installed. # Make sure pkg-config is installed.
# #
@ -48,8 +50,8 @@ endif
all: dmrconfig all: dmrconfig
dmrconfig: $(OBJS) dmrconfig: $(OBJS) $(RUST_RELEASE)
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(RUST_RELEASE) -ldl $(LIBS)
clean: clean:
rm -f *~ *.o core dmrconfig dmrconfig.exe rm -f *~ *.o core dmrconfig dmrconfig.exe
@ -57,6 +59,10 @@ clean:
install: dmrconfig install: dmrconfig
install -c -s dmrconfig /usr/local/bin/dmrconfig install -c -s dmrconfig /usr/local/bin/dmrconfig
.PHONY: $(RUST_RELEASE)
target/release/libdmrconfig.a:
VERSION=$(VERSION_STR) cargo build --verbose --release
### ###
d868uv.o: d868uv.c radio.h util.h d868uv-map.h d868uv.o: d868uv.c radio.h util.h d868uv-map.h
dfu-libusb.o: dfu-libusb.c util.h dfu-libusb.o: dfu-libusb.c util.h
@ -68,7 +74,6 @@ hid-macos.o: hid-macos.c util.h
hid-windows.o: hid-windows.c util.h hid-windows.o: hid-windows.c util.h
main.o: main.c radio.h util.h main.o: main.c radio.h util.h
md380.o: md380.c radio.h util.h md380.o: md380.c radio.h util.h
radio.o: radio.c radio.h util.h
rd5r.o: rd5r.c radio.h util.h rd5r.o: rd5r.c radio.h util.h
serial.o: serial.c util.h serial.o: serial.c util.h
util.o: util.c util.h util.o: util.c util.h

49
build.rs Normal file
View File

@ -0,0 +1,49 @@
extern crate bindgen;
use std::env;
use std::path::PathBuf;
fn main() {
// Tell cargo to invalidate the built crate whenever the wrapper changes
println!("cargo:rerun-if-changed=radio.h");
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header("radio.h")
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.whitelist_type("radio_device_t")
.whitelist_var("radio_md380")
.whitelist_var("radio_md390")
.whitelist_var("radio_uv380")
.whitelist_var("radio_uv390")
.whitelist_var("radio_md2017")
.whitelist_var("radio_md9600")
.whitelist_var("radio_rd5r")
.whitelist_var("radio_dm1801")
.whitelist_var("radio_rt84")
.whitelist_var("radio_gd77")
.whitelist_var("radio_d868uv")
.whitelist_var("radio_d878uv")
.whitelist_var("radio_dmr6x2")
.whitelist_var("radio_d900")
.whitelist_var("radio_dp880")
.whitelist_var("radio_rt27d")
.blacklist_type("FILE")
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings for radio.h");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

View File

@ -347,7 +347,7 @@ static const char *identify()
// Return the first path found (dynamically allocated). // Return the first path found (dynamically allocated).
// Return 0 when no device with such GUID is present. // Return 0 when no device with such GUID is present.
// //
static char *find_path(GUID *guid) static char *find_path_for_guid(GUID *guid)
{ {
char *path = 0; char *path = 0;
@ -392,7 +392,7 @@ const char *dfu_init(unsigned vid, unsigned pid)
// Find path for device. // Find path for device.
if (vid == 0x0483 && pid == 0xdf11) { if (vid == 0x0483 && pid == 0xdf11) {
path = find_path(&guid_0483_df11); path = find_path_for_guid(&guid_0483_df11);
} else { } else {
fprintf(stderr, "No guid for vid=%04x, pid=%04x!\n", vid, pid); fprintf(stderr, "No guid for vid=%04x, pid=%04x!\n", vid, pid);
exit(-1); exit(-1);

View File

@ -348,6 +348,7 @@ static void dm1801_print_version(radio_device_t *radio, FILE *out)
static void download(radio_device_t *radio) static void download(radio_device_t *radio)
{ {
int bno; int bno;
int radio_progress = 0;
// Read range 0x80...0x1ee5f. // Read range 0x80...0x1ee5f.
#define NBLK 989 #define NBLK 989
@ -390,6 +391,7 @@ static void dm1801_download(radio_device_t *radio)
static void dm1801_upload(radio_device_t *radio, int cont_flag) static void dm1801_upload(radio_device_t *radio, int cont_flag)
{ {
int bno; int bno;
int radio_progress = 0;
// Write range 0x80...0x1ee5f. // Write range 0x80...0x1ee5f.
for (bno = 1; bno < NBLK; bno++) { for (bno = 1; bno < NBLK; bno++) {

2
gd77.c
View File

@ -348,6 +348,7 @@ static void gd77_print_version(radio_device_t *radio, FILE *out)
static void download(radio_device_t *radio) static void download(radio_device_t *radio)
{ {
int bno; int bno;
int radio_progress = 0;
// Read range 0x80...0x1e29f. // Read range 0x80...0x1e29f.
for (bno=1; bno<966; bno++) { for (bno=1; bno<966; bno++) {
@ -388,6 +389,7 @@ static void gd77_download(radio_device_t *radio)
static void gd77_upload(radio_device_t *radio, int cont_flag) static void gd77_upload(radio_device_t *radio, int cont_flag)
{ {
int bno; int bno;
int radio_progress = 0;
// Write range 0x80...0x1e29f. // Write range 0x80...0x1e29f.
for (bno=1; bno<966; bno++) { for (bno=1; bno<966; bno++) {

158
main.c
View File

@ -32,163 +32,15 @@
#include "util.h" #include "util.h"
const char version[] = VERSION; const char version[] = VERSION;
const char *copyright;
extern char *optarg; extern char *optarg;
extern int optind; extern int optind;
extern int rust_main(int argc, char** argv);
//TODO handle this correctly
int trace_flag = 0; int trace_flag = 0;
void usage() int main(int argc, char **argv) {
{ return rust_main(argc, argv);
fprintf(stderr, "DMR Config, Version %s, %s\n", version, copyright);
fprintf(stderr, "Usage:\n");
fprintf(stderr, " dmrconfig -r [-t]\n");
fprintf(stderr, " Read codeplug from the radio to a file 'device.img'.\n");
fprintf(stderr, " Save configuration to a text file 'device.conf'.\n");
fprintf(stderr, " dmrconfig -w [-t] file.img\n");
fprintf(stderr, " Write codeplug to the radio.\n");
fprintf(stderr, " dmrconfig -v [-t] file.conf\n");
fprintf(stderr, " Verify configuration script for the radio.\n");
fprintf(stderr, " dmrconfig -c [-t] file.conf\n");
fprintf(stderr, " Apply configuration script to the radio.\n");
fprintf(stderr, " dmrconfig -c file.img file.conf\n");
fprintf(stderr, " Apply configuration script to the codeplug image.\n");
fprintf(stderr, " Store modified copy to a file 'device.img'.\n");
fprintf(stderr, " dmrconfig file.img\n");
fprintf(stderr, " Display configuration from the codeplug image.\n");
fprintf(stderr, " dmrconfig -u [-t] file.csv\n");
fprintf(stderr, " Update contacts database from CSV file.\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -r Read codeplug from the radio.\n");
fprintf(stderr, " -w Write codeplug to the radio.\n");
fprintf(stderr, " -c Configure the radio from a text script.\n");
fprintf(stderr, " -v Verify config file.\n");
fprintf(stderr, " -u Update contacts database.\n");
fprintf(stderr, " -l List all supported radios.\n");
fprintf(stderr, " -t Trace USB protocol.\n");
exit(-1);
}
int main(int argc, char **argv)
{
int read_flag = 0, write_flag = 0, config_flag = 0, csv_flag = 0;
int list_flag = 0, verify_flag = 0;
copyright = "Copyright (C) 2018 Serge Vakulenko KK6ABQ";
trace_flag = 0;
for (;;) {
switch (getopt(argc, argv, "tcwrulv")) {
case 't': ++trace_flag; continue;
case 'r': ++read_flag; continue;
case 'w': ++write_flag; continue;
case 'c': ++config_flag; continue;
case 'u': ++csv_flag; continue;
case 'l': ++list_flag; continue;
case 'v': ++verify_flag; continue;
default:
usage();
case EOF:
break;
}
break;
}
argc -= optind;
argv += optind;
if (list_flag) {
radio_list();
exit(0);
}
if (read_flag + write_flag + config_flag + csv_flag + verify_flag > 1) {
fprintf(stderr, "Only one of -r, -w, -c, -v or -u options is allowed.\n");
usage();
}
setvbuf(stdout, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IOLBF, 0);
if (write_flag) {
// Restore image file to device.
if (argc != 1)
usage();
radio_connect();
radio_read_image(argv[0]);
radio_print_version(stdout);
radio_upload(0);
radio_disconnect();
} else if (config_flag) {
if (argc != 1 && argc != 2)
usage();
if (argc == 2) {
// Apply text config to image file.
radio_read_image(argv[0]);
radio_print_version(stdout);
radio_parse_config(argv[1]);
radio_verify_config();
radio_save_image("device.img");
} else {
// Update device from text config file.
radio_connect();
radio_download();
radio_print_version(stdout);
radio_save_image("backup.img");
radio_parse_config(argv[0]);
radio_verify_config();
radio_upload(1);
radio_disconnect();
}
} else if (verify_flag) {
if (argc != 1)
usage();
// Verify text config file.
radio_connect();
radio_parse_config(argv[0]);
radio_verify_config();
radio_disconnect();
} else if (read_flag) {
if (argc != 0)
usage();
// Dump device to image file.
radio_connect();
radio_download();
radio_print_version(stdout);
radio_disconnect();
radio_save_image("device.img");
// Print configuration to file.
const char *filename = "device.conf";
printf("Print configuration to file '%s'.\n", filename);
FILE *conf = fopen(filename, "w");
if (!conf) {
perror(filename);
exit(-1);
}
radio_print_config(conf, 1);
fclose(conf);
} else if (csv_flag) {
// Update contacts database on the device.
if (argc != 1)
usage();
radio_connect();
radio_write_csv(argv[0]);
radio_disconnect();
} else {
if (argc != 1)
usage();
// Print configuration from image file.
radio_read_image(argv[0]);
radio_print_config(stdout, !isatty(1));
}
return 0;
} }

View File

@ -365,6 +365,7 @@ static void md380_print_version(radio_device_t *radio, FILE *out)
static void md380_download(radio_device_t *radio) static void md380_download(radio_device_t *radio)
{ {
int bno; int bno;
int radio_progress = 0;
for (bno=0; bno<MEMSZ/1024; bno++) { for (bno=0; bno<MEMSZ/1024; bno++) {
dfu_read_block(bno, &radio_mem[bno*1024], 1024); dfu_read_block(bno, &radio_mem[bno*1024], 1024);
@ -383,6 +384,7 @@ static void md380_download(radio_device_t *radio)
static void md380_upload(radio_device_t *radio, int cont_flag) static void md380_upload(radio_device_t *radio, int cont_flag)
{ {
int bno; int bno;
int radio_progress = 0;
dfu_erase(0, MEMSZ); dfu_erase(0, MEMSZ);

428
radio.c
View File

@ -1,428 +0,0 @@
/*
* Configuration Utility for DMR radios.
*
* Copyright (C) 2018 Serge Vakulenko, KK6ABQ
*
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include "radio.h"
#include "util.h"
static struct {
char *ident;
radio_device_t *device;
} radio_tab[] = {
{ "DR780", &radio_md380 }, // TYT MD-380, Retevis RT3, RT8
{ "MD390", &radio_md390 }, // TYT MD-390
{ "MD-UV380", &radio_uv380 }, // TYT MD-UV380
{ "MD-UV390", &radio_uv390 }, // TYT MD-UV390, Retevis RT3S
{ "2017", &radio_md2017 }, // TYT MD-2017, Retevis RT82
{ "MD9600", &radio_md9600 }, // TYT MD-9600
{ "BF-5R", &radio_rd5r }, // Baofeng RD-5R, TD-5R
{ "1801", &radio_dm1801 }, // Baofeng DM-1801
{ "DM-1701", &radio_rt84 }, // Baofeng DM-1701, Retevis RT84
{ "MD-760P", &radio_gd77 }, // Radioddity GD-77, version 3.1.1 and later
{ "D868UVE", &radio_d868uv }, // Anytone AT-D868UV
{ "D878UV", &radio_d878uv }, // Anytone AT-D878UV
{ "D6X2UV", &radio_dmr6x2 }, // BTECH DMR-6x2
{ "ZD3688", &radio_d900 }, // Zastone D900
{ "TP660", &radio_dp880 }, // Zastone DP880
{ "ZN><:", &radio_rt27d }, // Radtel RT-27D
{ 0, 0 }
};
unsigned char radio_mem [1024*1024*2]; // Radio memory contents, up to 2 Mbytes
int radio_progress; // Read/write progress counter
static radio_device_t *device; // Device-dependent interface
//
// Close the serial port.
//
void radio_disconnect()
{
fprintf(stderr, "Close device.\n");
// Restore the normal radio mode.
dfu_reboot();
dfu_close();
hid_close();
serial_close();
}
//
// Print a generic information about the device.
//
void radio_print_version(FILE *out)
{
device->print_version(device, out);
}
//
// Connect to the radio and identify the type of device.
//
void radio_connect()
{
const char *ident;
int i;
// Try TYT MD family.
ident = dfu_init(0x0483, 0xdf11);
if (! ident) {
// Try RD-5R, DM-1801 and GD-77.
if (hid_init(0x15a2, 0x0073) >= 0)
ident = hid_identify();
}
if (! ident) {
// Try AT-D868UV.
if (serial_init(0x28e9, 0x018a) >= 0)
ident = serial_identify();
}
if (! ident) {
fprintf(stderr, "No radio detected.\n");
fprintf(stderr, "Check your USB cable!\n");
exit(-1);
}
for (i=0; radio_tab[i].ident; i++) {
if (strcasecmp(ident, radio_tab[i].ident) == 0) {
device = radio_tab[i].device;
break;
}
}
if (! device) {
fprintf(stderr, "Unrecognized radio '%s'.\n", ident);
exit(-1);
}
fprintf(stderr, "Connect to %s.\n", device->name);
}
//
// List all supported radios.
//
void radio_list()
{
int i;
printf("Supported radios:\n");
for (i=0; radio_tab[i].ident; i++) {
printf(" %s\n", radio_tab[i].device->name);
}
}
//
// Read firmware image from the device.
//
void radio_download()
{
radio_progress = 0;
if (! trace_flag) {
fprintf(stderr, "Read device: ");
fflush(stderr);
}
device->download(device);
if (! trace_flag)
fprintf(stderr, " done.\n");
}
//
// Write firmware image to the device.
//
void radio_upload(int cont_flag)
{
// Check for compatibility.
if (! device->is_compatible(device)) {
fprintf(stderr, "Incompatible image - cannot upload.\n");
exit(-1);
}
radio_progress = 0;
if (! trace_flag) {
fprintf(stderr, "Write device: ");
fflush(stderr);
}
device->upload(device, cont_flag);
if (! trace_flag)
fprintf(stderr, " done.\n");
}
//
// Read firmware image from the binary file.
//
void radio_read_image(const char *filename)
{
FILE *img;
struct stat st;
char ident[8];
fprintf(stderr, "Read codeplug from file '%s'.\n", filename);
img = fopen(filename, "rb");
if (! img) {
perror(filename);
exit(-1);
}
// Guess device type by file size.
if (stat(filename, &st) < 0) {
perror(filename);
exit(-1);
}
switch (st.st_size) {
case 851968:
case 852533:
device = &radio_uv380;
break;
case 262144:
case 262709:
device = &radio_md380;
break;
case 1606528:
if (fread(ident, 1, 8, img) != 8) {
fprintf(stderr, "%s: Cannot read header.\n", filename);
exit(-1);
}
fseek(img, 0, SEEK_SET);
if (memcmp(ident, "D868UVE", 7) == 0) {
device = &radio_d868uv;
} else if (memcmp(ident, "D878UV", 6) == 0) {
device = &radio_d878uv;
} else if (memcmp(ident, "D6X2UV", 6) == 0) {
device = &radio_dmr6x2;
} else {
fprintf(stderr, "%s: Unrecognized header '%.6s'\n",
filename, ident);
exit(-1);
}
break;
case 131072:
if (fread(ident, 1, 8, img) != 8) {
fprintf(stderr, "%s: Cannot read header.\n", filename);
exit(-1);
}
if (memcmp(ident, "BF-5R", 5) == 0) {
device = &radio_rd5r;
} else if (memcmp(ident, "MD-760P", 7) == 0) {
device = &radio_gd77;
} else if (memcmp(ident, "1801", 4) == 0) {
device = &radio_dm1801;
} else if (memcmp(ident, "MD-760", 6) == 0) {
fprintf(stderr, "Old Radioddity GD-77 v2.6 image not supported!\n");
exit(-1);
} else {
fprintf(stderr, "%s: Unrecognized header '%.6s'\n",
filename, ident);
exit(-1);
}
fseek(img, 0, SEEK_SET);
break;
default:
fprintf(stderr, "%s: Unrecognized file size %u bytes.\n",
filename, (int) st.st_size);
exit(-1);
}
device->read_image(device, img);
fclose(img);
}
//
// Save firmware image to the binary file.
//
void radio_save_image(const char *filename)
{
FILE *img;
fprintf(stderr, "Write codeplug to file '%s'.\n", filename);
img = fopen(filename, "wb");
if (! img) {
perror(filename);
exit(-1);
}
device->save_image(device, img);
fclose(img);
}
//
// Read the configuration from text file, and modify the firmware.
//
void radio_parse_config(const char *filename)
{
FILE *conf;
char line [256], *p, *v;
int table_id = 0, table_dirty = 0;
fprintf(stderr, "Read configuration from file '%s'.\n", filename);
conf = fopen(filename, "r");
if (! conf) {
perror(filename);
exit(-1);
}
device->channel_count = 0;
while (fgets(line, sizeof(line), conf)) {
line[sizeof(line)-1] = 0;
// Strip comments.
v = strchr(line, '#');
if (v)
*v = 0;
// Strip trailing spaces and newline.
v = line + strlen(line) - 1;
while (v >= line && (*v=='\n' || *v=='\r' || *v==' ' || *v=='\t'))
*v-- = 0;
// Ignore comments and empty lines.
p = line;
if (*p == 0)
continue;
if (*p != ' ') {
// Table finished.
table_id = 0;
// Find the value.
v = strchr(p, ':');
if (! v) {
// Table header: get table type.
table_id = device->parse_header(device, p);
if (! table_id) {
badline: fprintf(stderr, "Invalid line: '%s'\n", line);
exit(-1);
}
table_dirty = 0;
continue;
}
// Parameter.
*v++ = 0;
// Skip spaces.
while (*v == ' ' || *v == '\t')
v++;
device->parse_parameter(device, p, v);
} else {
// Table row or comment.
// Skip spaces.
// Ignore comments and empty lines.
while (*p == ' ' || *p == '\t')
p++;
if (*p == '#' || *p == 0)
continue;
if (! table_id) {
goto badline;
}
if (! device->parse_row(device, table_id, ! table_dirty, p)) {
goto badline;
}
table_dirty = 1;
}
}
fclose(conf);
device->update_timestamp(device);
}
//
// Print full information about the device configuration.
//
void radio_print_config(FILE *out, int verbose)
{
if (verbose) {
char buf [40];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (! tmp || ! strftime(buf, sizeof(buf), "%Y/%m/%d ", tmp))
buf[0] = 0;
fprintf(out, "#\n");
fprintf(out, "# Configuration generated %sby dmrconfig, version %s\n",
buf, version);
fprintf(out, "#\n");
}
device->print_config(device, out, verbose);
}
//
// Check the configuration is correct.
//
void radio_verify_config()
{
if (!device->verify_config(device)) {
// Message should be already printed.
exit(-1);
}
}
//
// Update contacts database on the device.
//
void radio_write_csv(const char *filename)
{
FILE *csv;
if (!device->write_csv) {
fprintf(stderr, "%s does not support CSV database.\n", device->name);
return;
}
csv = fopen(filename, "r");
if (! csv) {
perror(filename);
return;
}
fprintf(stderr, "Read file '%s'.\n", filename);
device->write_csv(device, csv);
fclose(csv);
}
//
// Check for compatible radio model.
//
int radio_is_compatible(const char *name)
{
int i;
for (i=0; radio_tab[i].ident; i++) {
// Radio is compatible when it has the same parse routine.
if (device->parse_parameter == radio_tab[i].device->parse_parameter &&
strcasecmp(name, radio_tab[i].device->name) == 0) {
return 1;
}
}
return 0;
}

68
radio.h
View File

@ -26,70 +26,11 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
//
// Connect to the radio via the serial port.
// Identify the type of device.
//
void radio_connect(void);
//
// Close the serial port.
//
void radio_disconnect(void);
//
// Read firmware image from the device.
//
void radio_download(void);
//
// Write firmware image to the device.
//
void radio_upload(int cont_flag);
//
// Print a generic information about the device.
//
void radio_print_version(FILE *out);
//
// Print full information about the device configuration.
//
void radio_print_config(FILE *out, int verbose);
//
// Read firmware image from the binary file.
//
void radio_read_image(const char *filename);
//
// Save firmware image to the binary file.
//
void radio_save_image(const char *filename);
//
// Read the configuration from text file, and modify the firmware.
//
void radio_parse_config(const char *filename);
//
// Check the configuration.
//
void radio_verify_config(void);
//
// Update CSV contacts database.
//
void radio_write_csv(const char *filename);
//
// List all supported radios.
//
void radio_list(void);
// //
// Check for compatible radio model. // Check for compatible radio model.
// //
#include <stdio.h>
int radio_is_compatible(const char *ident); int radio_is_compatible(const char *ident);
// //
@ -140,8 +81,3 @@ extern unsigned char radio_mem[];
// File descriptor of serial port with programming cable attached. // File descriptor of serial port with programming cable attached.
// //
extern int radio_port; extern int radio_port;
//
// Read/write progress counter.
//
extern int radio_progress;

2
rd5r.c
View File

@ -345,6 +345,7 @@ static void rd5r_print_version(radio_device_t *radio, FILE *out)
static void download(radio_device_t *radio) static void download(radio_device_t *radio)
{ {
int bno; int bno;
int radio_progress = 0;
// Read range 0x80...0x1e29f. // Read range 0x80...0x1e29f.
for (bno=1; bno<966; bno++) { for (bno=1; bno<966; bno++) {
@ -385,6 +386,7 @@ static void rd5r_download(radio_device_t *radio)
static void rd5r_upload(radio_device_t *radio, int cont_flag) static void rd5r_upload(radio_device_t *radio, int cont_flag)
{ {
int bno; int bno;
int radio_progress = 0;
// Write range 0x80...0x1e29f. // Write range 0x80...0x1e29f.
for (bno=1; bno<966; bno++) { for (bno=1; bno<966; bno++) {

View File

@ -55,7 +55,7 @@
# include <IOKit/serial/IOSerialKeys.h> # include <IOKit/serial/IOSerialKeys.h>
#endif #endif
static char *dev_path; //static char *dev_path;
static const unsigned char CMD_PRG[] = "PROGRAM"; static const unsigned char CMD_PRG[] = "PROGRAM";
static const unsigned char CMD_PRG2[] = "\2"; static const unsigned char CMD_PRG2[] = "\2";
@ -357,7 +357,7 @@ int serial_open(const char *devname, int baud_rate)
// //
// Find a device path by vid/pid. // Find a device path by vid/pid.
// //
static char *find_path(int vid, int pid) char *find_path(int vid, int pid)
{ {
char *result = 0; char *result = 0;
@ -576,21 +576,6 @@ static char *find_path(int vid, int pid)
// Connect to the specified device. // Connect to the specified device.
// Initiate the programming session. // Initiate the programming session.
// //
int serial_init(int vid, int pid)
{
dev_path = find_path(vid, pid);
if (!dev_path) {
if (trace_flag) {
fprintf(stderr, "Cannot find USB device %04x:%04x\n",
vid, pid);
}
return -1;
}
// Succeeded.
printf("Serial port: %s\n", dev_path);
return 0;
}
// //
// Send the command sequence and get back a response. // Send the command sequence and get back a response.
@ -612,7 +597,8 @@ static int send_recv(const unsigned char *cmd, int cmdlen,
} }
if (serial_write(cmd, cmdlen) < 0) { if (serial_write(cmd, cmdlen) < 0) {
fprintf(stderr, "%s: write error\n", dev_path); //fprintf(stderr, "%s: write error\n", dev_path);
fprintf(stderr, "serial write error\n");
exit(-1); exit(-1);
} }
@ -671,7 +657,7 @@ void serial_close()
// Query and return the device identification string. // Query and return the device identification string.
// On error, return NULL. // On error, return NULL.
// //
const char *serial_identify() const char *serial_identify(char* dev_path)
{ {
static unsigned char reply[16]; static unsigned char reply[16];
unsigned char ack[3]; unsigned char ack[3];
@ -717,7 +703,7 @@ again:
return (char*)&reply[1]; return (char*)&reply[1];
} }
void serial_read_region(int addr, unsigned char *data, int nbytes) void c_serial_read_region(int addr, unsigned char *data, int nbytes)
{ {
static const int DATASZ = 64; static const int DATASZ = 64;
unsigned char cmd[6], reply[8 + DATASZ]; unsigned char cmd[6], reply[8 + DATASZ];
@ -755,7 +741,7 @@ again:
} }
} }
void serial_write_region(int addr, unsigned char *data, int nbytes) void c_serial_write_region(int addr, unsigned char *data, int nbytes)
{ {
//static const int DATASZ = 64; //static const int DATASZ = 64;
static const int DATASZ = 16; static const int DATASZ = 16;

189
src/lib.rs Normal file
View File

@ -0,0 +1,189 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use libc::{c_int, c_char};
use getopts::Options;
use std::process::exit;
mod serial;
mod radio;
const COPYRIGHT: &'static str = "Copyright (C) 2018 Serge Vakulenko KK6ABQ";
const VERSION: Option<&'static str> = option_env!("VERSION");
fn version() -> &'static str {
VERSION.unwrap_or("-------")
}
fn print_usage() {
let version = version();
print!("DMR Config, Version {}, {}", version, COPYRIGHT);
let msg = r#"
Usage:
dmrconfig -r [-t]
Read codeplug from the radio to a file 'device.img'.
Save configuration to a text file 'device.conf'.
dmrconfig -w [-t] file.img
Write codeplug to the radio.
dmrconfig -v [-t] file.conf
Verify configuration script for the radio.
dmrconfig -c [-t] file.conf
Apply configuration script to the radio.
dmrconfig -c file.img file.conf
Apply configuration script to the codeplug image.
Store modified copy to a file 'device.img'.
dmrconfig file.img
Display configuration from the codeplug image.
dmrconfig -u [-t] file.csv
Update contacts database from CSV file.
Options:
-r Read codeplug from the radio.
-w Write codeplug to the radio.
-c Configure the radio from a text script.
-v Verify config file.
-u Update contacts database.
-l List all supported radios.
-t Trace USB protocol."#;
print!("{}", msg);
exit(-1);
}
fn get_options() -> Options {
let mut opts = Options::new();
opts.optflag("t", "", "Trace USB protocol.");
opts.optflag("r", "", "Read codeplug from the radio to a file 'device.img'.\nSave configuration to a text file 'device.conf'.");
opts.optflag("w", "", "Write codeplug to the radio.");
opts.optflag("c", "", "Verify configuration script for the radio.");
opts.optflag("u", "", "Update contacts database from CSV file.");
opts.optflag("l", "", "List all supported radios.");
opts.optflag("v", "", "Verify configuration script for the radio.");
opts
}
#[no_mangle]
pub extern "C" fn rust_main(_argc: c_int, _argv: *const *const c_char) -> c_int {
let args = std::env::args().skip(1);
let matches = match get_options().parse(args) {
Ok(m) => m,
Err(fail) => {
eprintln!("{}", fail);
exit(-1);
}
};
let trace_flag = matches.opt_present("t");
let list_flag = matches.opt_present("l");
let verify_flag = matches.opt_present("v");
let read_flag = matches.opt_present("r");
let write_flag = matches.opt_present("w");
let config_flag = matches.opt_present("c");
let csv_flag = matches.opt_present("u");
if list_flag {
radio::list();
exit(0);
}
if [read_flag, write_flag, config_flag, csv_flag, verify_flag].iter().filter(|x| **x).count() > 1 {
eprintln!("Only one of -r, -w, -c, -v or -u options is allowed.");
print_usage();
}
if write_flag {
if matches.free.len() != 1 {
print_usage();
}
let device = radio::connect();
radio::read_image(&matches.free[0]);
radio::print_version(&device);
radio::upload(&device, 0, trace_flag);
radio::disconnect();
} else if config_flag {
let conf_args = matches.free.len();
if !(conf_args == 1 || conf_args == 2) {
print_usage();
}
let (config_filename, image_filename) = if conf_args == 2 {
(matches.free[1].clone(), Some(matches.free[0].clone()))
} else {
(matches.free[0].clone(), None)
};
if let Some(img) = image_filename {
// Apply text config to image file.
let device = radio::connect(); //NOTE this changes the semantics of the program
radio::read_image(&img);
radio::print_version(&device);
radio::parse_config(&device, &config_filename);
radio::verify_config(&device);
radio::save_image(&device, "device.img");
} else {
// Update device from text config file.
let device = radio::connect();
radio::download(&device, trace_flag);
radio::print_version(&device);
radio::save_image(&device, "device.img");
radio::parse_config(&device, &config_filename);
radio::verify_config(&device);
radio::upload(&device, 1, trace_flag);
radio::disconnect();
}
} else if verify_flag {
if matches.free.len() != 1 {
print_usage();
}
// Verify text config file.
let device = radio::connect();
radio::parse_config(&device, &matches.free[0]);
radio::verify_config(&device);
radio::disconnect();
} else if read_flag {
if matches.free.len() != 0 {
print_usage();
}
// Dump device to image file.
let device = radio::connect();
radio::download(&device, trace_flag);
radio::print_version(&device);
radio::disconnect();
radio::save_image(&device, "device.img");
// Print configuration to file.
let filename = "device.conf";
println!("Print configuration to file '{}.", filename);
radio::print_config(&device, Some(filename));
} else if csv_flag {
if matches.free.len() != 1 {
print_usage();
}
let device = radio::connect();
radio::write_csv(&device, &matches.free[0]);
radio::disconnect();
} else {
if matches.free.len() != 1 {
print_usage();
}
let device = radio::read_image(&matches.free[0]);
radio::print_config(&device, None);
}
exit(0);
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

512
src/radio.rs Normal file
View File

@ -0,0 +1,512 @@
use std::ffi::{CStr, CString};
use libc::{c_char, c_int, c_uint};
use std::os::unix::io::AsRawFd;
use std::process::exit;
use libc::FILE;
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
pub struct Radio {
ptr: *const radio_device_t
}
static mut RADIO_TABLE: [(&'static str, &'static radio_device_t); 16] = unsafe {
[
("DR780", &radio_md380), // TYT MD-380, Retevis RT3, RT8
("MD390", &radio_md390), // TYT MD-390
("MD-UV380", &radio_uv380), // TYT MD-UV380
("MD-UV390", &radio_uv390), // TYT MD-UV390, Retevis RT3S
("2017", &radio_md2017), // TYT MD-2017, Retevis RT82
("MD9600", &radio_md9600), // TYT MD-9600
("BF-5R", &radio_rd5r), // Baofeng RD-5R, TD-5R
("1801", &radio_dm1801), // Baofeng DM-1801
("DM-1701", &radio_rt84), // Baofeng DM-1701, Retevis RT84
("MD-760P", &radio_gd77), // Radioddity GD-77, version 3.1.1 and later
("D868UVE", &radio_d868uv), // Anytone AT-D868UV
("D878UV", &radio_d878uv), // Anytone AT-D878UV
("D6X2UV", &radio_dmr6x2), // BTECH DMR-6x2
("ZD3688", &radio_d900), // Zastone D900
("TP660", &radio_dp880), // Zastone DP880
("ZN><:", &radio_rt27d), // Radtel RT-27D
]
};
// Radio memory contents, up to 2 Mbytes
#[no_mangle]
pub static mut radio_mem: [u8; 1024*1024*2] = [0; 1024*1024*2];
extern {
fn dfu_init(vid: c_uint, pid: c_uint) -> *const c_char;
fn dfu_reboot();
fn dfu_close();
fn hid_init(vid: c_int, pid: c_int) -> c_int;
fn hid_identify() -> *const c_char;
fn hid_close();
}
static mut active_device: *const radio_device_t = std::ptr::null(); // Device-dependent interface
unsafe fn get_active_device() -> *const radio_device_t {
return active_device;
}
unsafe fn set_active_device(d: *const radio_device_t) {
active_device = d;
}
/// Connect to the radio via the serial port.
/// and identify the type of device.
pub fn connect() -> Radio {
let ident_str = (|| {
let mut ident: *const c_char;
// Try TYT MD family.
ident = unsafe { dfu_init(0x0483, 0xdf11) };
if ident.is_null() {
// Try RD-5R, DM-1801 and GD-77.
if unsafe { hid_init(0x15a2, 0x0073) } >= 0 {
ident = unsafe { hid_identify() };
}
}
if ident.is_null() {
// Try AT-D868UV.
let trace_flag = false; //TODO fix
if let Some(device_path) = crate::serial::serial_init(0x28e9, 0x018a, trace_flag) {
if let Some(identifier) = crate::serial::identify(&device_path) {
unsafe {
crate::serial::device_path = Some(device_path);
}
return identifier;
}
}
}
if ident.is_null() {
eprintln!("No radio detected.");
eprintln!("Check your USB cable!");
exit(-1);
}
unsafe { CStr::from_ptr(ident).to_str().unwrap().to_string() }
})();
unsafe {
let mut device: *const radio_device_t = std::ptr::null();
for (table_ident, device_ptr) in RADIO_TABLE.iter() {
if ident_str == *table_ident {
device = *device_ptr as *const radio_device_t;
break;
}
}
if device.is_null() {
eprintln!("Unrecognized radio '{}'.", ident_str);
exit(-1);
}
let name_ptr = (*device).name;
let name = CStr::from_ptr(name_ptr).to_str().unwrap();
println!("Connect to {}", name);
set_active_device(device);
return Radio { ptr: device as *mut radio_device_t };
}
}
/// Close the serial port.
pub fn disconnect() {
eprintln!("Close device.");
unsafe {
// Restore the normal radio mode.
dfu_reboot();
dfu_close();
hid_close();
crate::serial::close();
}
}
/// Read firmware image from the device
pub fn download(radio: &Radio, trace: bool) {
let device = radio.ptr as *mut radio_device_t;
if !trace {
eprint!("Read device: ");
}
unsafe {
let download_fn = (*device).download.unwrap();
download_fn(device);
}
if !trace {
eprintln!(" done.");
}
}
/// Write firmware image to the device.
pub fn upload(radio: &Radio, cont_flag: c_int, trace: bool) {
let device = radio.ptr as *mut radio_device_t;
unsafe {
let is_compatible_fn = (*device).is_compatible.unwrap();
if is_compatible_fn(device) == 0 {
eprintln!("Incompatible image - cannot upload.");
exit(-1);
}
if !trace {
eprint!("Write device: ");
}
let upload_fn = (*device).upload.unwrap();
upload_fn(device, cont_flag);
if !trace {
eprintln!(" done.");
}
}
}
/// List all supported radios.
pub fn list() {
println!("Supported radios:");
unsafe {
for (_, device) in RADIO_TABLE.iter() {
let name_ptr = (*device).name;
let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_string();
println!(" {}", name);
}
}
}
/// Check that the configuration is correct.
pub fn verify_config(radio: &Radio) {
let device = radio.ptr as *mut radio_device_t;
unsafe {
let verify_fn = (*device).verify_config.unwrap();
if verify_fn(device) == 0 {
// Message should be already printed.
exit(-1);
}
}
}
/// Read firmware image from the binary file.
pub fn read_image(filename: &str) -> Radio {
use std::io::{Seek, SeekFrom, Read};
fn read_header(file: &mut std::fs::File, filename: &str) -> Vec<u8> {
let mut header_buf: [u8; 7] = [0; 7];
match file.read(&mut header_buf) {
Ok(7) => (),
Ok(_) => {
eprintln!("{}: Cannot read header.", filename);
exit(-1);
},
Err(e) => {
eprintln!("{}: Cannot read header. Error: {}", filename, e);
exit(-1);
}
};
file.seek(SeekFrom::Start(0)).unwrap();
header_buf.to_vec()
}
eprintln!("Read codeplug from file '{}'.", filename);
let mut img_file = std::fs::File::open(filename).unwrap();
let metadata = std::fs::metadata(filename).unwrap();
let file_size = metadata.len();
let device = unsafe {match file_size {
851968 | 852533 => {
&radio_uv380
}
262144 | 262709 => {
&radio_md380
}
1606528 => {
let header = read_header(&mut img_file, filename);
if header.as_slice().starts_with(b"D868UVE") {
&radio_d868uv
} else if header.as_slice().starts_with(b"D878UV") {
&radio_d878uv
} else if header.as_slice().starts_with(b"D6X2UV") {
&radio_dmr6x2
} else {
match std::str::from_utf8(&header) {
Ok(s) => eprintln!("Unrecognized header: {}", s),
Err(_) => eprintln!("Unrecognized header: {:?}", header),
};
exit(-1)
}
}
131072 => {
let header = read_header(&mut img_file, filename);
if header.as_slice().starts_with(b"BF-5R") {
&radio_rd5r
} else if header.as_slice().starts_with(b"MD-760P") {
&radio_gd77
} else if header.as_slice().starts_with(b"1801") {
&radio_dm1801
} else if header.as_slice().starts_with(b"MD-760") {
eprintln!("Old Radioddity GD-77 v2.6 image not supported!");
exit(-1)
} else {
match std::str::from_utf8(&header) {
Ok(s) => eprintln!("Unrecognized header: {}", s),
Err(_) => eprintln!("Unrecognized header: {:?}", header),
};
exit(-1)
}
}
size => {
eprintln!("{}: Unrecognized file size {} bytes.", filename, size);
exit(-1)
}
} };
let fd = img_file.as_raw_fd();
let mode = CString::new("rb").unwrap();
unsafe {
let device = device as *const radio_device_t;
let device = device as *mut radio_device_t;
let file = libc::fdopen(fd, mode.as_ptr());
let read_image_fn = (*device).read_image.unwrap();
read_image_fn(device, file);
libc::fclose(file);
set_active_device(device);
Radio { ptr: device }
}
}
/// Save firmware image to the binary file.
pub fn save_image(radio: &Radio, filename: &str) {
eprintln!("Write codeplug to file '{}'.", filename);
let device = radio.ptr as *mut radio_device_t;
let file = std::fs::File::create(filename).unwrap();
let fd = file.as_raw_fd();
let mode = CString::new("wb").unwrap();
unsafe {
let file = libc::fdopen(fd, mode.as_ptr());
let save_image_fn = (*device).save_image.unwrap();
save_image_fn(device, file);
libc::fclose(file);
}
}
/// Read the configuration from a text file, and modify the firmware.
pub fn parse_config(radio: &Radio, filename: &str) {
use std::io::{BufRead, BufReader};
let device = radio.ptr as *mut radio_device_t;
let parse_header_fn = unsafe {
(*device).parse_header.unwrap()
};
let parse_parameter_fn = unsafe {
(*device).parse_parameter.unwrap()
};
let parse_row_fn = unsafe {
(*device).parse_row.unwrap()
};
let update_timestamp_fn = unsafe {
(*device).update_timestamp.unwrap()
};
eprintln!("Read configuration from file '{}'.", filename);
let file = std::fs::File::open(filename).unwrap();
let file = BufReader::new(file);
let mut table_id: c_int = 0;
let mut table_dirty: c_int = 0;
for line in file.lines() {
let line = line.unwrap();
// Strip text after comment marker '#'
let trimmed_line = line
.split('#')
.next()
.unwrap()
.trim_end();
// Skip comments and blank lines
if trimmed_line.is_empty() {
continue;
}
// Table row
if trimmed_line.chars().nth(0) == Some(' ') {
if table_id == 0 {
eprintln!("Invalid line: '{}'", line);
exit(-1);
}
let trimmed_more = CString::new(trimmed_line.trim_start()).unwrap();
let ptr = trimmed_more.as_ptr() as *mut c_char;
let output = unsafe { parse_row_fn(device, table_id, !table_dirty, ptr) };
if output == 0 {
eprintln!("Invalid line: '{}'", line);
exit(-1);
}
table_dirty = 1;
} else {
// Table finished
table_id = 0;
// Find the value
if let Some(colon_byte_idx) = line.find(':') {
let (param_str, value_str) = line.split_at(colon_byte_idx);
let stripped_value_str = value_str.strip_prefix(':').unwrap();
let param_c_str = CString::new(param_str.trim_start()).unwrap();
let param_ptr = param_c_str.as_ptr() as *mut c_char;
let value_c_str = CString::new(stripped_value_str.trim_start()).unwrap();
let value_ptr = value_c_str.as_ptr() as *mut c_char;
unsafe {
parse_parameter_fn(device, param_ptr, value_ptr);
}
} else { // Table header
let c_line = CString::new(trimmed_line).unwrap();
let ptr = c_line.as_ptr() as *mut c_char;
table_id = unsafe { parse_header_fn(device, ptr) };
if table_id == 0 {
eprintln!("Invalid line: '{}'", line);
exit(-1);
}
table_dirty = 0;
}
}
}
unsafe {
update_timestamp_fn(device);
}
}
/// Print full information about the device configuration.
/// If `filename` is `None`, write to stdout.
pub fn print_config(radio: &Radio, filename: Option<&str>) {
use std::io::Write;
fn make_config_string() -> String {
use chrono::Datelike;
let date = chrono::offset::Local::today();
let y = date.year(); let m = date.month(); let d = date.day();
format!("#\n# Configuration generated {}/{}/{} by dmrconfig, version {}\n#\n", y, m, d, crate::version())
}
let mut file;
let (fd, verbose) = match filename {
None => {
unsafe {
let verbose = libc::isatty(libc::STDOUT_FILENO) != 1;
if verbose {
print!("{}", make_config_string());
}
(libc::STDOUT_FILENO, verbose)
}
},
Some(filename) => {
file = std::fs::File::create(filename).unwrap();
file.write_all(make_config_string().as_bytes()).unwrap();
(file.as_raw_fd(), true)
}
};
let mode = CString::new("w").unwrap();
unsafe {
let device = radio.ptr as *mut radio_device_t;
let print_config_fn = (*device).print_config.unwrap();
let file = libc::fdopen(fd, mode.as_ptr());
print_config_fn(device, file, if verbose { 1 } else { 0 } );
libc::fclose(file);
}
}
/// Print generic information about the device.
pub fn print_version(radio: &Radio) {
let device = radio.ptr as *mut radio_device_t;
let mode = CString::new("w").unwrap();
unsafe {
let print_version_fn = (*device).print_version.unwrap();
print_version_fn(device, libc::fdopen(libc::STDOUT_FILENO, mode.as_ptr()));
}
}
/// Update contacts database on the device.
pub fn write_csv(radio: &Radio, filename: &str) {
let device = radio.ptr as *mut radio_device_t;
unsafe {
let write_csv_fn = match (*device).write_csv {
Some(func) => func,
None => {
let name_ptr = (*device).name;
let name = CStr::from_ptr(name_ptr).to_str().unwrap();
eprintln!("{} does not support CSV database.", name);
exit(-1);
}
};
let file = std::fs::File::open(filename).unwrap();
eprintln!("Read file '{}.", filename);
let fd = file.as_raw_fd();
let mode = CString::new("r").unwrap();
let file = libc::fdopen(fd, mode.as_ptr());
write_csv_fn(device, file);
libc::fclose(file);
}
}
/// Check for compatible radio model.
#[no_mangle]
pub extern "C" fn radio_is_compatible(name: *const c_char) -> c_int {
unsafe {
let name = CStr::from_ptr(name).to_str().unwrap();
let dev = get_active_device();
for (_, device) in RADIO_TABLE.iter() {
// Radio is compatible when it has the same parse routine.
let same_parse = (*dev).parse_parameter.unwrap() == (*device).parse_parameter.unwrap();
let name_ptr = (*device).name;
let device_name = CStr::from_ptr(name_ptr).to_str().unwrap();
let same_name = name.eq_ignore_ascii_case(device_name);
if same_parse && same_name {
return 1;
}
}
}
return 0;
}

259
src/serial.rs Normal file
View File

@ -0,0 +1,259 @@
use std::ffi::{CStr};
use libc::{c_char, c_int, c_uchar, c_void};
use std::process::exit;
use std::time::Duration;
//hack to handle the serial device path state
pub static mut device_path: Option<String> = None;
extern {
fn find_path(vid: libc::c_int, pid: libc::c_int) -> *const c_char;
fn serial_identify(s: *const c_char) -> *const c_char;
fn serial_close();
}
/// Connect to the specified device.
/// Initiate the programming session.
pub fn serial_init(vid: u32, pid: u32, trace_flag: bool) -> Option<String> {
let dev_path = unsafe { find_path(vid as i32, pid as i32) };
if dev_path.is_null() {
if trace_flag {
eprintln!("Cannot find USB device: {:#x}{:#x}", vid, pid);
}
return None;
}
let dev_path = unsafe { CStr::from_ptr(dev_path).to_str().unwrap().to_string() };
println!("Serial port: {}", dev_path);
Some(dev_path)
}
fn send_receive(port: &mut dyn serial::SerialPort, command: &[u8], reply_len: usize, trace: bool) -> Vec<u8> {
if trace {
eprintln!("----Send [{}] {:?}", command.len(), command);
}
match port.write(&command) {
Ok(n) => {
if trace {
eprintln!("Wrote {} bytes over serial", n);
}
},
Err(e) => {
eprintln!("Serial write error: {}", e);
exit(-1);
}
};
let mut buf: Vec<u8> = vec![0; reply_len];
match port.read(&mut buf) {
Ok(n) if n == reply_len => {
if trace {
eprintln!("----Received [{}] {:?}", reply_len, buf);
}
},
Ok(n) => {
eprintln!("Serial: read {} bytes, expected {}", n, reply_len);
}
Err(e) => {
eprintln!("Serial read error: {}", e);
exit(-1);
}
};
buf
}
#[no_mangle]
pub extern "C" fn serial_read_region(addr: c_int, data: *mut c_uchar, nbytes: c_int) {
use serial::prelude::*;
let dev_path = unsafe {
match device_path {
Some(ref s) => s.clone(),
None => {
eprintln!("No serial device path set, exiting");
exit(-1);
}
}
};
let mut port = match serial::open(&dev_path) {
Ok(port) => port,
Err(err) => {
println!("{}", err);
exit(-1);
}
};
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
settings.set_char_size(serial::CharSize::Bits8);
Ok(())
}).unwrap();
port.set_timeout(Duration::new(1,0)).unwrap();
const DATA_SIZE: usize = 64;
let num_bytes = nbytes as usize;
let addr = addr as usize;
let mut output_data = vec![];
let mut n = 0;
while n < num_bytes {
// Serial read command: 'R' aa aa aa aa 0x10
let cmd: [u8; 6] = [
0x52, // ASCII 'R'
((addr + n) >> 24) as u8,
((addr + n) >> 16) as u8,
((addr + n) >> 8) as u8,
(addr + n) as u8,
64,
];
let reply_len = 8 + DATA_SIZE;
eprintln!("Here in serial_read_region");
let reply = send_receive(&mut port, &cmd, reply_len, true);
if reply[0] != b'W' || reply[7+DATA_SIZE] != 0x6 {
eprintln!("serial_read_region: wrong read reply: {:?} shit: {}", reply, reply[7+DATA_SIZE]);
exit(-1);
}
//Compute checksum
let mut checksum: u8 = reply[1];
for idx in 2..6+DATA_SIZE {
checksum = checksum.overflowing_add(reply[idx]).0;
}
let expected_checksum = reply[6+DATA_SIZE];
if checksum != expected_checksum {
eprintln!("serial_read_region: wrong read checksum {}, expected {}", checksum, expected_checksum);
exit(-1);
}
output_data.extend(reply.into_iter());
n += DATA_SIZE;
}
let n = output_data.len();
let ptr = output_data.as_ptr() as *const c_void;
unsafe {
libc::memcpy(data as *mut c_void, ptr, n);
}
}
#[no_mangle]
pub extern "C" fn serial_write_region(addr: c_int, data: *mut c_uchar, nbytes: c_int) {
use serial::prelude::*;
let dev_path = unsafe {
match device_path {
Some(ref s) => s.clone(),
None => {
eprintln!("No serial device path set, exiting");
exit(-1);
}
}
};
let mut port = match serial::open(&dev_path) {
Ok(port) => port,
Err(err) => {
println!("{}", err);
exit(-1);
}
};
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
settings.set_char_size(serial::CharSize::Bits8);
Ok(())
}).unwrap();
port.set_timeout(Duration::new(1,0)).unwrap();
const DATA_SIZE: usize = 16;
let num_bytes = nbytes as usize;
let addr = addr as usize;
let mut n = 0;
while n < num_bytes {
let mut cmd: [u8; 8 + DATA_SIZE] = [0; 8 + DATA_SIZE];
cmd[0] = 0x57; // ASCII 'W'
cmd[1] = ((addr + n) >> 24) as u8;
cmd[2] = ((addr + n) >> 16) as u8;
cmd[3] = ((addr + n) >> 8) as u8;
cmd[4] = (addr + n) as u8;
cmd[5] = DATA_SIZE as u8;
let ptr = unsafe {
cmd.as_mut_ptr().offset(6) as *mut c_void
};
unsafe {
libc::memcpy(ptr, data.offset(n as isize) as *const c_void, DATA_SIZE);
}
//Compute checksum
let mut checksum: u8 = cmd[1];
for idx in 2..(6+DATA_SIZE) {
checksum = checksum.overflowing_add(cmd[idx]).0;
}
cmd[DATA_SIZE+6] = checksum;
cmd[DATA_SIZE+7] = b'\x06'; // CMD_ACK byte
let reply = send_receive(&mut port, &cmd, 1, true);
if reply[0] != b'\x06' {
eprintln!("serial_write_region: wrong acknowledge {:?}, expected: {:?}", reply, b'\x06');
exit(-1);
}
n += DATA_SIZE as usize;
}
}
/// Query and return the device identification string.
/// On error, return None.
pub fn identify(dev_path: &str) -> Option<String> {
use serial::prelude::*;
let mut port = match serial::open(dev_path) {
Ok(port) => port,
Err(err) => {
println!("{}", err);
exit(-1);
}
};
port.reconfigure(&|settings: &mut dyn SerialPortSettings| {
settings.set_baud_rate(serial::BaudRate::Baud115200)?;
settings.set_char_size(serial::CharSize::Bits8);
Ok(())
}).unwrap();
port.set_timeout(Duration::new(1,0)).unwrap();
let program_cmd = b"PROGRAM";
let program_cmd_2 = b"\x02";
let program_ack = b"QX\x06";
let command_ack: u8 = b'\x06';
let output = send_receive(&mut port, program_cmd, 3, true);
if output != program_ack {
eprintln!("serial identify: wrong PRG acknowledge {:?}, expected {:?}", output, program_ack);
}
// Expected output should be something like:
// 49 44 38 36 38 55 56 45 00 56 31 30 32 00 00 06
// I D 8 6 8 U V E V 1 0 2
let output = send_receive(&mut port, program_cmd_2, 16, true);
if (output[0] != 'I' as u8) || (output[15] != command_ack) {
eprintln!("serial identify: wrong PRG2 reply {:?} expected 'I'...'\\6'", output);
}
return Some(String::from_utf8(output[1..7].to_vec()).unwrap())
}
/// Close the serial port.
pub fn close() {
unsafe {
serial_close();
}
}

5
util.h
View File

@ -93,11 +93,12 @@ void hid_write_finish(void);
// //
// Serial functions. // Serial functions.
// //
int serial_init(int vid, int pid); //int serial_init(int vid, int pid);
const char *serial_identify(void); //const char *serial_identify(void);
void serial_close(void); void serial_close(void);
void serial_read_region(int addr, unsigned char *data, int nbytes); void serial_read_region(int addr, unsigned char *data, int nbytes);
void serial_write_region(int addr, unsigned char *data, int nbytes); void serial_write_region(int addr, unsigned char *data, int nbytes);
char *find_path(int vid, int pid);
// //
// Delay in milliseconds. // Delay in milliseconds.

View File

@ -400,6 +400,7 @@ static void uv380_print_version(radio_device_t *radio, FILE *out)
static void uv380_download(radio_device_t *radio) static void uv380_download(radio_device_t *radio)
{ {
int bno; int bno;
int radio_progress = 0;
for (bno=0; bno<MEMSZ/1024; bno++) { for (bno=0; bno<MEMSZ/1024; bno++) {
dfu_read_block(bno, &radio_mem[bno*1024], 1024); dfu_read_block(bno, &radio_mem[bno*1024], 1024);
@ -418,6 +419,7 @@ static void uv380_download(radio_device_t *radio)
static void uv380_upload(radio_device_t *radio, int cont_flag) static void uv380_upload(radio_device_t *radio, int cont_flag)
{ {
int bno; int bno;
int radio_progress = 0;
dfu_erase(0, MEMSZ); dfu_erase(0, MEMSZ);
@ -2490,7 +2492,7 @@ static void uv380_write_csv(radio_device_t *radio, FILE *csv)
// Erase whole region. // Erase whole region.
// Align finish to 64kbytes. // Align finish to 64kbytes.
// //
radio_progress = 0; int radio_progress = 0;
if (! trace_flag) { if (! trace_flag) {
fprintf(stderr, "Erase: "); fprintf(stderr, "Erase: ");
fflush(stderr); fflush(stderr);