From 20cb29c2f8c5c87bc590896854a20b1473ceb358 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Sat, 8 Aug 2026 03:54:55 +0000 Subject: #2 --- plugins/baycom/CMakeLists.txt | 36 +++ plugins/baycom/baycom.c | 623 +++++++++++++++++++++++++++++++++++++++++ plugins/baycom/baycom_config.c | 395 ++++++++++++++++++++++++++ plugins/baycom/baycom_kernel.c | 472 +++++++++++++++++++++++++++++++ plugins/baycom/baycom_kernel.h | 26 ++ plugins/baycom/baycom_kiss.c | 262 +++++++++++++++++ plugins/baycom/baycom_kiss.h | 26 ++ plugins/baycom/baycom_modem.c | 94 +++++++ plugins/baycom/baycom_modem.h | 31 ++ 9 files changed, 1965 insertions(+) create mode 100644 plugins/baycom/CMakeLists.txt create mode 100644 plugins/baycom/baycom.c create mode 100644 plugins/baycom/baycom_config.c create mode 100644 plugins/baycom/baycom_kernel.c create mode 100644 plugins/baycom/baycom_kernel.h create mode 100644 plugins/baycom/baycom_kiss.c create mode 100644 plugins/baycom/baycom_kiss.h create mode 100644 plugins/baycom/baycom_modem.c create mode 100644 plugins/baycom/baycom_modem.h (limited to 'plugins/baycom') diff --git a/plugins/baycom/CMakeLists.txt b/plugins/baycom/CMakeLists.txt new file mode 100644 index 0000000..8a2dc2d --- /dev/null +++ b/plugins/baycom/CMakeLists.txt @@ -0,0 +1,36 @@ +add_library(hybbx_plugin_baycom STATIC + baycom.c + baycom_config.c + baycom_modem.c + baycom_kernel.c + baycom_kiss.c + ../packet_radio/ax25.c + ../packet_radio/kiss.c + ../packet_radio/serial_port.c +) + +target_include_directories(hybbx_plugin_baycom + PRIVATE + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/plugins/packet_radio + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_compile_definitions(hybbx_plugin_baycom + PRIVATE HYBBX_PLUGIN_BUILD +) + +find_package(Threads REQUIRED) +target_link_libraries(hybbx_plugin_baycom PRIVATE hybbx_core Threads::Threads) +if(HYBBX_PLATFORM_AMIGAOS) + target_link_libraries(hybbx_plugin_baycom PRIVATE amiga) +endif() +hybbx_link_plugin_instances(hybbx_plugin_baycom HYBBX_HAVE_PLUGIN_BAYCOM) +hybbx_apply_hardening(hybbx_plugin_baycom) + +if(HYBBX_INSTALL_DEV) +install(TARGETS hybbx_plugin_baycom + ARCHIVE DESTINATION ${HYBBX_PLUGIN_INSTALL_DIR} + LIBRARY DESTINATION ${HYBBX_PLUGIN_INSTALL_DIR} +) +endif() diff --git a/plugins/baycom/baycom.c b/plugins/baycom/baycom.c new file mode 100644 index 0000000..e2fb5c9 --- /dev/null +++ b/plugins/baycom/baycom.c @@ -0,0 +1,623 @@ +/* + * baycom — BayCom PR-Stack link adapter (Linux kernel SER12/PAR96/EPP or KISS serial). + * INI: [transport.baycom] or [transport.baycomN]. See docs/BAYCOM.md. + */ +#include "hybbx/plugin.h" +#include "hybbx/service.h" +#include "hybbx/baycom.h" +#include "hybbx/circuit.h" +#include "hybbx/circuit_tcp.h" +#include "hybbx/circuit_balance.h" +#include "hybbx/limits.h" +#include "hybbx/max25.h" +#include "hybbx/util.h" +#include "hybbx/log.h" +#include "hybbx/ax25.h" + +#include "baycom_modem.h" + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#endif + +typedef struct baycom_instance { + hybbx_baycom_config_t config; + baycom_modem_t *modem; + int circuit_fd; + hybbx_circuit_decoder_t circuit_dec; + volatile int flow_paused; + volatile int flow_cancel; + unsigned circuit_reconnect_polls; + unsigned index; +} baycom_instance_t; + +static hybbx_service_t *g_service; + +static baycom_instance_t g_instances[HYBBX_BAYCOM_MAX_INSTANCES]; +static unsigned g_instance_count; +static pthread_t g_poll_thread; +static volatile int g_running; +static int g_poll_thread_started; + +static void baycom_poll_sleep_ms(unsigned ms); +static hybbx_result_t instance_connect_circuit(baycom_instance_t *inst); +static void on_circuit_downlink(hybbx_circuit_proto_t proto, uint16_t flags, + const uint8_t *payload, size_t len, + void *userdata); + +static int instance_circuit_uplink_allowed(const baycom_instance_t *inst) +{ + return inst != NULL && !inst->flow_paused && !inst->flow_cancel; +} + +static hybbx_result_t instance_circuit_uplink(baycom_instance_t *inst, + const uint8_t *frame, + size_t frame_len) +{ + uint8_t hbx[HYBBX_CIRCUIT_MAX_FRAME]; + size_t hbx_len; + + if (inst == NULL || inst->circuit_fd < 0 || frame_len == 0) { + return HYBBX_ERR_IO; + } + + if (!instance_circuit_uplink_allowed(inst)) { + return HYBBX_OK; + } + + hbx_len = hybbx_circuit_encode_ax25(frame, frame_len, HYBBX_CIRCUIT_FLAG_RX, + hbx, sizeof(hbx)); + if (hbx_len == 0) { + return HYBBX_ERR_IO; + } + + return hybbx_circuit_link_write(inst->circuit_fd, hbx, hbx_len); +} + +static void instance_format_call(const hybbx_ax25_address_t *addr, + char *out, size_t out_len) +{ + if (out == NULL || out_len == 0) { + return; + } + + out[0] = '\0'; + if (addr == NULL || addr->call[0] == '\0') { + return; + } + + if (addr->ssid > 0u) { + unsigned ssid = addr->ssid; + + if (ssid > HYBBX_AX25_SSID_MAX) { + ssid = HYBBX_AX25_SSID_MAX; + } + snprintf(out, out_len, "%s-%u", addr->call, ssid); + } else { + hybbx_strlcpy(out, addr->call, out_len); + } +} + +static void instance_note_rf_activity(const baycom_instance_t *inst) +{ + hybbx_circuit_hub_t *hub; + const char *link_id; + + if (inst == NULL || g_service == NULL) { + return; + } + + hub = hybbx_service_circuit_hub(g_service); + link_id = inst->config.link_id; + if (hub != NULL && link_id != NULL && link_id[0] != '\0') { + hybbx_circuit_hub_note_rf_activity(hub, link_id); + } +} + +static void instance_log_rf_tx(const baycom_instance_t *inst, + hybbx_result_t rc, + const hybbx_ax25_path_t *path, + size_t payload_len) +{ + char src[HYBBX_AX25_CALL_MAX + 8]; + char dst[HYBBX_AX25_CALL_MAX + 8]; + + if (inst == NULL) { + return; + } + + instance_format_call(path != NULL ? &path->source : NULL, src, sizeof(src)); + instance_format_call(path != NULL ? &path->dest : NULL, dst, sizeof(dst)); + + if (rc == HYBBX_OK) { + hybbx_log_stats("[baycom%u] RF TX %s>%s (%zu bytes)", + inst->index + 1, src, dst, payload_len); + } else { + hybbx_log_warn("[baycom%u] RF TX failed %s>%s (%zu bytes, rc=%d)", + inst->index + 1, src, dst, payload_len, (int)rc); + } +} + +static void instance_circuit_disconnect(baycom_instance_t *inst) +{ + if (inst == NULL) { + return; + } + + if (inst->circuit_fd >= 0) { + close(inst->circuit_fd); + inst->circuit_fd = -1; + } + hybbx_circuit_decoder_init(&inst->circuit_dec); + inst->circuit_reconnect_polls = 0; +} + +static void instance_circuit_reconnect(baycom_instance_t *inst) +{ + if (inst == NULL) { + return; + } + + instance_circuit_disconnect(inst); + baycom_poll_sleep_ms(250); + if (instance_connect_circuit(inst) == HYBBX_OK) { + hybbx_log_info("[baycom%u] circuit reconnected", inst->index + 1); + } +} + +static void instance_drain_circuit(baycom_instance_t *inst) +{ + uint8_t buf[512]; + + if (inst == NULL || inst->circuit_fd < 0) { + return; + } + + for (;;) { + size_t read_len = 0; + hybbx_result_t rc = hybbx_circuit_link_read(inst->circuit_fd, buf, + sizeof(buf), &read_len); + + if (rc == HYBBX_ERR_IO) { + hybbx_log_warn("[baycom%u] circuit disconnected — reconnecting", + inst->index + 1); + instance_circuit_reconnect(inst); + return; + } + if (rc != HYBBX_OK || read_len == 0) { + break; + } + + hybbx_circuit_decoder_feed(&inst->circuit_dec, buf, read_len, + on_circuit_downlink, inst); + } +} + +static void on_modem_ax25_frame(const uint8_t *frame, size_t len, void *userdata) +{ + baycom_instance_t *inst = (baycom_instance_t *)userdata; + + if (len == 0 || inst == NULL) { + return; + } + + (void)instance_circuit_uplink(inst, frame, len); +} + +static void baycom_poll_sleep_ms(unsigned ms) +{ +#if defined(_WIN32) + Sleep(ms); +#else + struct timeval tv; + + tv.tv_sec = (time_t)(ms / 1000u); + tv.tv_usec = (suseconds_t)((ms % 1000u) * 1000u); + (void)select(0, NULL, NULL, NULL, &tv); +#endif +} + +static hybbx_result_t instance_connect_circuit(baycom_instance_t *inst) +{ + const char *host; + unsigned port; + unsigned attempt; + + if (inst == NULL) { + return HYBBX_ERR_INVALID; + } + + host = inst->config.circuit_host; + port = inst->config.circuit_port; + + if (host == NULL || host[0] == '\0') { + host = "127.0.0.1"; + } + if (port == 0) { + port = HYBBX_CIRCUIT_DEFAULT_PORT; + } + + for (attempt = 0; attempt < 50; attempt++) { + hybbx_result_t rc = hybbx_circuit_link_connect(host, port, + &inst->circuit_fd); + if (rc == HYBBX_OK) { + hybbx_circuit_decoder_init(&inst->circuit_dec); + if (inst->config.link_password != NULL && + inst->config.link_password[0] != '\0') { + const char *link_id = inst->config.link_id; + const char *link_role = inst->config.link_role; + hybbx_circuit_link_qos_t qos; + + if (link_id == NULL || link_id[0] == '\0') { + link_id = "baycom-link"; + } + if (link_role == NULL || link_role[0] == '\0') { + link_role = "link"; + } + + memset(&qos, 0, sizeof(qos)); + qos.baud = inst->config.radio_baud; + qos.duplex = inst->config.full_duplex ? 2 : 1; + qos.bandwidth = "low"; + qos.frequency_mhz = inst->config.frequency_mhz; + + rc = hybbx_circuit_link_authenticate_ex( + inst->circuit_fd, inst->config.link_password, + link_role, link_id, &qos); + if (rc != HYBBX_OK) { + close(inst->circuit_fd); + inst->circuit_fd = -1; + baycom_poll_sleep_ms(100); + continue; + } + } + hybbx_log_info("[baycom%u] linked to internal circuit %s:%u (HBX)", + inst->index + 1, host, port); + return HYBBX_OK; + } + baycom_poll_sleep_ms(100); + } + + hybbx_log_warn("[baycom%u] could not connect to internal circuit %s:%u", + inst->index + 1, host, port); + return HYBBX_ERR_IO; +} + +static void instance_on_circuit_flow_ctrl(baycom_instance_t *inst, + const uint8_t *payload, size_t len) +{ + hybbx_circuit_balance_action_t action; + char reason[64]; + + if (inst == NULL) { + return; + } + + if (hybbx_circuit_flow_ctrl_parse((const char *)payload, len, &action, + reason, sizeof(reason)) != HYBBX_OK) { + return; + } + + switch (action) { + case HYBBX_CIRCUIT_BAL_PAUSE: + inst->flow_paused = 1; + hybbx_log_stats("[baycom%u] circuit flow pause (%s)", inst->index + 1, reason); + break; + case HYBBX_CIRCUIT_BAL_BREAK: + inst->flow_paused = 0; + hybbx_circuit_decoder_init(&inst->circuit_dec); + hybbx_log_stats("[baycom%u] circuit flow break (%s)", inst->index + 1, reason); + break; + case HYBBX_CIRCUIT_BAL_CANCEL: + inst->flow_cancel = 1; + hybbx_log_warn("[baycom%u] circuit flow cancel (%s)", + inst->index + 1, reason); + break; + case HYBBX_CIRCUIT_BAL_RESUME: + inst->flow_paused = 0; + hybbx_log_stats("[baycom%u] circuit flow resume (%s)", inst->index + 1, reason); + break; + default: + break; + } +} + +static void on_circuit_downlink(hybbx_circuit_proto_t proto, uint16_t flags, + const uint8_t *payload, size_t len, + void *userdata) +{ + baycom_instance_t *inst = (baycom_instance_t *)userdata; + + (void)flags; + + if (inst == NULL) { + return; + } + + if (proto == HYBBX_CIRCUIT_PROTO_FLOW_CTRL) { + instance_on_circuit_flow_ctrl(inst, payload, len); + return; + } + + if (inst->modem == NULL || len == 0 || inst->flow_paused) { + return; + } + + if (proto == HYBBX_CIRCUIT_PROTO_AX25) { + hybbx_result_t rc = baycom_modem_send_frame(inst->modem, payload, len); + hybbx_ax25_path_t path; + uint8_t ui[HYBBX_AX25_PAYLOAD_MAX]; + size_t ui_len = hybbx_ax25_parse_ui(payload, len, &path, ui, sizeof(ui)); + + if (rc == HYBBX_OK) { + instance_note_rf_activity(inst); + if (ui_len > 0) { + instance_log_rf_tx(inst, rc, &path, ui_len); + } + } else { + hybbx_log_warn("[baycom%u] RF TX frame failed (rc=%d, %zu bytes)", + inst->index + 1, (int)rc, len); + } + } +} + +static void instance_shutdown(baycom_instance_t *inst) +{ + if (inst == NULL) { + return; + } + + if (inst->modem != NULL) { + baycom_modem_close(inst->modem); + inst->modem = NULL; + } + + if (inst->circuit_fd >= 0) { + close(inst->circuit_fd); + inst->circuit_fd = -1; + } + + hybbx_baycom_config_free(&inst->config); + memset(inst, 0, sizeof(*inst)); + inst->circuit_fd = -1; +} + +static void *baycom_poll_thread(void *arg) +{ + unsigned i; + + (void)arg; + + while (g_running) { + for (i = 0; i < g_instance_count; i++) { + baycom_instance_t *inst = &g_instances[i]; + + if (inst->modem != NULL) { + (void)baycom_modem_poll(inst->modem); + } + + instance_drain_circuit(inst); + + if (inst->circuit_fd < 0) { + inst->circuit_reconnect_polls++; + if (inst->circuit_reconnect_polls >= 50u) { + inst->circuit_reconnect_polls = 0; + instance_circuit_reconnect(inst); + } + } else { + inst->circuit_reconnect_polls = 0; + } + + if (inst->flow_cancel) { + inst->flow_cancel = 0; + inst->flow_paused = 0; + if (inst->circuit_fd >= 0) { + close(inst->circuit_fd); + inst->circuit_fd = -1; + } + (void)instance_connect_circuit(inst); + } + } + + baycom_poll_sleep_ms(20); + } + + return NULL; +} + +static hybbx_result_t instance_start(baycom_instance_t *inst, + const char *config, + unsigned index) +{ + hybbx_result_t rc; + + if (inst == NULL || config == NULL) { + return HYBBX_ERR_INVALID; + } + + memset(inst, 0, sizeof(*inst)); + inst->circuit_fd = -1; + inst->index = index; + + rc = hybbx_baycom_config_parse(config, &inst->config); + if (rc != HYBBX_OK) { + hybbx_log_warn("[baycom%u] invalid configuration", index + 1); + instance_shutdown(inst); + return rc; + } + + { + char msg[512]; + size_t pos = 0; + + pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, + "[baycom%u] backend=%s mode=%s", + index + 1, + hybbx_baycom_backend_name(inst->config.backend), + hybbx_baycom_modem_mode_name(inst->config.mode)); + + if (inst->config.backend == HYBBX_BAYCOM_BACKEND_KERNEL) { + pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, + " interface=%s module=%s iobase=0x%x irq=%u radio_baud=%u", + inst->config.interface != NULL ? inst->config.interface : "?", + inst->config.kernel_module != NULL ? inst->config.kernel_module + : "?", + inst->config.iobase, inst->config.irq, inst->config.radio_baud); + } else { + pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, + " device=%s serial_baud=%u", + inst->config.device != NULL ? inst->config.device : "?", + inst->config.serial_baud); + } + + pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, + " circuit=%s:%u (HBX over internal TCP)", + inst->config.circuit_host, inst->config.circuit_port); + hybbx_log_info("%s", msg); + } + + if (inst->config.frequency_mhz != NULL && + inst->config.frequency_mhz[0] != '\0') { + hybbx_log_info("[baycom%u] frequency_mhz=%s", index + 1, + inst->config.frequency_mhz); + } + + rc = instance_connect_circuit(inst); + if (rc != HYBBX_OK) { + instance_shutdown(inst); + return rc; + } + + rc = baycom_modem_open(&inst->modem, &inst->config, on_modem_ax25_frame, + inst); + if (rc != HYBBX_OK) { + hybbx_log_warn("[baycom%u] modem open failed (%s)", index + 1, + hybbx_result_name(rc)); + instance_shutdown(inst); + return rc; + } + + return HYBBX_OK; +} + +static hybbx_result_t baycom_init(hybbx_service_t *service) +{ + g_service = service; + g_instance_count = 0; + g_running = 0; + return HYBBX_OK; +} + +static void baycom_shutdown(void) +{ + unsigned i; + + g_running = 0; + if (g_poll_thread_started) { + pthread_join(g_poll_thread, NULL); + g_poll_thread_started = 0; + } + + for (i = 0; i < g_instance_count; i++) { + instance_shutdown(&g_instances[i]); + } + g_instance_count = 0; +} + +static hybbx_result_t baycom_start(const char *config) +{ + hybbx_max25_config_t max25; + hybbx_result_t rc = HYBBX_OK; + unsigned started = 0; + const char *cursor; + const char *cursor_after_max25; + + if (config == NULL) { + return HYBBX_ERR_INVALID; + } + + baycom_shutdown(); + g_poll_thread = (pthread_t)0; + + cursor_after_max25 = hybbx_max25_config_skip_prefix(config, &max25); + if (max25.check && cursor_after_max25 != config) { + rc = hybbx_max25_wait_ready(&max25); + if (rc != HYBBX_OK) { + hybbx_log_warn("[baycom] local RF requires max25d — continuing without BayCom"); + return HYBBX_OK; + } + } + + cursor = cursor_after_max25; + while (*cursor != '\0' && g_instance_count < HYBBX_BAYCOM_MAX_INSTANCES) { + char scratch[4096]; + const char *sep = strchr(cursor, HYBBX_BAYCOM_INSTANCE_SEP); + size_t chunk_len; + + if (sep != NULL) { + chunk_len = (size_t)(sep - cursor); + } else { + chunk_len = strlen(cursor); + } + + if (chunk_len >= sizeof(scratch)) { + return HYBBX_ERR_INVALID; + } + + memcpy(scratch, cursor, chunk_len); + scratch[chunk_len] = '\0'; + + rc = instance_start(&g_instances[g_instance_count], scratch, + g_instance_count); + if (rc == HYBBX_OK) { + g_instance_count++; + started++; + } + + if (sep == NULL) { + break; + } + cursor = sep + 1; + } + + if (started == 0) { + /* Soft-fail: keep HyBBX up (TNCs/telnet/…) if BayCom/based open fails. */ + hybbx_log_warn("[baycom] no instances started (%s) — continuing without BayCom", + rc != HYBBX_OK ? hybbx_result_name(rc) : "invalid"); + return HYBBX_OK; + } + + g_running = 1; + if (pthread_create(&g_poll_thread, NULL, baycom_poll_thread, NULL) != 0) { + baycom_shutdown(); + return HYBBX_ERR_IO; + } + g_poll_thread_started = 1; + + return HYBBX_OK; +} + +static hybbx_result_t baycom_stop(void) +{ + baycom_shutdown(); + return HYBBX_OK; +} + +const hybbx_transport_plugin_t hybbx_plugin_baycom = { + .name = "baycom", + .kind = HYBBX_TRANSPORT_BAYCOM, + .version = 1, + .init = baycom_init, + .shutdown = baycom_shutdown, + .start = baycom_start, + .stop = baycom_stop, + .write = NULL, +}; diff --git a/plugins/baycom/baycom_config.c b/plugins/baycom/baycom_config.c new file mode 100644 index 0000000..4e740f1 --- /dev/null +++ b/plugins/baycom/baycom_config.c @@ -0,0 +1,395 @@ +#include "hybbx/baycom.h" +#include "hybbx/circuit.h" +#include "hybbx/util.h" + +#include +#include +#include + +static char *baycom_strdup(const char *s) +{ + size_t len; + char *copy; + + if (s == NULL) { + return NULL; + } + + len = strlen(s) + 1; + copy = malloc(len); + if (copy != NULL) { + memcpy(copy, s, len); + } + return copy; +} + +static char *baycom_normalize_kiss_path(const char *device) +{ + static const struct { + const char *from; + const char *to; + } map[] = { + {"/tmp/bcpr/", "/tmp/max25-bcpr/"}, + {"/tmp/bcpr", "/tmp/max25-bcpr"}, + {"/var/run/bcpr/", "/tmp/max25-bcpr/"}, + {"/var/run/bcpr", "/tmp/max25-bcpr"}, + }; + size_t i; + size_t flen; + char *norm; + + if (device == NULL || device[0] == '\0') { + return NULL; + } + + for (i = 0; i < sizeof(map) / sizeof(map[0]); i++) { + flen = strlen(map[i].from); + if (strncmp(device, map[i].from, flen) != 0) { + continue; + } + if (device[flen] != '\0' && device[flen] != '/') { + continue; + } + norm = malloc(strlen(map[i].to) + strlen(device + flen) + 1); + if (norm == NULL) { + return baycom_strdup(device); + } + snprintf(norm, strlen(map[i].to) + strlen(device + flen) + 1, "%s%s", + map[i].to, device + flen); + return norm; + } + + return baycom_strdup(device); +} + +static int str_ieq(const char *a, const char *b) +{ + if (a == NULL || b == NULL) { + return 0; + } + + while (*a != '\0' && *b != '\0') { + char ca = (char)(*a >= 'A' && *a <= 'Z' ? *a + 32 : *a); + char cb = (char)(*b >= 'A' && *b <= 'Z' ? *b + 32 : *b); + + if (ca != cb) { + return 0; + } + a++; + b++; + } + + return *a == '\0' && *b == '\0'; +} + +static const char *find_kv(const char *config, const char *key, + char *scratch, size_t scratch_len) +{ + const char *cursor = config; + size_t key_len = strlen(key); + + if (config == NULL || key == NULL) { + return NULL; + } + + while (*cursor != '\0') { + const char *sep = strchr(cursor, ';'); + const char *end = sep != NULL ? sep : cursor + strlen(cursor); + const char *eq = strchr(cursor, '='); + + if (eq != NULL && eq < end && (size_t)(eq - cursor) == key_len && + strncmp(cursor, key, key_len) == 0) { + const char *value = eq + 1; + size_t value_len = (size_t)(end - value); + + if (value_len >= scratch_len) { + value_len = scratch_len - 1; + } + memcpy(scratch, value, value_len); + scratch[value_len] = '\0'; + return scratch; + } + + if (sep == NULL) { + break; + } + cursor = sep + 1; + } + + return NULL; +} + +const char *hybbx_baycom_backend_name(hybbx_baycom_backend_t backend) +{ + switch (backend) { + case HYBBX_BAYCOM_BACKEND_KISS: + return "kiss"; + case HYBBX_BAYCOM_BACKEND_KERNEL: + default: + return "kernel"; + } +} + +const char *hybbx_baycom_modem_mode_name(hybbx_baycom_modem_mode_t mode) +{ + switch (mode) { + case HYBBX_BAYCOM_MODE_SER12_SOFTDCD: + return "ser12*"; + case HYBBX_BAYCOM_MODE_SER12_INV: + return "ser12+"; + case HYBBX_BAYCOM_MODE_SER12HDX: + return "ser12hdx"; + case HYBBX_BAYCOM_MODE_PAR96: + return "par96"; + case HYBBX_BAYCOM_MODE_PAR96_SOFTDCD: + return "par96*"; + case HYBBX_BAYCOM_MODE_EPP: + return "epp"; + case HYBBX_BAYCOM_MODE_SER12: + default: + return "ser12"; + } +} + +static hybbx_baycom_backend_t parse_backend(const char *value) +{ + if (value == NULL || str_ieq(value, "kernel") || str_ieq(value, "hdlc") || + str_ieq(value, "prstack") || str_ieq(value, "l2")) { + return HYBBX_BAYCOM_BACKEND_KERNEL; + } + + if (str_ieq(value, "kiss") || str_ieq(value, "serial")) { + return HYBBX_BAYCOM_BACKEND_KISS; + } + + return HYBBX_BAYCOM_BACKEND_KERNEL; +} + +static hybbx_baycom_modem_mode_t parse_mode(const char *value) +{ + if (value == NULL || str_ieq(value, "ser12")) { + return HYBBX_BAYCOM_MODE_SER12; + } + if (str_ieq(value, "ser12*") || str_ieq(value, "ser12_softdcd")) { + return HYBBX_BAYCOM_MODE_SER12_SOFTDCD; + } + if (str_ieq(value, "ser12+") || str_ieq(value, "ser12_inv")) { + return HYBBX_BAYCOM_MODE_SER12_INV; + } + if (str_ieq(value, "ser12hdx") || str_ieq(value, "ser12_hdx") || + str_ieq(value, "hdx")) { + return HYBBX_BAYCOM_MODE_SER12HDX; + } + if (str_ieq(value, "par96")) { + return HYBBX_BAYCOM_MODE_PAR96; + } + if (str_ieq(value, "par96*") || str_ieq(value, "par96_softdcd")) { + return HYBBX_BAYCOM_MODE_PAR96_SOFTDCD; + } + if (str_ieq(value, "epp") || str_ieq(value, "par97") || + str_ieq(value, "baycom_epp")) { + return HYBBX_BAYCOM_MODE_EPP; + } + + return HYBBX_BAYCOM_MODE_SER12_SOFTDCD; +} + +void hybbx_baycom_config_free(hybbx_baycom_config_t *config) +{ + if (config == NULL) { + return; + } + + free(config->interface); + free(config->kernel_module); + free(config->device); + free(config->mycall); + free(config->circuit_host); + free(config->link_id); + free(config->link_password); + free(config->link_role); + free(config->frequency_mhz); + memset(config, 0, sizeof(*config)); +} + +static void apply_mode_defaults(hybbx_baycom_config_t *out) +{ + if (out->interface == NULL) { + switch (out->mode) { + case HYBBX_BAYCOM_MODE_SER12HDX: + out->interface = baycom_strdup("bcsh0"); + break; + case HYBBX_BAYCOM_MODE_PAR96: + case HYBBX_BAYCOM_MODE_PAR96_SOFTDCD: + out->interface = baycom_strdup("bcp0"); + break; + case HYBBX_BAYCOM_MODE_EPP: + out->interface = baycom_strdup("bce0"); + break; + case HYBBX_BAYCOM_MODE_SER12: + case HYBBX_BAYCOM_MODE_SER12_SOFTDCD: + case HYBBX_BAYCOM_MODE_SER12_INV: + default: + out->interface = baycom_strdup(HYBBX_BAYCOM_DEFAULT_INTERFACE); + break; + } + } + + if (out->kernel_module == NULL) { + switch (out->mode) { + case HYBBX_BAYCOM_MODE_SER12HDX: + out->kernel_module = baycom_strdup("baycom_ser_hdx"); + break; + case HYBBX_BAYCOM_MODE_PAR96: + case HYBBX_BAYCOM_MODE_PAR96_SOFTDCD: + out->kernel_module = baycom_strdup("baycom_par"); + break; + case HYBBX_BAYCOM_MODE_EPP: + out->kernel_module = baycom_strdup("baycom_epp"); + break; + default: + out->kernel_module = baycom_strdup("baycom_ser_fdx"); + break; + } + } + + if (out->device == NULL) { + if (out->backend == HYBBX_BAYCOM_BACKEND_KISS) { + out->device = baycom_strdup("/tmp/max25-bcpr/kiss-bc0"); + } else { + out->device = baycom_strdup("/dev/ttyS0"); + } + } +} + +hybbx_result_t hybbx_baycom_config_parse(const char *config, + hybbx_baycom_config_t *out) +{ + char scratch[128]; + const char *value; + + if (out == NULL) { + return HYBBX_ERR_INVALID; + } + + hybbx_baycom_config_free(out); + + if (config == NULL) { + config = ""; + } + + value = find_kv(config, "backend", scratch, sizeof(scratch)); + out->backend = parse_backend(value); + + value = find_kv(config, "mode", scratch, sizeof(scratch)); + out->mode = parse_mode(value); + + value = find_kv(config, "interface", scratch, sizeof(scratch)); + if (value != NULL) { + out->interface = baycom_strdup(value); + } + + value = find_kv(config, "kernel_module", scratch, sizeof(scratch)); + if (value != NULL) { + out->kernel_module = baycom_strdup(value); + } + + value = find_kv(config, "iobase", scratch, sizeof(scratch)); + out->iobase = value != NULL ? (unsigned)strtoul(value, NULL, 0) + : HYBBX_BAYCOM_DEFAULT_IOBASE; + + value = find_kv(config, "irq", scratch, sizeof(scratch)); + out->irq = value != NULL ? (unsigned)strtoul(value, NULL, 0) + : HYBBX_BAYCOM_DEFAULT_IRQ; + + value = find_kv(config, "radio_baud", scratch, sizeof(scratch)); + if (value == NULL) { + value = find_kv(config, "baud", scratch, sizeof(scratch)); + } + out->radio_baud = value != NULL ? (unsigned)strtoul(value, NULL, 0) + : HYBBX_BAYCOM_DEFAULT_RADIO_BAUD; + + value = find_kv(config, "kernel_autoload", scratch, sizeof(scratch)); + out->kernel_autoload = value != NULL ? hybbx_parse_bool(value, 0) : 0; + + value = find_kv(config, "txdelay", scratch, sizeof(scratch)); + out->txdelay = value != NULL ? (unsigned)strtoul(value, NULL, 0) : 30u; + + value = find_kv(config, "txtail", scratch, sizeof(scratch)); + out->txtail = value != NULL ? (unsigned)strtoul(value, NULL, 0) : 1u; + + value = find_kv(config, "slot", scratch, sizeof(scratch)); + if (value == NULL) { + value = find_kv(config, "slottime", scratch, sizeof(scratch)); + } + out->slot = value != NULL ? (unsigned)strtoul(value, NULL, 0) : 10u; + + value = find_kv(config, "persist", scratch, sizeof(scratch)); + if (value == NULL) { + value = find_kv(config, "ppersist", scratch, sizeof(scratch)); + } + out->persist = value != NULL ? (unsigned)strtoul(value, NULL, 0) : 128u; + + value = find_kv(config, "full_duplex", scratch, sizeof(scratch)); + if (value != NULL) { + out->full_duplex = hybbx_parse_bool(value, 0); + } else { + value = find_kv(config, "duplex", scratch, sizeof(scratch)); + out->full_duplex = value != NULL && str_ieq(value, "full"); + } + + value = find_kv(config, "device", scratch, sizeof(scratch)); + if (value != NULL) { + out->device = baycom_strdup(value); + } + + value = find_kv(config, "serial_baud", scratch, sizeof(scratch)); + if (value == NULL) { + value = find_kv(config, "host_baud", scratch, sizeof(scratch)); + } + out->serial_baud = value != NULL ? (unsigned)strtoul(value, NULL, 0) : 1200u; + + value = find_kv(config, "mycall", scratch, sizeof(scratch)); + if (value != NULL) { + out->mycall = baycom_strdup(value); + } + + value = find_kv(config, "circuit_host", scratch, sizeof(scratch)); + out->circuit_host = baycom_strdup(value != NULL ? value : "127.0.0.1"); + + value = find_kv(config, "circuit_port", scratch, sizeof(scratch)); + out->circuit_port = value != NULL ? (unsigned)strtoul(value, NULL, 0) + : HYBBX_CIRCUIT_DEFAULT_PORT; + + value = find_kv(config, "link_id", scratch, sizeof(scratch)); + out->link_id = baycom_strdup(value != NULL ? value : "baycom-link"); + + value = find_kv(config, "link_password", scratch, sizeof(scratch)); + out->link_password = baycom_strdup(value != NULL ? value : ""); + + value = find_kv(config, "link_role", scratch, sizeof(scratch)); + out->link_role = baycom_strdup(value != NULL ? value : "link"); + + value = find_kv(config, "frequency_mhz", scratch, sizeof(scratch)); + if (value != NULL) { + out->frequency_mhz = baycom_strdup(value); + } + + apply_mode_defaults(out); + + if (out->backend == HYBBX_BAYCOM_BACKEND_KISS && out->device != NULL) { + char *norm = baycom_normalize_kiss_path(out->device); + + if (norm != NULL) { + free(out->device); + out->device = norm; + } + } + + if (out->circuit_host == NULL || out->link_id == NULL) { + hybbx_baycom_config_free(out); + return HYBBX_ERR_NOMEM; + } + + return HYBBX_OK; +} diff --git a/plugins/baycom/baycom_kernel.c b/plugins/baycom/baycom_kernel.c new file mode 100644 index 0000000..0babc08 --- /dev/null +++ b/plugins/baycom/baycom_kernel.c @@ -0,0 +1,472 @@ +#if defined(__linux__) +#define _DEFAULT_SOURCE +#endif + +#include "baycom_kernel.h" + +#include "hybbx/ax25.h" +#include "hybbx/baycom.h" +#include "hybbx/log.h" +#include "hybbx/rf_tx_pace.h" + +#include +#include +#include +#include +#include + +#if defined(__linux__) + +#include +#include +#include +#include +#include +#include +#include +#include + +static char *baycom_strdup(const char *s) +{ + size_t len; + char *copy; + + if (s == NULL) { + return NULL; + } + + len = strlen(s) + 1; + copy = malloc(len); + if (copy != NULL) { + memcpy(copy, s, len); + } + return copy; +} + +struct baycom_kernel_modem { + hybbx_baycom_config_t cfg; + baycom_modem_frame_cb frame_cb; + void *frame_userdata; + int ctl_fd; + int pkt_fd; + int ifindex; +}; + +static void baycom_kernel_mode_string(const hybbx_baycom_config_t *cfg, + char *out, size_t out_cap) +{ + unsigned rate; + char suffix = '\0'; + + if (out == NULL || out_cap == 0 || cfg == NULL) { + return; + } + + switch (cfg->mode) { + case HYBBX_BAYCOM_MODE_SER12_SOFTDCD: + case HYBBX_BAYCOM_MODE_PAR96_SOFTDCD: + suffix = '*'; + break; + case HYBBX_BAYCOM_MODE_SER12_INV: + suffix = '+'; + break; + default: + break; + } + + if (cfg->mode == HYBBX_BAYCOM_MODE_PAR96 || + cfg->mode == HYBBX_BAYCOM_MODE_PAR96_SOFTDCD) { + snprintf(out, out_cap, "par96%c", suffix); + return; + } + + if (cfg->mode == HYBBX_BAYCOM_MODE_EPP) { + snprintf(out, out_cap, "epp"); + return; + } + + rate = cfg->radio_baud / 100u; + if (rate == 0) { + rate = 12; + } + snprintf(out, out_cap, "ser%u%c", rate, suffix); +} + +static int baycom_kernel_hdlc_ioctl(baycom_kernel_modem_t *modem, + struct hdlcdrv_ioctl *hi) +{ + struct ifreq ifr; + + if (modem == NULL || hi == NULL || modem->ctl_fd < 0 || + modem->cfg.interface == NULL) { + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, modem->cfg.interface, IFNAMSIZ - 1); + ifr.ifr_data = (void *)hi; + + if (ioctl(modem->ctl_fd, SIOCDEVPRIVATE, &ifr) < 0) { + return -1; + } + + return 0; +} + +static int baycom_kernel_interface_flags(baycom_kernel_modem_t *modem, + unsigned short *flags_out) +{ + struct ifreq ifr; + + if (modem == NULL || flags_out == NULL || modem->ctl_fd < 0) { + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, modem->cfg.interface, IFNAMSIZ - 1); + if (ioctl(modem->ctl_fd, SIOCGIFFLAGS, &ifr) < 0) { + return -1; + } + + *flags_out = ifr.ifr_flags; + return 0; +} + +static int baycom_kernel_interface_up(baycom_kernel_modem_t *modem, int up) +{ + struct ifreq ifr; + unsigned short flags = 0; + + if (baycom_kernel_interface_flags(modem, &flags) < 0) { + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, modem->cfg.interface, IFNAMSIZ - 1); + ifr.ifr_flags = flags; + if (up) { + ifr.ifr_flags |= IFF_UP; + } else { + ifr.ifr_flags &= (unsigned short)~IFF_UP; + } + + return ioctl(modem->ctl_fd, SIOCSIFFLAGS, &ifr); +} + +static int baycom_kernel_set_modem_params(baycom_kernel_modem_t *modem) +{ + struct hdlcdrv_ioctl hi; + + memset(&hi, 0, sizeof(hi)); + hi.cmd = HDLCDRVCTL_SETMODEMPAR; + hi.data.mp.iobase = (int)modem->cfg.iobase; + hi.data.mp.irq = (int)modem->cfg.irq; + + return baycom_kernel_hdlc_ioctl(modem, &hi); +} + +static int baycom_kernel_set_mode(baycom_kernel_modem_t *modem) +{ + struct hdlcdrv_ioctl hi; + char modestr[32]; + + baycom_kernel_mode_string(&modem->cfg, modestr, sizeof(modestr)); + + memset(&hi, 0, sizeof(hi)); + hi.cmd = HDLCDRVCTL_SETMODE; + strncpy(hi.data.modename, modestr, sizeof(hi.data.modename) - 1); + + return baycom_kernel_hdlc_ioctl(modem, &hi); +} + +static int baycom_kernel_set_channel_params(baycom_kernel_modem_t *modem) +{ + struct hdlcdrv_ioctl hi; + + memset(&hi, 0, sizeof(hi)); + hi.cmd = HDLCDRVCTL_SETCHANNELPAR; + hi.data.cp.tx_delay = (int)modem->cfg.txdelay; + hi.data.cp.tx_tail = (int)modem->cfg.txtail; + hi.data.cp.slottime = (int)modem->cfg.slot; + hi.data.cp.ppersist = (int)modem->cfg.persist; + hi.data.cp.fulldup = modem->cfg.full_duplex ? 1 : 0; + + return baycom_kernel_hdlc_ioctl(modem, &hi); +} + +static int baycom_kernel_modprobe(const hybbx_baycom_config_t *cfg) +{ + char cmd[384]; + char modestr[32]; + int rc; + + if (cfg == NULL || cfg->kernel_module == NULL) { + return -1; + } + + baycom_kernel_mode_string(cfg, modestr, sizeof(modestr)); + snprintf(cmd, sizeof(cmd), + "modprobe %s mode=%s iobase=0x%x irq=%u baud=%u 2>/dev/null", + cfg->kernel_module, modestr, cfg->iobase, cfg->irq, + cfg->radio_baud); + + rc = system(cmd); + if (rc != 0) { + hybbx_log_warn("[baycom] modprobe failed (%s)", cfg->kernel_module); + return -1; + } + + return 0; +} + +static int baycom_kernel_ifindex(baycom_kernel_modem_t *modem) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, modem->cfg.interface, IFNAMSIZ - 1); + if (ioctl(modem->ctl_fd, SIOCGIFINDEX, &ifr) < 0) { + return -1; + } + + modem->ifindex = ifr.ifr_ifindex; + return 0; +} + +static int baycom_kernel_open_packet(baycom_kernel_modem_t *modem) +{ + struct sockaddr_ll bind_addr; + + modem->pkt_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_AX25)); + if (modem->pkt_fd < 0) { + return -1; + } + + if (baycom_kernel_ifindex(modem) < 0) { + return -1; + } + + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sll_family = AF_PACKET; + bind_addr.sll_protocol = htons(ETH_P_AX25); + bind_addr.sll_ifindex = modem->ifindex; + + if (bind(modem->pkt_fd, (struct sockaddr *)&bind_addr, + sizeof(bind_addr)) < 0) { + return -1; + } + + return 0; +} + +hybbx_result_t baycom_kernel_open(baycom_kernel_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata) +{ + baycom_kernel_modem_t *km; + unsigned short flags = 0; + + if (out == NULL || cfg == NULL || cfg->interface == NULL) { + return HYBBX_ERR_INVALID; + } + + *out = NULL; + km = calloc(1, sizeof(*km)); + if (km == NULL) { + return HYBBX_ERR_NOMEM; + } + + /* Own string copies only — avoid double-free with caller config. */ + memset(&km->cfg, 0, sizeof(km->cfg)); + km->cfg.backend = cfg->backend; + km->cfg.mode = cfg->mode; + km->cfg.iobase = cfg->iobase; + km->cfg.irq = cfg->irq; + km->cfg.radio_baud = cfg->radio_baud; + km->cfg.kernel_autoload = cfg->kernel_autoload; + km->cfg.txdelay = cfg->txdelay; + km->cfg.txtail = cfg->txtail; + km->cfg.slot = cfg->slot; + km->cfg.persist = cfg->persist; + km->cfg.full_duplex = cfg->full_duplex; + km->cfg.serial_baud = cfg->serial_baud; + km->cfg.circuit_port = cfg->circuit_port; + km->cfg.interface = baycom_strdup(cfg->interface); + km->cfg.kernel_module = baycom_strdup(cfg->kernel_module); + km->cfg.device = baycom_strdup(cfg->device); + km->cfg.mycall = baycom_strdup(cfg->mycall); + km->cfg.circuit_host = baycom_strdup(cfg->circuit_host); + km->cfg.link_id = baycom_strdup(cfg->link_id); + km->cfg.link_password = baycom_strdup(cfg->link_password); + km->cfg.link_role = baycom_strdup(cfg->link_role); + km->cfg.frequency_mhz = baycom_strdup(cfg->frequency_mhz); + km->frame_cb = cb; + km->frame_userdata = userdata; + km->ctl_fd = -1; + km->pkt_fd = -1; + + if (km->cfg.interface == NULL) { + free(km); + return HYBBX_ERR_NOMEM; + } + + if (cfg->kernel_autoload) { + if (baycom_kernel_modprobe(cfg) < 0) { + hybbx_baycom_config_free(&km->cfg); + free(km); + return HYBBX_ERR_IO; + } + } + + km->ctl_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (km->ctl_fd < 0) { + hybbx_baycom_config_free(&km->cfg); + free(km); + return HYBBX_ERR_IO; + } + + if (baycom_kernel_interface_flags(km, &flags) < 0) { + hybbx_log_warn("[baycom] interface %s not found (load %s first)", + km->cfg.interface, + km->cfg.kernel_module != NULL ? km->cfg.kernel_module + : "baycom module"); + baycom_kernel_close(km); + return HYBBX_ERR_IO; + } + + if (!(flags & IFF_UP)) { + if (baycom_kernel_set_modem_params(km) < 0) { + hybbx_log_warn("[baycom] SETMODEMPAR failed (need CAP_SYS_RAWIO, iface down)"); + } + if (baycom_kernel_set_mode(km) < 0) { + hybbx_log_warn("[baycom] SETMODE failed (need CAP_NET_ADMIN, iface down)"); + } + if (baycom_kernel_interface_up(km, 1) < 0) { + hybbx_log_warn("[baycom] failed to bring %s up", km->cfg.interface); + baycom_kernel_close(km); + return HYBBX_ERR_IO; + } + } + + if (baycom_kernel_set_channel_params(km) < 0) { + hybbx_log_warn("[baycom] channel params ioctl failed"); + } + + if (baycom_kernel_open_packet(km) < 0) { + baycom_kernel_close(km); + return HYBBX_ERR_IO; + } + + *out = km; + return HYBBX_OK; +} + +void baycom_kernel_close(baycom_kernel_modem_t *km) +{ + if (km == NULL) { + return; + } + + if (km->pkt_fd >= 0) { + close(km->pkt_fd); + } + if (km->ctl_fd >= 0) { + close(km->ctl_fd); + } + + hybbx_baycom_config_free(&km->cfg); + free(km); +} + +hybbx_result_t baycom_kernel_poll(baycom_kernel_modem_t *km) +{ + uint8_t buf[HYBBX_AX25_FRAME_MAX + 64]; + ssize_t n; + + if (km == NULL || km->pkt_fd < 0) { + return HYBBX_ERR_IO; + } + + for (;;) { + n = recv(km->pkt_fd, buf, sizeof(buf), MSG_DONTWAIT); + if (n < 0) { + if (errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || errno == EWOULDBLOCK +#endif + ) { + return HYBBX_OK; + } + return HYBBX_ERR_IO; + } + if (n == 0) { + return HYBBX_OK; + } + + if (km->frame_cb != NULL && (size_t)n > 0) { + km->frame_cb(buf, (size_t)n, km->frame_userdata); + } + } +} + +hybbx_result_t baycom_kernel_send_frame(baycom_kernel_modem_t *km, + const uint8_t *frame, size_t len) +{ + struct sockaddr_ll dest; + ssize_t n; + + if (km == NULL || frame == NULL || len == 0 || km->pkt_fd < 0) { + return HYBBX_ERR_INVALID; + } + + hybbx_rf_tx_pace(); + + memset(&dest, 0, sizeof(dest)); + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_AX25); + dest.sll_ifindex = km->ifindex; + dest.sll_halen = 0; + + n = sendto(km->pkt_fd, frame, len, 0, (struct sockaddr *)&dest, + sizeof(dest)); + if (n < 0 || (size_t)n != len) { + return HYBBX_ERR_IO; + } + + return HYBBX_OK; +} + +#else /* !__linux__ */ + +hybbx_result_t baycom_kernel_open(baycom_kernel_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata) +{ + (void)out; + (void)cfg; + (void)cb; + (void)userdata; + return HYBBX_ERR_UNSUPPORTED; +} + +void baycom_kernel_close(baycom_kernel_modem_t *modem) +{ + (void)modem; +} + +hybbx_result_t baycom_kernel_poll(baycom_kernel_modem_t *modem) +{ + (void)modem; + return HYBBX_ERR_UNSUPPORTED; +} + +hybbx_result_t baycom_kernel_send_frame(baycom_kernel_modem_t *modem, + const uint8_t *frame, size_t len) +{ + (void)modem; + (void)frame; + (void)len; + return HYBBX_ERR_UNSUPPORTED; +} + +#endif /* __linux__ */ diff --git a/plugins/baycom/baycom_kernel.h b/plugins/baycom/baycom_kernel.h new file mode 100644 index 0000000..b35e3b9 --- /dev/null +++ b/plugins/baycom/baycom_kernel.h @@ -0,0 +1,26 @@ +#ifndef BAYCOM_KERNEL_H +#define BAYCOM_KERNEL_H + +#include "baycom_modem.h" +#include "hybbx/baycom.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct baycom_kernel_modem baycom_kernel_modem_t; + +hybbx_result_t baycom_kernel_open(baycom_kernel_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata); +void baycom_kernel_close(baycom_kernel_modem_t *modem); +hybbx_result_t baycom_kernel_poll(baycom_kernel_modem_t *modem); +hybbx_result_t baycom_kernel_send_frame(baycom_kernel_modem_t *modem, + const uint8_t *frame, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* BAYCOM_KERNEL_H */ diff --git a/plugins/baycom/baycom_kiss.c b/plugins/baycom/baycom_kiss.c new file mode 100644 index 0000000..c66b220 --- /dev/null +++ b/plugins/baycom/baycom_kiss.c @@ -0,0 +1,262 @@ +#if defined(__linux__) || defined(__GLIBC__) +#define _DEFAULT_SOURCE 1 +#endif + +#include "baycom_kiss.h" + +#include "hybbx/kiss.h" +#include "hybbx/rf_tx_pace.h" +#include "serial_port.h" +#include "hybbx/log.h" +#include "hybbx/limits.h" + +#include +#include +#include +#include +#include + +static hybbx_result_t kiss_wait_device(const char *path) +{ + unsigned waited_ms = 0; + const unsigned step_ms = 500u; + const unsigned max_wait_ms = 30000u; + + if (path == NULL || path[0] == '\0') { + return HYBBX_ERR_INVALID; + } + + if (access(path, F_OK) == 0) { + return HYBBX_OK; + } + + while (access(path, F_OK) != 0) { + if (waited_ms >= max_wait_ms) { + hybbx_log_warn("[baycom] kiss device not ready: %s", path); + return HYBBX_ERR_IO; + } + if (waited_ms == 0u) { + hybbx_log_info("[baycom] waiting for kiss device %s (up to %u s)", + path, max_wait_ms / 1000u); + } + usleep((useconds_t)step_ms * 1000u); + waited_ms += step_ms; + } + + return HYBBX_OK; +} + +struct baycom_kiss_modem { + hybbx_baycom_config_t cfg; + hybbx_serial_port_t *serial; + hybbx_kiss_decoder_t kiss_dec; + baycom_modem_frame_cb frame_cb; + void *frame_userdata; +}; + +static char *baycom_strdup(const char *s) +{ + size_t len; + char *copy; + + if (s == NULL) { + return NULL; + } + + len = strlen(s) + 1; + copy = malloc(len); + if (copy != NULL) { + memcpy(copy, s, len); + } + return copy; +} + +static void on_kiss_frame(uint8_t port, const uint8_t *frame, size_t len, + void *userdata) +{ + baycom_kiss_modem_t *km = (baycom_kiss_modem_t *)userdata; + + (void)port; + + if (km == NULL || km->frame_cb == NULL || len == 0) { + return; + } + + km->frame_cb(frame, len, km->frame_userdata); +} + +static hybbx_result_t kiss_send_param(baycom_kiss_modem_t *km, + hybbx_kiss_command_t cmd, uint8_t value) +{ + uint8_t out[16]; + size_t out_len; + + out_len = hybbx_kiss_encode(0, cmd, &value, 1, out, sizeof(out)); + if (out_len == 0) { + return HYBBX_ERR_IO; + } + + return hybbx_serial_write(km->serial, out, out_len); +} + +static hybbx_result_t kiss_apply_channel_params(baycom_kiss_modem_t *km) +{ + hybbx_result_t rc; + + rc = kiss_send_param(km, HYBBX_KISS_CMD_TXDELAY, (uint8_t)km->cfg.txdelay); + if (rc != HYBBX_OK) { + return rc; + } + rc = kiss_send_param(km, HYBBX_KISS_CMD_PERSIST, (uint8_t)km->cfg.persist); + if (rc != HYBBX_OK) { + return rc; + } + rc = kiss_send_param(km, HYBBX_KISS_CMD_SLOTTIME, (uint8_t)km->cfg.slot); + if (rc != HYBBX_OK) { + return rc; + } + rc = kiss_send_param(km, HYBBX_KISS_CMD_TXTAIL, (uint8_t)km->cfg.txtail); + if (rc != HYBBX_OK) { + return rc; + } + return kiss_send_param(km, HYBBX_KISS_CMD_FULLDUPLEX, + km->cfg.full_duplex ? 1u : 0u); +} + +hybbx_result_t baycom_kiss_open(baycom_kiss_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata) +{ + baycom_kiss_modem_t *km; + hybbx_serial_params_t sparams; + hybbx_result_t rc; + + if (out == NULL || cfg == NULL || cfg->device == NULL) { + return HYBBX_ERR_INVALID; + } + + *out = NULL; + km = calloc(1, sizeof(*km)); + if (km == NULL) { + return HYBBX_ERR_NOMEM; + } + + /* Own string copies only — never shallow-copy pointers from caller config + * (hybbx_baycom_config_free on close would double-free inst->config). */ + memset(&km->cfg, 0, sizeof(km->cfg)); + km->cfg.backend = cfg->backend; + km->cfg.mode = cfg->mode; + km->cfg.iobase = cfg->iobase; + km->cfg.irq = cfg->irq; + km->cfg.radio_baud = cfg->radio_baud; + km->cfg.kernel_autoload = cfg->kernel_autoload; + km->cfg.txdelay = cfg->txdelay; + km->cfg.txtail = cfg->txtail; + km->cfg.slot = cfg->slot; + km->cfg.persist = cfg->persist; + km->cfg.full_duplex = cfg->full_duplex; + km->cfg.serial_baud = cfg->serial_baud; + km->cfg.circuit_port = cfg->circuit_port; + km->cfg.device = baycom_strdup(cfg->device); + km->cfg.mycall = baycom_strdup(cfg->mycall); + km->cfg.circuit_host = baycom_strdup(cfg->circuit_host); + km->cfg.link_id = baycom_strdup(cfg->link_id); + km->cfg.link_password = baycom_strdup(cfg->link_password); + km->cfg.link_role = baycom_strdup(cfg->link_role); + km->cfg.frequency_mhz = baycom_strdup(cfg->frequency_mhz); + km->frame_cb = cb; + km->frame_userdata = userdata; + hybbx_kiss_decoder_init(&km->kiss_dec); + + hybbx_serial_params_default(&sparams, cfg->serial_baud); + sparams.data_bits = 8; + sparams.parity = HYBBX_SERIAL_PARITY_NONE; + sparams.stop_bits = 1; + sparams.assert_modem_lines = 0; + + rc = kiss_wait_device(km->cfg.device); + if (rc != HYBBX_OK) { + baycom_kiss_close(km); + return rc; + } + + if (access(km->cfg.device, R_OK | W_OK) != 0) { + hybbx_log_warn("[baycom] kiss device not readable/writable: %s (%s; user in dialout?)", + km->cfg.device, strerror(errno)); + baycom_kiss_close(km); + return HYBBX_ERR_IO; + } + + rc = hybbx_serial_open(&km->serial, km->cfg.device, &sparams); + if (rc != HYBBX_OK) { + hybbx_log_warn("[baycom] kiss serial open failed: %s (see [serial] log)", + km->cfg.device); + baycom_kiss_close(km); + return rc; + } + + rc = kiss_apply_channel_params(km); + if (rc != HYBBX_OK) { + hybbx_log_warn("[baycom] kiss channel params failed"); + } + + *out = km; + return HYBBX_OK; +} + +void baycom_kiss_close(baycom_kiss_modem_t *km) +{ + if (km == NULL) { + return; + } + + if (km->serial != NULL) { + hybbx_serial_close(km->serial); + } + + hybbx_baycom_config_free(&km->cfg); + free(km); +} + +hybbx_result_t baycom_kiss_poll(baycom_kiss_modem_t *km) +{ + uint8_t buf[256]; + size_t read_len = 0; + hybbx_result_t rc; + + if (km == NULL || km->serial == NULL) { + return HYBBX_ERR_IO; + } + + rc = hybbx_serial_read(km->serial, buf, sizeof(buf), &read_len); + if (rc != HYBBX_OK) { + return rc; + } + + if (read_len > 0) { + hybbx_kiss_decoder_feed(&km->kiss_dec, buf, read_len, on_kiss_frame, km); + } + + return HYBBX_OK; +} + +hybbx_result_t baycom_kiss_send_frame(baycom_kiss_modem_t *km, + const uint8_t *frame, size_t len) +{ + uint8_t out[HYBBX_KISS_MAX_FRAME + 8]; + size_t out_len; + + if (km == NULL || km->serial == NULL || frame == NULL || len == 0) { + return HYBBX_ERR_INVALID; + } + + hybbx_rf_tx_burst_guard(); + + out_len = hybbx_kiss_encode(0, HYBBX_KISS_CMD_DATA, frame, len, + out, sizeof(out)); + if (out_len == 0) { + return HYBBX_ERR_IO; + } + + return hybbx_serial_write(km->serial, out, out_len); +} diff --git a/plugins/baycom/baycom_kiss.h b/plugins/baycom/baycom_kiss.h new file mode 100644 index 0000000..49c7b36 --- /dev/null +++ b/plugins/baycom/baycom_kiss.h @@ -0,0 +1,26 @@ +#ifndef BAYCOM_KISS_H +#define BAYCOM_KISS_H + +#include "baycom_modem.h" +#include "hybbx/baycom.h" +#include "hybbx/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct baycom_kiss_modem baycom_kiss_modem_t; + +hybbx_result_t baycom_kiss_open(baycom_kiss_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata); +void baycom_kiss_close(baycom_kiss_modem_t *modem); +hybbx_result_t baycom_kiss_poll(baycom_kiss_modem_t *modem); +hybbx_result_t baycom_kiss_send_frame(baycom_kiss_modem_t *modem, + const uint8_t *frame, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* BAYCOM_KISS_H */ diff --git a/plugins/baycom/baycom_modem.c b/plugins/baycom/baycom_modem.c new file mode 100644 index 0000000..e4e332f --- /dev/null +++ b/plugins/baycom/baycom_modem.c @@ -0,0 +1,94 @@ +#include "baycom_modem.h" +#include "baycom_kernel.h" +#include "baycom_kiss.h" + +#include + +struct baycom_modem { + hybbx_baycom_backend_t backend; + baycom_kernel_modem_t *kernel; + baycom_kiss_modem_t *kiss; +}; + +hybbx_result_t baycom_modem_open(baycom_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata) +{ + baycom_modem_t *modem; + hybbx_result_t rc; + + if (out == NULL || cfg == NULL) { + return HYBBX_ERR_INVALID; + } + + *out = NULL; + modem = calloc(1, sizeof(*modem)); + if (modem == NULL) { + return HYBBX_ERR_NOMEM; + } + + modem->backend = cfg->backend; + + if (cfg->backend == HYBBX_BAYCOM_BACKEND_KISS) { + rc = baycom_kiss_open(&modem->kiss, cfg, cb, userdata); + } else { + rc = baycom_kernel_open(&modem->kernel, cfg, cb, userdata); + } + + if (rc != HYBBX_OK) { + baycom_modem_close(modem); + return rc; + } + + *out = modem; + return HYBBX_OK; +} + +void baycom_modem_close(baycom_modem_t *modem) +{ + if (modem == NULL) { + return; + } + + if (modem->kernel != NULL) { + baycom_kernel_close(modem->kernel); + } + if (modem->kiss != NULL) { + baycom_kiss_close(modem->kiss); + } + + free(modem); +} + +hybbx_result_t baycom_modem_poll(baycom_modem_t *modem) +{ + if (modem == NULL) { + return HYBBX_ERR_INVALID; + } + + if (modem->kiss != NULL) { + return baycom_kiss_poll(modem->kiss); + } + if (modem->kernel != NULL) { + return baycom_kernel_poll(modem->kernel); + } + + return HYBBX_ERR_IO; +} + +hybbx_result_t baycom_modem_send_frame(baycom_modem_t *modem, + const uint8_t *frame, size_t len) +{ + if (modem == NULL) { + return HYBBX_ERR_INVALID; + } + + if (modem->kiss != NULL) { + return baycom_kiss_send_frame(modem->kiss, frame, len); + } + if (modem->kernel != NULL) { + return baycom_kernel_send_frame(modem->kernel, frame, len); + } + + return HYBBX_ERR_IO; +} diff --git a/plugins/baycom/baycom_modem.h b/plugins/baycom/baycom_modem.h new file mode 100644 index 0000000..2365636 --- /dev/null +++ b/plugins/baycom/baycom_modem.h @@ -0,0 +1,31 @@ +#ifndef BAYCOM_MODEM_H +#define BAYCOM_MODEM_H + +#include "hybbx/baycom.h" +#include "hybbx/types.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*baycom_modem_frame_cb)(const uint8_t *frame, size_t len, + void *userdata); + +typedef struct baycom_modem baycom_modem_t; + +hybbx_result_t baycom_modem_open(baycom_modem_t **out, + const hybbx_baycom_config_t *cfg, + baycom_modem_frame_cb cb, void *userdata); +void baycom_modem_close(baycom_modem_t *modem); +hybbx_result_t baycom_modem_poll(baycom_modem_t *modem); +hybbx_result_t baycom_modem_send_frame(baycom_modem_t *modem, + const uint8_t *frame, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* BAYCOM_MODEM_H */ -- cgit v1.3.1