parent
db1fee9698
commit
7e25e191af
@ -0,0 +1,35 @@ |
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) -fstack-check $(FUSE_CFLAGS)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
datenklo
|
||||
|
||||
datenklo_SOURCES = \
|
||||
am791x.c \
|
||||
uart.c \
|
||||
device.c \
|
||||
datenklo.c \
|
||||
main.c
|
||||
datenklo_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(top_builddir)/src/liboptions/liboptions.a \
|
||||
$(top_builddir)/src/libdebug/libdebug.a \
|
||||
$(top_builddir)/src/libfsk/libfsk.a \
|
||||
$(top_builddir)/src/libtimer/libtimer.a \
|
||||
$(top_builddir)/src/libfm/libfm.a \
|
||||
$(top_builddir)/src/libfilter/libfilter.a \
|
||||
$(top_builddir)/src/libsound/libsound.a \
|
||||
$(top_builddir)/src/libwave/libwave.a \
|
||||
$(top_builddir)/src/libdisplay/libdisplay.a \
|
||||
$(top_builddir)/src/libsample/libsample.a \
|
||||
$(ALSA_LIBS) \
|
||||
$(FUSE_LIBS) \
|
||||
-lm
|
||||
|
||||
if HAVE_ALSA |
||||
AM_CPPFLAGS += -DHAVE_ALSA
|
||||
endif |
||||
|
||||
if HAVE_FUSE |
||||
AM_CPPFLAGS += -DHAVE_FUSE
|
||||
endif |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,107 @@ |
||||
|
||||
enum am791x_type { |
||||
AM791X_TYPE_7910 = 0, |
||||
AM791X_TYPE_7911 = 1, |
||||
}; |
||||
|
||||
enum am791x_st { |
||||
AM791X_STATE_INIT = 0, |
||||
AM791X_STATE_RCON, |
||||
AM791X_STATE_CDON, |
||||
AM791X_STATE_DATA, |
||||
AM791X_STATE_RCOFF, |
||||
AM791X_STATE_CDOFF, |
||||
AM791X_STATE_STO_OFF, |
||||
AM791X_STATE_SQ_OFF, |
||||
AM791X_STATE_BRCON, |
||||
AM791X_STATE_BCDON, |
||||
AM791X_STATE_BDATA, |
||||
AM791X_STATE_BRCOFF, |
||||
AM791X_STATE_BCDOFF, |
||||
}; |
||||
|
||||
typedef struct am791x { |
||||
/* settings */ |
||||
void *inst; /* upper layer instance */ |
||||
enum am791x_type type; |
||||
int samplerate; |
||||
double tx_baud, rx_baud; |
||||
|
||||
/* callbacks */ |
||||
void (*cts_cb)(void *inst, int cts); |
||||
void (*bcts_cb)(void *inst, int cts); |
||||
void (*cd_cb)(void *inst, int cd); |
||||
void (*bcd_cb)(void *inst, int cd); |
||||
int (*td_cb)(void *inst); |
||||
int (*btd_cb)(void *inst); |
||||
void (*rd_cb)(void *inst, int bit, double quality, double level); |
||||
void (*brd_cb)(void *inst, int bit, double quality, double level); |
||||
|
||||
/* modes */ |
||||
uint8_t mc; /* current mode setting */ |
||||
int fullduplex; /* duplex */ |
||||
int loopback_main, loopback_back; /* loopback */ |
||||
int equalizer, sto; /* equalizer & STO */ |
||||
int bell_202; /* is BELL 202 */ |
||||
|
||||
/* states */ |
||||
enum am791x_st tx_state, rx_state; |
||||
int tx_silence; /* no audio transmitted */ |
||||
int tx_sto; /* no STO transmitted */ |
||||
int block_td; /* "TD IGNORED" */ |
||||
int block_rd; /* "RD = MARK" */ |
||||
int line_cd; /* 1 = CD is low */ |
||||
int block_cd; /* "SET CD HIGH" (CD is ignored) */ |
||||
int block_btd; /* "BTD IGNORED" */ |
||||
int block_brd; /* "BRD = MARK" */ |
||||
int line_bcd; /* 1 = BCD is low */ |
||||
int block_bcd; /* "SET BCD HIGH" (BCD is ignored) */ |
||||
int squelch; /* "SQUELCH" (mute received audio) */ |
||||
int line_dtr; /* 1 = DTR is low */ |
||||
int line_rts; /* 1 = RTS is low */ |
||||
int line_brts; /* 1 = BRTS is low */ |
||||
int line_ring; /* 1 = ring is low */ |
||||
|
||||
/* frequencies */ |
||||
int f0_tx, f1_tx; |
||||
int f0_rx, f1_rx; |
||||
|
||||
/* timers */ |
||||
struct timer tx_timer, rx_timer; |
||||
double t_rcon; |
||||
double t_rcoff; |
||||
double t_brcon; |
||||
double t_brcoff; |
||||
double t_cdon; |
||||
double t_cdoff; |
||||
double t_bcdon; |
||||
double t_bcdoff; |
||||
double t_at; |
||||
double t_sil1; |
||||
double t_sil2; |
||||
double t_sq; |
||||
double t_sto; |
||||
|
||||
/* FSK/STO signal */ |
||||
int rx_back_channel; /* indikates if receiver is tuned to back channel */ |
||||
fsk_mod_t fsk_tx; /* FSK modulator */ |
||||
fsk_demod_t fsk_rx; /* FSK demodulator */ |
||||
double tx_level; /* level of TX */ |
||||
double cd_on, cd_off; /* levels for CD */ |
||||
int cd, bcd; /* carrier detected */ |
||||
double sto_phaseshift65536; /* STO tone phase shift */ |
||||
} am791x_t; |
||||
|
||||
void am791x_send(am791x_t *am791x, sample_t *samples, int length); |
||||
void am791x_receive(am791x_t *am791x, sample_t *samples, int length); |
||||
void am791x_list_mc(enum am791x_type type); |
||||
int am791x_init(am791x_t *am791x, void *inst, enum am791x_type type, uint8_t mc, int samplerate, double tx_baud, double rx_baud, void (*cts)(void *inst, int cts), void (*bcts)(void *inst, int cts), void (*cd)(void *inst, int cd), void (*bcd)(void *inst, int cd), int (*td)(void *inst), int (*btd)(void *inst), void (*rd)(void *inst, int bit, double quality, double level), void (*brd)(void *inst, int bit, double quality, double level)); |
||||
void am791x_exit(am791x_t *am791x); |
||||
double am791x_max_baud(uint8_t mc); |
||||
int am791x_mc(am791x_t *am791x, uint8_t mc, int samplerate, double tx_baud, double rx_baud); |
||||
void am791x_reset(am791x_t *am791x); |
||||
void am791x_dtr(am791x_t *am791x, int dtr); |
||||
void am791x_rts(am791x_t *am791x, int rts); |
||||
void am791x_brts(am791x_t *am791x, int brts); |
||||
void am791x_ring(am791x_t *am791x, int ring); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,88 @@ |
||||
|
||||
enum datenklo_auto_mc { |
||||
DATENKLO_AUTO_MC_NONE = 0, |
||||
DATENKLO_AUTO_MC_BELL_ORIGINATE, |
||||
DATENKLO_AUTO_MC_BELL_ANSWER, |
||||
DATENKLO_AUTO_MC_BELL_4WIRE, |
||||
DATENKLO_AUTO_MC_CCITT_ORIGINATE, |
||||
DATENKLO_AUTO_MC_CCITT_ANSWER, |
||||
DATENKLO_AUTO_MC_CCITT_4WIRE, |
||||
}; |
||||
|
||||
typedef struct datenklo { |
||||
struct datenklo *slave; |
||||
|
||||
/* settings */ |
||||
uint8_t mc; /* modem chip mode */ |
||||
int auto_rts; /* automatic RTS controling for half duplex */ |
||||
double max_baud; /* limit to what the mode supports */ |
||||
double force_tx_baud, force_rx_baud; /* override IOCTL */ |
||||
int tx_back, rx_back; /* set if back channel is used for path */ |
||||
int samplerate; /* audio sample rate */ |
||||
int latspl; /* latenc */ |
||||
int loopback; /* loopback mode */ |
||||
|
||||
/* states */ |
||||
int flags; /* open() flags */ |
||||
struct termios termios; /* current termios */ |
||||
double baudrate; /* current baud rate */ |
||||
int lines; /* state of lines (from IOCTL) */ |
||||
int break_on; /* currently sending a break */ |
||||
int break_bits; /* counts bits while sending a break signal */ |
||||
int tcsetsw; /* send new termios after TX buffer is flused */ |
||||
struct termios tcsetsw_termios; /* new termios after TX buffer is flused */ |
||||
int ignbrk; /* IGNBRK option enabled */ |
||||
int parmrk; /* PARMRK option enabled */ |
||||
int istrip; /* ISTRIP option enabled */ |
||||
int inlcr; /* INLCR option enabled */ |
||||
int igncr; /* IGNCR option enabled */ |
||||
int icrnl; /* ICRNL option enabled */ |
||||
int iuclc; /* IUCLC option enabled */ |
||||
int opost; /* OPOST option to enable all post options */ |
||||
int onlcr; /* ONLCR option enabled */ |
||||
int onlcr_char; /* CR transmitted, next up is NL */ |
||||
int ocrnl; /* OCRNL option enabled */ |
||||
int onlret; /* ONLRET option enabled */ |
||||
int olcuc; /* OLCUC option enabled */ |
||||
int echo; /* ECHO option enabled */ |
||||
short revents; /* current set of poll reply events */ |
||||
int open_count; /* to see if device is in use */ |
||||
int auto_rts_on; /* Data available */ |
||||
int auto_rts_rts; /* RTS was raised */ |
||||
int auto_rts_cts; /* CTS was indicated */ |
||||
int auto_rts_cd; /* CD was indicated */ |
||||
int output_off; /* output stopped by flow control */ |
||||
struct timer vtimer; /* VTIME timer */ |
||||
int vtimeout; /* when timeout has fired */ |
||||
|
||||
/* data fifos */ |
||||
uint8_t *tx_fifo; |
||||
int tx_fifo_in, tx_fifo_out; |
||||
int tx_fifo_size; |
||||
int tx_fifo_full; /* watermark to change POLLOUT flag */ |
||||
uint8_t *rx_fifo; |
||||
int rx_fifo_in, rx_fifo_out; |
||||
int rx_fifo_size; |
||||
|
||||
/* instances */ |
||||
am791x_t am791x; /* da great modem IC */ |
||||
uart_t uart; /* soft uart */ |
||||
void *audio; /* sound interface */ |
||||
void *device; /* CUSE device */ |
||||
wave_rec_t wave_rx_rec; /* wave recording (from RX) */ |
||||
wave_rec_t wave_tx_rec; /* wave recording (from TX) */ |
||||
wave_play_t wave_rx_play; /* wave playback (as RX) */ |
||||
wave_play_t wave_tx_play; /* wave playback (as TX) */ |
||||
dispwav_t dispwav; /* wave display */ |
||||
dispmeas_t dispmeas; /* measurements display */ |
||||
dispmeasparam_t *dmp_level; |
||||
dispmeasparam_t *dmp_quality; |
||||
int last_bit; /* to check if we have valid quality */ |
||||
} datenklo_t; |
||||
|
||||
void datenklo_main(datenklo_t *datenklo, int loopback); |
||||
int datenklo_init(datenklo_t *datenklo, const char *dev_name, enum am791x_type am791x_type, uint8_t mc, int auto_rts, double force_tx_baud, double force_rx_baud, int samplerate, int loopback); |
||||
int datenklo_open_audio(datenklo_t *datenklo, const char *audiodev, int latency, const char *write_rx_wave, const char *write_tx_wave, const char *read_rx_wave, const char *read_tx_wave); |
||||
void datenklo_exit(datenklo_t *datenklo); |
||||
void datenklo_init_global(void); |
||||
|
@ -0,0 +1,559 @@ |
||||
/* character device link to libfuse
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#define FUSE_USE_VERSION 30 |
||||
|
||||
#include <cuse_lowlevel.h> |
||||
#include <fuse_opt.h> |
||||
#include <stddef.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include "../libdebug/debug.h" |
||||
#define __USE_GNU |
||||
#include <pthread.h> |
||||
#include <signal.h> |
||||
|
||||
#include "fioc.h" |
||||
#include "device.h" |
||||
|
||||
/* enable to heavily debug poll process */ |
||||
//#define DEBUG_POLL
|
||||
|
||||
typedef struct device { |
||||
struct device *next; |
||||
void *inst; |
||||
pthread_t thread; |
||||
int thread_started, thread_stopped; |
||||
const char *name; |
||||
int major, minor; |
||||
int (*open_cb)(void *inst, int flags); |
||||
void (*close_cb)(void *inst); |
||||
ssize_t (*read_cb)(void *inst, char *buf, size_t size, int flags); |
||||
ssize_t (*write_cb)(void *inst, const char *buf, size_t size, int flags); |
||||
ssize_t (*ioctl_get_cb)(void *inst, int cmd, void *buf, size_t out_bufsz); |
||||
ssize_t (*ioctl_set_cb)(void *inst, int cmd, const void *buf, size_t in_bufsz); |
||||
void (*flush_tx)(void *inst); |
||||
void (*lock_cb)(void); |
||||
void (*unlock_cb)(void); |
||||
short poll_revents; |
||||
struct fuse_pollhandle *poll_handle; |
||||
/* handle read blocking */ |
||||
fuse_req_t read_req; |
||||
size_t read_size; |
||||
int read_flags; |
||||
int read_locked; |
||||
/* handle write blocking */ |
||||
fuse_req_t write_req; |
||||
size_t write_size; |
||||
char *write_buf; |
||||
int write_flags; |
||||
int write_locked; |
||||
} device_t; |
||||
|
||||
static device_t *device_list = NULL; |
||||
|
||||
static device_t *get_device_by_thread(void) |
||||
{ |
||||
device_t *device = device_list; |
||||
pthread_t thread = pthread_self(); |
||||
|
||||
while (device) { |
||||
if (device->thread == thread) |
||||
return device; |
||||
device = device->next; |
||||
} |
||||
|
||||
fprintf(stderr, "Our thread is unknown, please fix!\n"); |
||||
abort(); |
||||
} |
||||
|
||||
static void cuse_device_open(fuse_req_t req, struct fuse_file_info *fi) |
||||
{ |
||||
(void)fi; |
||||
int rc; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
device->lock_cb(); |
||||
rc = device->open_cb(device->inst, fi->flags); |
||||
device->unlock_cb(); |
||||
|
||||
if (rc < 0) |
||||
fuse_reply_err(req, -rc); |
||||
else |
||||
fuse_reply_open(req, fi); |
||||
} |
||||
|
||||
static void cuse_device_release(fuse_req_t req, struct fuse_file_info *fi) |
||||
{ |
||||
(void)fi; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
device->lock_cb(); |
||||
device->close_cb(device->inst); |
||||
device->unlock_cb(); |
||||
|
||||
fuse_reply_err(req, 0); |
||||
} |
||||
|
||||
static void cuse_read_interrupt(fuse_req_t req, void *data) |
||||
{ |
||||
(void)req; |
||||
device_t *device = (device_t *)data; |
||||
|
||||
if (!device->read_locked) |
||||
device->lock_cb(); |
||||
|
||||
PDEBUG(DDEVICE, DEBUG_DEBUG, "%s received interrupt from client!\n", device->name); |
||||
|
||||
if (device->read_req) { |
||||
device->read_req = NULL; |
||||
fuse_reply_err(req, EINTR); |
||||
} |
||||
|
||||
if (!device->read_locked) |
||||
device->unlock_cb(); |
||||
} |
||||
|
||||
void device_read_available(void *inst) |
||||
{ |
||||
device_t *device = (device_t *)inst; |
||||
ssize_t count; |
||||
|
||||
// we are locked by caller
|
||||
|
||||
/* if enough data or if buffer is full */ |
||||
if (device->read_req) { |
||||
char buf[device->read_size]; |
||||
count = device->read_cb(device->inst, buf, device->read_size, device->read_flags); |
||||
/* still blocking, waiting for more... */ |
||||
if (count == -EAGAIN) |
||||
return; |
||||
fuse_reply_buf(device->read_req, buf, count); |
||||
device->read_req = NULL; |
||||
} |
||||
} |
||||
|
||||
static void cuse_device_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_info *fi) |
||||
{ |
||||
(void)off; |
||||
(void)fi; |
||||
ssize_t count; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
if (size > 65536) |
||||
size = 65536; |
||||
char buf[size]; |
||||
|
||||
device->lock_cb(); |
||||
|
||||
if (device->read_req) { |
||||
device->unlock_cb(); |
||||
PDEBUG(DDEVICE, DEBUG_ERROR, "%s: Got another read(), while first read() has not been replied, please fix.\n", device->name); |
||||
fuse_reply_err(req, EBUSY); |
||||
return; |
||||
} |
||||
|
||||
#ifdef DEBUG_POLL |
||||
puts("read: before fn"); |
||||
#endif |
||||
count = device->read_cb(device->inst, buf, size, fi->flags); |
||||
#ifdef DEBUG_POLL |
||||
puts("read: after fn"); |
||||
#endif |
||||
|
||||
/* this means that we block until modem's read() returns 0 or positive value (in nonblocking io, we return -EAGAIN) */ |
||||
if (!(fi->flags & O_NONBLOCK) && count == -EAGAIN) { |
||||
PDEBUG(DDEVICE, DEBUG_DEBUG, "%s has no data available, waiting for data, timer or interrupt.\n", device->name); |
||||
|
||||
device->read_req = req; |
||||
device->read_size = size; |
||||
device->read_flags = fi->flags; |
||||
/* to prevent race condition, tell cuse_write_interrupt that we are already locked.
|
||||
* (interrupt may have come before and will be processed by fuse_req_interrupt_func()) |
||||
*/ |
||||
device->read_locked = 1; |
||||
fuse_req_interrupt_func(req, cuse_read_interrupt, device); |
||||
device->read_locked = 0; |
||||
device->unlock_cb(); |
||||
return; |
||||
} |
||||
|
||||
device->unlock_cb(); |
||||
|
||||
if (count < 0) |
||||
fuse_reply_err(req, -count); |
||||
else |
||||
fuse_reply_buf(req, buf, count); |
||||
#ifdef DEBUG_POLL |
||||
puts("read: after reply"); |
||||
#endif |
||||
} |
||||
|
||||
static void cuse_write_interrupt(fuse_req_t req, void *data) |
||||
{ |
||||
(void)req; |
||||
device_t *device = (device_t *)data; |
||||
|
||||
if (!device->write_locked) |
||||
device->lock_cb(); |
||||
|
||||
PDEBUG(DDEVICE, DEBUG_DEBUG, "%s received interrupt from client!\n", device->name); |
||||
|
||||
if (device->write_req) { |
||||
device->write_req = NULL; |
||||
free(device->write_buf); |
||||
device->write_buf = NULL; |
||||
/* flushing TX buffer */ |
||||
device->flush_tx(device->inst); |
||||
fuse_reply_err(req, EINTR); |
||||
} |
||||
|
||||
if (!device->write_locked) |
||||
device->unlock_cb(); |
||||
} |
||||
|
||||
void device_write_available(void *inst) |
||||
{ |
||||
device_t *device = (device_t *)inst; |
||||
ssize_t count; |
||||
|
||||
// we are locked by caller
|
||||
|
||||
/* if enough space or buffer empty */ |
||||
if (device->write_req) { |
||||
count = device->write_cb(device->inst, device->write_buf, device->write_size, device->write_flags); |
||||
/* still blocking, waiting for more... */ |
||||
if (count == -EAGAIN) |
||||
return; |
||||
fuse_reply_write(device->write_req, count); |
||||
device->write_req = NULL; |
||||
free(device->write_buf); |
||||
device->write_buf = NULL; |
||||
} |
||||
} |
||||
|
||||
static void cuse_device_write(fuse_req_t req, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) |
||||
{ |
||||
(void)off; |
||||
(void)fi; |
||||
ssize_t count; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
device->lock_cb(); |
||||
|
||||
if (device->write_req) { |
||||
device->unlock_cb(); |
||||
PDEBUG(DDEVICE, DEBUG_ERROR, "%s: Got another write(), while first write() has not been replied, please fix.\n", device->name); |
||||
fuse_reply_err(req, EBUSY); |
||||
return; |
||||
} |
||||
|
||||
count = device->write_cb(device->inst, buf, size, fi->flags); |
||||
|
||||
/* this means that we block until modem's write() returns 0 or positive value (in nonblocking io, we return -EAGAIN) */ |
||||
if (!(fi->flags & O_NONBLOCK) && count == -EAGAIN) { |
||||
PDEBUG(DDEVICE, DEBUG_DEBUG, "%s has no buffer space available, waiting for space or interrupt.\n", device->name); |
||||
|
||||
device->write_req = req; |
||||
device->write_size = size; |
||||
device->write_buf = malloc(size); |
||||
if (!buf) { |
||||
PDEBUG(DDEVICE, DEBUG_ERROR, "No memory!\n"); |
||||
exit(0); |
||||
} |
||||
memcpy(device->write_buf, buf, size); |
||||
device->write_flags = fi->flags; |
||||
/* to prevent race condition, tell cuse_write_interrupt that we are already locked.
|
||||
* (interrupt may have come before and will be processed by fuse_req_interrupt_func()) |
||||
*/ |
||||
device->write_locked = 1; |
||||
fuse_req_interrupt_func(req, cuse_write_interrupt, device); |
||||
device->write_locked = 0; |
||||
device->unlock_cb(); |
||||
return; |
||||
} |
||||
|
||||
device->unlock_cb(); |
||||
|
||||
if (count < 0) |
||||
fuse_reply_err(req, -count); |
||||
else |
||||
fuse_reply_write(req, count); |
||||
} |
||||
|
||||
static void cuse_device_ioctl(fuse_req_t req, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz) |
||||
{ |
||||
(void)fi; |
||||
ssize_t rc; |
||||
char out_buf[out_bufsz]; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
if (flags & FUSE_IOCTL_COMPAT) { |
||||
fuse_reply_err(req, ENOSYS); |
||||
return; |
||||
} |
||||
|
||||
switch (cmd) { |
||||
case TCGETS: |
||||
case TIOCMGET: |
||||
case TIOCGWINSZ: |
||||
case FIONREAD: |
||||
case TIOCOUTQ: |
||||
device->lock_cb(); |
||||
rc = device->ioctl_get_cb(device->inst, cmd, out_buf, out_bufsz); |
||||
device->unlock_cb(); |
||||
if (rc < 0) { |
||||
fuse_reply_err(req, -rc); |
||||
break; |
||||
} |
||||
if (rc == 0) { |
||||
// do we need this ?
|
||||
fuse_reply_ioctl(req, 0, NULL, 0); |
||||
break; |
||||
} |
||||
if (!out_bufsz) { |
||||
struct iovec iov = { arg, rc }; |
||||
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); |
||||
} else { |
||||
fuse_reply_ioctl(req, 0, out_buf, rc); |
||||
} |
||||
break; |
||||
case TCSETS: |
||||
case TCSETSW: |
||||
case TCSETSF: |
||||
case TIOCMBIS: |
||||
case TIOCMBIC: |
||||
case TIOCMSET: |
||||
case TCFLSH: |
||||
case TCSBRK: |
||||
case TCSBRKP: |
||||
case TIOCSBRK: |
||||
case TIOCCBRK: |
||||
case TIOCGSID: |
||||
case TIOCGPGRP: |
||||
case TIOCSCTTY: |
||||
case TIOCSPGRP: |
||||
case TIOCSWINSZ: |
||||
case TCXONC: |
||||
device->lock_cb(); |
||||
rc = device->ioctl_set_cb(device->inst, cmd, in_buf, in_bufsz); |
||||
device->unlock_cb(); |
||||
if (rc < 0) { |
||||
fuse_reply_err(req, -rc); |
||||
break; |
||||
} |
||||
if (rc == 0) { |
||||
/* empty control is replied */ |
||||
fuse_reply_ioctl(req, 0, NULL, 0); |
||||
break; |
||||
} |
||||
if (!in_bufsz) { |
||||
struct iovec iov = { arg, rc }; |
||||
|
||||
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); |
||||
} else { |
||||
fuse_reply_ioctl(req, 0, NULL, 0); |
||||
} |
||||
break; |
||||
default: |
||||
PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: receives unknown ioctl: 0x%x\n", device->name, cmd); |
||||
fuse_reply_err(req, EINVAL); |
||||
} |
||||
} |
||||
|
||||
void device_set_poll_events(void *inst, short revents) |
||||
{ |
||||
device_t *device = (device_t *)inst; |
||||
|
||||
// we are locked by caller
|
||||
|
||||
if (revents == device->poll_revents) |
||||
return; |
||||
|
||||
#ifdef DEBUG_POLL |
||||
printf("new revents 0x%x\n", revents); |
||||
#endif |
||||
device->poll_revents = revents; |
||||
if (device->poll_handle) { |
||||
#ifdef DEBUG_POLL |
||||
printf("notify with handle %p\n", device->poll_handle); |
||||
#endif |
||||
fuse_lowlevel_notify_poll(device->poll_handle); |
||||
} |
||||
} |
||||
|
||||
static void cuse_device_poll(fuse_req_t req, struct fuse_file_info *fi, struct fuse_pollhandle *ph) |
||||
{ |
||||
(void)fi; |
||||
device_t *device = get_device_by_thread(); |
||||
|
||||
#ifdef DEBUG_POLL |
||||
printf("poll %p %p\n", ph, device->poll_handle); |
||||
#endif |
||||
if (ph) { |
||||
device->lock_cb(); |
||||
if (device->poll_handle) |
||||
fuse_pollhandle_destroy(device->poll_handle); |
||||
device->poll_handle = ph; |
||||
#ifdef DEBUG_POLL |
||||
printf("storing %p\n", device->poll_handle); |
||||
#endif |
||||
device->unlock_cb(); |
||||
} |
||||
|
||||
#ifdef DEBUG_POLL |
||||
printf("sending revents 0x%x\n", device->poll_revents); |
||||
#endif |
||||
fuse_reply_poll(req, device->poll_revents); |
||||
} |
||||
|
||||
static void cuse_device_flush(fuse_req_t req, struct fuse_file_info *fi) |
||||
{ |
||||
(void)req; |
||||
(void)fi; |
||||
device_t *device = get_device_by_thread(); |
||||
PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: unhandled flush\n", device->name); |
||||
} |
||||
|
||||
static void cuse_device_fsync(fuse_req_t req, int datasync, struct fuse_file_info *fi) |
||||
{ |
||||
(void)req; |
||||
(void)datasync; |
||||
(void)fi; |
||||
device_t *device = get_device_by_thread(); |
||||
PDEBUG(DDEVICE, DEBUG_NOTICE, "%s: unhandled fsync\n", device->name); |
||||
} |
||||
|
||||
|
||||
static const struct cuse_lowlevel_ops cuse_device_clop = { |
||||
.open = cuse_device_open, |
||||
.release = cuse_device_release, |
||||
.read = cuse_device_read, |
||||
.write = cuse_device_write, |
||||
.ioctl = cuse_device_ioctl, |
||||
.poll = cuse_device_poll, |
||||
.fsync = cuse_device_fsync, |
||||
.flush = cuse_device_flush, |
||||
}; |
||||
|
||||
static void *device_child(void *arg) |
||||
{ |
||||
device_t *device = (device_t *)arg; |
||||
|
||||
int argc = 3; |
||||
/* use -f to run without debug, but -d to debug */ |
||||
char *argv[3] = { "datenklo", "-f", "-s" }; |
||||
char dev_name[128] = "DEVNAME="; |
||||
const char *dev_info_argv[] = { dev_name }; |
||||
struct cuse_info ci; |
||||
|
||||
strncat(dev_name, device->name, sizeof(dev_name) - strlen(device->name) - 1); |
||||
|
||||
memset(&ci, 0, sizeof(ci)); |
||||
ci.dev_major = device->major; |
||||
ci.dev_minor = device->minor; |
||||
ci.dev_info_argc = 1; |
||||
ci.dev_info_argv = dev_info_argv; |
||||
ci.flags = CUSE_UNRESTRICTED_IOCTL; |
||||
|
||||
device->thread_started = 1; |
||||
|
||||
PDEBUG(DDEVICE, DEBUG_INFO, "Device '%s' started.\n", device->name); |
||||
cuse_lowlevel_main(argc, argv, &ci, &cuse_device_clop, NULL); |
||||
PDEBUG(DDEVICE, DEBUG_INFO, "Device '%s' terminated.\n", device->name); |
||||
|
||||
device->thread_stopped = 1; |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
void *device_init(void *inst, const char *name, int (*open)(void *inst, int flags), void (*close)(void *inst), ssize_t (*read)(void *inst, char *buf, size_t size, int flags), ssize_t (*write)(void *inst, const char *buf, size_t size, int flags), ssize_t ioctl_get(void *inst, int cmd, void *buf, size_t out_bufsz), ssize_t ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufsz), void (*flush_tx)(void *inst), void (*lock)(void), void (*unlock)(void)) |
||||
{ |
||||
int rc = -EINVAL; |
||||
char tname[64]; |
||||
device_t *device = NULL; |
||||
device_t **devicep; |
||||
|
||||
device = calloc(1, sizeof(*device)); |
||||
if (!device) { |
||||
PDEBUG(DDEVICE, DEBUG_ERROR, "No memory!\n"); |
||||
errno = ENOMEM; |
||||
goto error; |
||||
} |
||||
device->inst = inst; |
||||
device->name = name; |
||||
device->open_cb = open; |
||||
device->close_cb = close; |
||||
device->read_cb = read; |
||||
device->write_cb = write; |
||||
device->ioctl_get_cb = ioctl_get; |
||||
device->ioctl_set_cb = ioctl_set; |
||||
device->flush_tx = flush_tx; |
||||
device->lock_cb = lock; |
||||
device->unlock_cb = unlock; |
||||
|
||||
rc = pthread_create(&device->thread, NULL, device_child, device); |
||||
if (rc < 0) { |
||||
PDEBUG(DDEVICE, DEBUG_ERROR, "Failed to create device thread!\n"); |
||||
errno = -rc; |
||||
goto error; |
||||
} |
||||
|
||||
pthread_getname_np(device->thread, tname, sizeof(tname)); |
||||
strncat(tname, "-device", sizeof(tname) - 7 - 1); |
||||
tname[sizeof(tname) - 1] = '\0'; |
||||
pthread_setname_np(device->thread, tname); |
||||
|
||||
while (!device->thread_started) |
||||
usleep(100); |
||||
|
||||
/* attach to list */ |
||||
devicep = &device_list; |
||||
while (*devicep) |
||||
devicep = &((*devicep)->next); |
||||
*devicep = device; |
||||
|
||||
return device; |
||||
|
||||
error: |
||||
device_exit(device); |
||||
return NULL; |
||||
} |
||||
|
||||
void device_exit(void *inst) |
||||
{ |
||||
device_t *device = (device_t *)inst; |
||||
device_t **devicep; |
||||
|
||||
/* detach from list */ |
||||
devicep = &device_list; |
||||
while (*devicep && *devicep != device) |
||||
devicep = &((*devicep)->next); |
||||
if (*devicep) |
||||
*devicep = device->next; |
||||
|
||||
/* the device-thread is terminated when the program terminates, so no kill required (REALLY????) */ |
||||
|
||||
free(device); |
||||
} |
||||
|
@ -0,0 +1,7 @@ |
||||
|
||||
void *device_init(void *inst, const char *name, int (*open)(void *inst, int flags), void (*close)(void *inst), ssize_t (*read)(void *inst, char *buf, size_t size, int flags), ssize_t (*write)(void *inst, const char *buf, size_t size, int flags), ssize_t ioctl_get(void *inst, int cmd, void *buf, size_t out_bufsz), ssize_t ioctl_set(void *inst, int cmd, const void *buf, size_t in_bufsz), void (*flush_tx)(void *inst), void (*lock)(void), void (*unlock)(void)); |
||||
void device_exit(void *inst); |
||||
void device_set_poll_events(void *inst, short revents); |
||||
void device_read_available(void *inst); |
||||
void device_write_available(void *inst); |
||||
|
@ -0,0 +1,32 @@ |
||||
/*
|
||||
FUSE-ioctl: ioctl support for FUSE |
||||
Copyright (C) 2008 SUSE Linux Products GmbH |
||||
Copyright (C) 2008 Tejun Heo <teheo@suse.de> |
||||
|
||||
This program can be distributed under the terms of the GNU GPL. |
||||
See the file COPYING. |
||||
*/ |
||||
|
||||
#include <sys/types.h> |
||||
#include <sys/uio.h> |
||||
#include <sys/ioctl.h> |
||||
|
||||
enum { |
||||
FIOC_GET_SIZE = _IOR('E', 0, size_t), |
||||
FIOC_SET_SIZE = _IOW('E', 1, size_t), |
||||
|
||||
/*
|
||||
* The following two ioctls don't follow usual encoding rules |
||||
* and transfer variable amount of data. |
||||
*/ |
||||
FIOC_READ = _IO('E', 2), |
||||
FIOC_WRITE = _IO('E', 3), |
||||
}; |
||||
|
||||
struct fioc_rw_arg { |
||||
off_t offset; |
||||
void *buf; |
||||
size_t size; |
||||
size_t prev_size; /* out param for previous total size */ |
||||
size_t new_size; /* out param for new total size */ |
||||
}; |
@ -0,0 +1,320 @@ |
||||
/* osmo datenklo main file
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <termios.h> |
||||
#include <unistd.h> |
||||
#include <errno.h> |
||||
#include <math.h> |
||||
#include "../libsample/sample.h" |
||||
#include "../libtimer/timer.h" |
||||
#include "../liboptions/options.h" |
||||
#include "../libdebug/debug.h" |
||||
#include "../libfsk/fsk.h" |
||||
#include "../libwave/wave.h" |
||||
#include "../libdisplay/display.h" |
||||
#include "am791x.h" |
||||
#include "uart.h" |
||||
#include "datenklo.h" |
||||
|
||||
#define MAX_DEVICES 2 |
||||
|
||||
#define OPT_ARRAY(num_name, name, value) \ |
||||
{ \
|
||||
if (num_name == MAX_DEVICES) { \
|
||||
fprintf(stderr, "Too many devices defined!\n"); \
|
||||
exit(0); \
|
||||
} \
|
||||
name[num_name++] = value; \
|
||||
} |
||||
|
||||
/* dummy functions */ |
||||
int num_kanal = 1; /* only one channel used for debugging */ |
||||
void *get_sender_by_empfangsfrequenz() { return "void"; } |
||||
|
||||
static datenklo_t datenklo[MAX_DEVICES]; |
||||
static enum am791x_type am791x_type = AM791X_TYPE_7911; |
||||
static int num_mc = 0; |
||||
static uint8_t mc[MAX_DEVICES] = { 0 }; |
||||
static int auto_rts = 0; |
||||
static int num_tx_baudrate = 0, num_rx_baudrate = 0; |
||||
static int tx_baudrate[MAX_DEVICES] = { 0 }, rx_baudrate[MAX_DEVICES] = { 0 }; |
||||
static int num_ttydev = 0; |
||||
static const char *ttydev[MAX_DEVICES] = { "/dev/ttyDATENKLO0" }; |
||||
static const char *audiodev = "hw:0,0"; |
||||
static int samplerate = 48000; |
||||
static int latency = 50; |
||||
static int stereo = 0; |
||||
static int loopback = 0; |
||||
static int fast_math = 0; |
||||
const char *write_tx_wave = NULL; |
||||
const char *write_rx_wave = NULL; |
||||
const char *read_tx_wave = NULL; |
||||
const char *read_rx_wave = NULL; |
||||
|
||||
void print_help(const char *arg0) |
||||
{ |
||||
printf("Usage: %s [options] -M <mode>\n\n", arg0); |
||||
/* - - */ |
||||
printf(" -T --am791x-type 7910 | 7911\n"); |
||||
printf(" Give modem chip type. (Default = 791%d)\n", am791x_type); |
||||
printf(" -M --mc <mode>\n"); |
||||
printf(" Give mode setting of AM7910/AM71911, use 'list' to list all modes.\n"); |
||||
printf(" -A --auto-rts\n"); |
||||
printf(" Automatically rais and drop modem's RTS line for half duplex operation.\n"); |
||||
printf(" TX data will be queued while remote carrier is detected.\n"); |
||||
printf(" -B --baudrate <RX rate> <TX rate>\n"); |
||||
printf(" Given baud rate will override the baud rate given by ioctl.\n"); |
||||
printf(" A baud rate of <= 150 Bps for TX and/or RX will attach TX and/or RX to\n"); |
||||
printf(" the back channel instead of the main channel.\n"); |
||||
printf(" -D --device\n"); |
||||
printf(" Device name. The prefix '/dev/ is automatically added, if not given.\n"); |
||||
printf(" (default = '%s')\n", ttydev[0]); |
||||
printf(" -S --stereo\n"); |
||||
printf(" Generate two devices. One device connects to the left and the other to\n"); |
||||
printf(" the right channel of the audio device. The device number in the device\n"); |
||||
printf(" name is automatically increased by one. You must also define the mode\n"); |
||||
printf(" twice. (-M <mode> -M <mode>)\n"); |
||||
printf(" -a --audio-device hw:<card>,<device>\n"); |
||||
printf(" Sound card and device number (default = '%s')\n", audiodev); |
||||
printf(" -s --samplerate <rate>\n"); |
||||
printf(" Sample rate of sound device (default = '%d')\n", samplerate); |
||||
printf(" -b --buffer <ms>\n"); |
||||
printf(" How many milliseconds are processed in advance (default = '%d')\n", latency); |
||||
printf(" -l --loopback <type>\n"); |
||||
printf(" Perform audio loopback to test modem.\n"); |
||||
printf(" type 1: Audio from transmitter is fed into receiver (analog loopback)\n"); |
||||
printf(" type 2: Audio is crossed between two modem instances. (use with -S)\n"); |
||||
printf(" --fast-math\n"); |
||||
printf(" Use fast math approximation for slow CPU / ARM based systems.\n"); |
||||
printf(" --write-rx-wave <file>\n"); |
||||
printf(" Write received audio to given wave file.\n"); |
||||
printf(" --write-tx-wave <file>\n"); |
||||
printf(" Write transmitted audio to given wave file.\n"); |
||||
printf(" --read-rx-wave <file>\n"); |
||||
printf(" Replace received audio by given wave file.\n"); |
||||
printf(" --read-tx-wave <file>\n"); |
||||
printf(" Replace transmitted audio by given wave file.\n"); |
||||
} |
||||
|
||||
#define OPT_WRITE_RX_WAVE 1001 |
||||
#define OPT_WRITE_TX_WAVE 1002 |
||||
#define OPT_READ_RX_WAVE 1003 |
||||
#define OPT_READ_TX_WAVE 1004 |
||||
#define OPT_MNCC_NAME 1006 |
||||
#define OPT_FAST_MATH 1007 |
||||
|
||||
static void add_options(void) |
||||
{ |
||||
option_add('h', "help", 0); |
||||
option_add('v', "debug", 1); |
||||
option_add('T', "am791x_type", 1); |
||||
option_add('M', "mc", 1); |
||||
option_add('A', "auto-rts", 0); |
||||
option_add('B', "baudrate", 2); |
||||
option_add('D', "device", 1); |
||||
option_add('S', "stereo", 0); |
||||
option_add('a', "audio-device", 1); |
||||
option_add('s', "samplerate", 1); |
||||
option_add('b', "buffer", 1); |
||||
option_add('l', "loopback", 1); |
||||
option_add(OPT_WRITE_RX_WAVE, "write-rx-wave", 1); |
||||
option_add(OPT_WRITE_TX_WAVE, "write-tx-wave", 1); |
||||
option_add(OPT_READ_RX_WAVE, "read-rx-wave", 1); |
||||
option_add(OPT_READ_TX_WAVE, "read-tx-wave", 1); |
||||
option_add(OPT_FAST_MATH, "fast-math", 0); |
||||
} |
||||
|
||||
static int handle_options(int short_option, int argi, char **argv) |
||||
{ |
||||
int rc; |
||||
|
||||
switch (short_option) { |
||||
case 'h': |
||||
print_help(argv[0]); |
||||
return 0; |
||||
case 'v': |
||||
if (!strcasecmp(argv[argi], "list")) { |
||||
debug_list_cat(); |
||||
return 0; |
||||
} |
||||
rc = parse_debug_opt(argv[argi]); |
||||
if (rc < 0) { |
||||
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n"); |
||||
return rc; |
||||
} |
||||
break; |
||||
case 'T': |
||||
am791x_type = atoi(argv[argi]); |
||||
if (am791x_type < 0 || am791x_type > 1) { |
||||
fprintf(stderr, "Given type parameter '%s' is invalid, use '-h' for help!\n", argv[argi]); |
||||
return -EINVAL; |
||||
} |
||||
break; |
||||
case 'M': |
||||
if (!strcasecmp(argv[argi], "list")) { |
||||
am791x_list_mc(am791x_type); |
||||
return 0; |
||||
} else |
||||
if (argv[argi][0] >= '0' && argv[argi][0] <= '9') { |
||||
OPT_ARRAY(num_mc, mc, atoi(argv[argi])) |
||||
if (mc[num_mc - 1] > 31) |
||||
goto mc_inval; |
||||
} else |
||||
{ |
||||
mc_inval: |
||||
fprintf(stderr, "Given mode parameter '%s' is invalid, use '-h' for help!\n", argv[argi]); |
||||
return -EINVAL; |
||||
} |
||||
break; |
||||
case 'A': |
||||
auto_rts = 1; |
||||
break; |
||||
case 'B': |
||||
OPT_ARRAY(num_rx_baudrate, rx_baudrate, atoi(argv[argi])) |
||||
OPT_ARRAY(num_tx_baudrate, tx_baudrate, atoi(argv[argi + 1])) |
||||
break; |
||||
case 'D': |
||||
OPT_ARRAY(num_ttydev, ttydev, strdup(argv[argi])) |
||||
break; |
||||
case 'S': |
||||
stereo = 1; |
||||
break; |
||||
case 'a': |
||||
audiodev = strdup(argv[argi]); |
||||
break; |
||||
case 's': |
||||
samplerate = atoi(argv[argi]); |
||||
break; |
||||
case 'b': |
||||
latency = atoi(argv[argi]); |
||||
break; |
||||
case 'l': |
||||
loopback = atoi(argv[argi]); |
||||
break; |
||||
case OPT_FAST_MATH: |
||||
fast_math = 1; |
||||
break; |
||||
case OPT_WRITE_RX_WAVE: |
||||
write_rx_wave = strdup(argv[argi]); |
||||
break; |
||||
case OPT_WRITE_TX_WAVE: |
||||
write_tx_wave = strdup(argv[argi]); |
||||
break; |
||||
case OPT_READ_RX_WAVE: |
||||
read_rx_wave = strdup(argv[argi]); |
||||
break; |
||||
case OPT_READ_TX_WAVE: |
||||
read_tx_wave = strdup(argv[argi]); |
||||
break; |
||||
} |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
const char *inc_dev_name(const char *dev_name) |
||||
{ |
||||
char *new_name, *number; |
||||
int integer; |
||||
|
||||
/* clone */ |
||||
new_name = malloc(256); |
||||
strcpy(new_name, dev_name); |
||||
|
||||
/* find number and remove, if any */ |
||||
number = new_name; |
||||
while(*number < '0' || *number > '9') |
||||
number++; |
||||
if (!(*number)) |
||||
integer = 2; |
||||
else |
||||
integer = atoi(number) + 1; |
||||
|
||||
/* change number */ |
||||
sprintf(number, "%d", integer); |
||||
|
||||
return new_name; |
||||
} |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
int rc, argi; |
||||
int i; |
||||
|
||||
/* handle options / config file */ |
||||
add_options(); |
||||
rc = options_config_file("~/.osmocom/analog/datenklo.conf", handle_options); |
||||
if (rc < 0) |
||||
return 0; |
||||
argi = options_command_line(argc, argv, handle_options); |
||||
if (argi <= 0) |
||||
return argi; |
||||
|
||||
/* inits */ |
||||
datenklo_init_global(); |
||||
fm_init(fast_math); |
||||
|
||||
if (stereo) { |
||||
num_kanal = 2; |
||||
} |
||||
if (num_mc == 0) { |
||||
fprintf(stderr, "You need to set the mode of the modem chip. See '--help'.\n"); |
||||
exit(0); |
||||
} |
||||
if (num_mc < num_kanal) { |
||||
fprintf(stderr, "You need to specify as many mode settings as you have channels.\n"); |
||||
exit(0); |
||||
} |
||||
|
||||
/* create modem instance */ |
||||
for (i = 0; i < num_kanal; i++) { |
||||
/* remove /dev/ */ |
||||
if (ttydev[i] && !strncmp(ttydev[i], "/dev/", 5)) |
||||
ttydev[i] += 5; |
||||
/* increment last name */ |
||||
if (i && ttydev[i] == NULL) |
||||
ttydev[i] = inc_dev_name(ttydev[i - 1]); |
||||
rc = datenklo_init(&datenklo[i], ttydev[i], am791x_type, mc[i], auto_rts, tx_baudrate[i], rx_baudrate[i], samplerate, loopback); |
||||
if (rc < 0) { |
||||
fprintf(stderr, "Failed to create \"Datenklo\" instance. Quitting!\n"); |
||||
goto fail; |
||||
} |
||||
if (i) |
||||
datenklo[i - 1].slave = &datenklo[i]; |
||||
printf("Datenklo on device '/dev/%s' ready. (using sound device '%s')\n", ttydev[i], audiodev); |
||||
} |
||||
|
||||
rc = datenklo_open_audio(&datenklo[0], audiodev, latency, write_rx_wave, write_tx_wave, read_rx_wave, read_tx_wave); |
||||
if (rc < 0) { |
||||
fprintf(stderr, "Failed to initialize audio. Quitting!\n"); |
||||
goto fail; |
||||
} |
||||
|
||||
datenklo_main(&datenklo[0], loopback); |
||||
|
||||
fail: |
||||
for (i = 0; i < num_kanal; i++) |
||||
datenklo_exit(&datenklo[i]); |
||||
|
||||
return 0; |
||||
} |
||||
|
@ -0,0 +1,166 @@ |
||||
/* Software UART
|
||||
* |
||||
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu> |
||||
* All Rights Reserved |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include "../libdebug/debug.h" |
||||
#include "uart.h" |
||||
|
||||
static uint32_t calc_parity(uint32_t data, uint8_t data_bits, enum uart_parity parity) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < data_bits; i++) |
||||
parity |= (data >> i); |
||||
parity &= 1; |
||||
|
||||
switch (parity) { |
||||
case UART_PARITY_NONE: |
||||
case UART_PARITY_SPACE: |
||||
return 0; |
||||
case UART_PARITY_MARK: |
||||
return 1; |
||||
case UART_PARITY_EVEN: |
||||
return parity; |
||||
case UART_PARITY_ODD: |
||||
return parity ^ 1; |
||||
} |
||||
|
||||
return 0; /* never reached */ |
||||
} |
||||
|
||||
int uart_init(uart_t *uart, void *inst, uint8_t data_bits, enum uart_parity parity, uint8_t stop_bits, int (*tx_cb)(void *inst), void (*rx_cb)(void *inst, int data, uint32_t flags)) |
||||
{ |
||||
memset(uart, 0, sizeof(*uart)); |
||||
|
||||
uart->inst = inst; |
||||
uart->tx_cb = tx_cb; |
||||
uart->rx_cb = rx_cb; |
||||
uart->data_bits = data_bits; |
||||
if (uart->data_bits > 9) { |
||||
PDEBUG(DUART, DEBUG_ERROR, "Illegal number of data bits, please fix!\n"); |
||||
abort(); |
||||
} |
||||
uart->parity = parity; |
||||
uart->stop_bits = stop_bits; |
||||
if (uart->stop_bits < 1 || uart->stop_bits > 2) { |
||||
PDEBUG(DUART, DEBUG_ERROR, "Illegal number of stop bits, please fix!\n"); |
||||
abort(); |
||||
} |
||||
uart->tx_pos = -1; |
||||
uart->rx_pos = -1; |
||||
uart->length = uart->stop_bits + !!uart->parity + uart->data_bits; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/* called by modulator to get next bit from uart */ |
||||
int uart_tx_bit(uart_t *uart) |
||||
{ |
||||
uint32_t bit, parity; |
||||
|
||||
if (uart->tx_pos < 0) { |
||||
/* no transmission, get data */ |
||||
uart->tx_data = uart->tx_cb(uart->inst); |
||||
/* return 1, if no data has not be sent */ |
||||
if (uart->tx_data > 0x7fffffff) |
||||
return 1; |
||||
/* all bits after data are stop bits */ |
||||
uart->tx_data |= 0xffffffff << uart->data_bits; |
||||
/* calculate parity */ |
||||
if (uart->parity) |
||||
parity = calc_parity(uart->tx_data, uart->data_bits, uart->parity); |
||||
/* add parity bit */ |
||||
if (uart->parity) { |
||||
/* erase bit for parity */ |
||||
uart->tx_data ^= 1 << uart->data_bits; |
||||
/* put parity bit */ |
||||
uart->tx_data |= parity << uart->data_bits; |
||||
} |
||||
/* start with the first bit */ |
||||
uart->tx_pos = 0; |
||||
/* return start bit */ |
||||
return 0; |
||||
} |
||||
/* get bit to be send */ |
||||
bit = (uart->tx_data >> uart->tx_pos) & 1; |
||||
/* go to next bit and set tx_pos to -1, if there is no more bit */ |
||||
if (++uart->tx_pos == uart->length) |
||||
uart->tx_pos = -1; |
||||
/* return bit */ |
||||
return bit; |
||||
} |
||||
|
||||
int uart_is_tx(uart_t *uart) |
||||
{ |
||||
if (uart->tx_pos >= 0) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
/* called by demodulator to indicate bit for uart */ |
||||
void uart_rx_bit(uart_t *uart, int bit) |
||||
{ |
||||
uint32_t flags = 0; |
||||
uint32_t parity; |
||||
|
||||
bit &= 1; |