Modem emulator for "Datenklo" with AM7910/AM7911 emulation
This commit is contained in:
parent
db1fee9698
commit
7e25e191af
|
@ -65,6 +65,7 @@ src/imts/imts-dialer
|
||||||
src/jolly/jollycom
|
src/jolly/jollycom
|
||||||
src/tv/osmotv
|
src/tv/osmotv
|
||||||
src/radio/osmoradio
|
src/radio/osmoradio
|
||||||
|
src/datenklo/datenklo
|
||||||
sim/cnetz_sim
|
sim/cnetz_sim
|
||||||
src/test/test_filter
|
src/test/test_filter
|
||||||
src/test/test_sendevolumenregler
|
src/test/test_sendevolumenregler
|
||||||
|
|
|
@ -32,19 +32,27 @@ AC_ARG_WITH([alsa], [AS_HELP_STRING([--with-alsa], [compile with Alsa driver @<:
|
||||||
AC_ARG_WITH([uhd], [AS_HELP_STRING([--with-uhd], [compile with UHD driver @<:@default=check@:>@]) ], [], [with_uhd="check"])
|
AC_ARG_WITH([uhd], [AS_HELP_STRING([--with-uhd], [compile with UHD driver @<:@default=check@:>@]) ], [], [with_uhd="check"])
|
||||||
AC_ARG_WITH([soapy], [AS_HELP_STRING([--with-soapy], [compile with SoapySDR driver @<:@default=check@:>@]) ], [], [with_soapy="check"])
|
AC_ARG_WITH([soapy], [AS_HELP_STRING([--with-soapy], [compile with SoapySDR driver @<:@default=check@:>@]) ], [], [with_soapy="check"])
|
||||||
AC_ARG_WITH([imagemagick], [AS_HELP_STRING([--with-imagemagick], [compile with ImageMagick support @<:@default=check@:>@]) ], [], [with_imagemagick="check"])
|
AC_ARG_WITH([imagemagick], [AS_HELP_STRING([--with-imagemagick], [compile with ImageMagick support @<:@default=check@:>@]) ], [], [with_imagemagick="check"])
|
||||||
|
AC_ARG_WITH([fuse], [AS_HELP_STRING([--with-fuse], [compile with FUSE support @<:@default=check@:>@]) ], [], [with_fuse="check"])
|
||||||
AS_IF([test "x$with_alsa" != xno], [PKG_CHECK_MODULES(ALSA, alsa >= 1.0, with_alsa=yes, with_alsa=no)])
|
AS_IF([test "x$with_alsa" != xno], [PKG_CHECK_MODULES(ALSA, alsa >= 1.0, with_alsa=yes, with_alsa=no)])
|
||||||
AS_IF([test "x$with_uhd" != xno], [PKG_CHECK_MODULES(UHD, uhd >= 3.0.0, with_sdr=yes with_uhd=yes, with_uhd=no)])
|
AS_IF([test "x$with_uhd" != xno], [PKG_CHECK_MODULES(UHD, uhd >= 3.0.0, with_sdr=yes with_uhd=yes, with_uhd=no)])
|
||||||
AS_IF([test "x$with_soapy" != xno], [PKG_CHECK_MODULES(SOAPY, SoapySDR >= 0.5.0, with_sdr=yes with_soapy=yes, with_soapy=no)])
|
AS_IF([test "x$with_soapy" != xno], [PKG_CHECK_MODULES(SOAPY, SoapySDR >= 0.5.0, with_sdr=yes with_soapy=yes, with_soapy=no)])
|
||||||
AS_IF([test "x$with_imagemagick" != xno], [PKG_CHECK_MODULES(IMAGEMAGICK, ImageMagick >= 7.0.0, with_imagemagick=yes, with_imagemagick=no)])
|
AS_IF([test "x$with_imagemagick" != xno], [PKG_CHECK_MODULES(IMAGEMAGICK, ImageMagick >= 7.0.0, with_imagemagick=yes, with_imagemagick=no)])
|
||||||
|
AS_IF([test "x$with_fuse" != xno], with_fuse=check)
|
||||||
|
AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse3 >= 0.30.0, with_fuse=yes, with_fuse=check)])
|
||||||
|
AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse2 >= 0.29.0, with_fuse=yes, with_fuse=check)])
|
||||||
|
AS_IF([test "x$with_fuse" == xcheck], [PKG_CHECK_MODULES(FUSE, fuse >= 0.29.0, with_fuse=yes, with_fuse=check)])
|
||||||
|
AS_IF([test "x$with_fuse" == xcheck], with_fuse=no)
|
||||||
AM_CONDITIONAL(HAVE_ALSA, test "x$with_alsa" == "xyes" )
|
AM_CONDITIONAL(HAVE_ALSA, test "x$with_alsa" == "xyes" )
|
||||||
AM_CONDITIONAL(HAVE_UHD, test "x$with_uhd" == "xyes" )
|
AM_CONDITIONAL(HAVE_UHD, test "x$with_uhd" == "xyes" )
|
||||||
AM_CONDITIONAL(HAVE_SOAPY, test "x$with_soapy" == "xyes" )
|
AM_CONDITIONAL(HAVE_SOAPY, test "x$with_soapy" == "xyes" )
|
||||||
AM_CONDITIONAL(HAVE_SDR, test "x$with_sdr" == "xyes" )
|
AM_CONDITIONAL(HAVE_SDR, test "x$with_sdr" == "xyes" )
|
||||||
AM_CONDITIONAL(HAVE_MAGICK, test "x$with_imagemagick" == "xyes" )
|
AM_CONDITIONAL(HAVE_MAGICK, test "x$with_imagemagick" == "xyes" )
|
||||||
|
AM_CONDITIONAL(HAVE_FUSE, test "x$with_fuse" == "xyes" )
|
||||||
AS_IF([test "x$with_alsa" == "xyes"],[AC_MSG_NOTICE( Compiling with Alsa support )], [AC_MSG_NOTICE( Alsa sound card not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
AS_IF([test "x$with_alsa" == "xyes"],[AC_MSG_NOTICE( Compiling with Alsa support )], [AC_MSG_NOTICE( Alsa sound card not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
||||||
AS_IF([test "x$with_uhd" == "xyes"],[AC_MSG_NOTICE( Compiling with UHD SDR support )], [AC_MSG_NOTICE( UHD SDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
AS_IF([test "x$with_uhd" == "xyes"],[AC_MSG_NOTICE( Compiling with UHD SDR support )], [AC_MSG_NOTICE( UHD SDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
||||||
AS_IF([test "x$with_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
AS_IF([test "x$with_soapy" == "xyes"],[AC_MSG_NOTICE( Compiling with SoapySDR support )], [AC_MSG_NOTICE( SoapySDR not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
||||||
AS_IF([test "x$with_imagemagick" == "xyes"],[AC_MSG_NOTICE( Compiling with ImageMagick )],[AC_MSG_NOTICE( ImageMagick not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
AS_IF([test "x$with_imagemagick" == "xyes"],[AC_MSG_NOTICE( Compiling with ImageMagick )],[AC_MSG_NOTICE( ImageMagick not supported. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
||||||
|
AS_IF([test "x$with_fuse" == "xyes"],[AC_MSG_NOTICE( Compiling with FUSE )],[AC_MSG_NOTICE( FUSE not supported. There will be no analog modem support. Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. )])
|
||||||
|
|
||||||
AS_IF([test "x$with_alsa" != "xyes" -a "x$with_sdr" != "xyes"],[AC_MSG_FAILURE( Without sound nor SDR support this project does not make sense. Please support sound card for analog transceivers or better SDR!" )],[])
|
AS_IF([test "x$with_alsa" != "xyes" -a "x$with_sdr" != "xyes"],[AC_MSG_FAILURE( Without sound nor SDR support this project does not make sense. Please support sound card for analog transceivers or better SDR!" )],[])
|
||||||
|
|
||||||
|
@ -88,6 +96,7 @@ AC_OUTPUT(
|
||||||
src/jolly/Makefile
|
src/jolly/Makefile
|
||||||
src/tv/Makefile
|
src/tv/Makefile
|
||||||
src/radio/Makefile
|
src/radio/Makefile
|
||||||
|
src/datenklo/Makefile
|
||||||
src/test/Makefile
|
src/test/Makefile
|
||||||
src/Makefile
|
src/Makefile
|
||||||
sim/Makefile
|
sim/Makefile
|
||||||
|
|
|
@ -26,6 +26,11 @@ The following test signals are supported:
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<font color="red">Importaint: SDR is required! It must be capable of about 15 Mega samples per second.</font>
|
||||||
|
<br><br>
|
||||||
|
If you use LimeSDR, you MUST use USB 3.0 to have enough bandwidth!
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr><center>[<a href="index.html">Back to main page</a>]</center><hr>
|
<hr><center>[<a href="index.html">Back to main page</a>]</center><hr>
|
||||||
</td></tr></table></center>
|
</td></tr></table></center>
|
||||||
|
|
|
@ -49,6 +49,15 @@ SUBDIRS += \
|
||||||
imts \
|
imts \
|
||||||
jolly \
|
jolly \
|
||||||
tv \
|
tv \
|
||||||
radio \
|
radio
|
||||||
|
|
||||||
|
if HAVE_SDR
|
||||||
|
if HAVE_FUSE
|
||||||
|
SUBDIRS += \
|
||||||
|
datenklo
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
SUBDIRS += \
|
||||||
test
|
test
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
/* if no data is receivd, check for start bit */
|
||||||
|
if (uart->rx_pos < 0) {
|
||||||
|
/* if no start bit */
|
||||||
|
if (bit != 0 || uart->last_bit != 1)
|
||||||
|
goto out;
|
||||||
|
/* start bit */
|
||||||
|
uart->rx_data = 0;
|
||||||
|
uart->rx_pos = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* shift bit */
|
||||||
|
uart->rx_data |= bit << (uart->rx_pos);
|
||||||
|
/* end of transmission */
|
||||||
|
if (++uart->rx_pos == uart->length) {
|
||||||
|
/* turn off reception */
|
||||||
|
uart->rx_pos = -1;
|
||||||
|
/* check if parity is invalid */
|
||||||
|
if (uart->parity) {
|
||||||
|
parity = calc_parity(uart->rx_data, uart->data_bits, uart->parity);
|
||||||
|
if (((uart->rx_data >> uart->data_bits) & 1) != parity)
|
||||||
|
flags |= UART_PARITY_ERROR;
|
||||||
|
}
|
||||||
|
/* check if last stop bit is invalid */
|
||||||
|
if (((uart->rx_data >> (uart->length - 1)) & 1) == 0) {
|
||||||
|
flags |= UART_CODE_VIOLATION;
|
||||||
|
}
|
||||||
|
/* check if all bits are 0 */
|
||||||
|
if (!uart->rx_data) {
|
||||||
|
flags |= UART_BREAK;
|
||||||
|
}
|
||||||
|
/* clear all bits after data */
|
||||||
|
uart->rx_data &= ~(0xffffffff << uart->data_bits);
|
||||||
|
uart->rx_cb(uart->inst, uart->rx_data, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
/* remember last bit for start bit detection (1 -> 0 transition) */
|
||||||
|
uart->last_bit = bit;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
enum uart_parity {
|
||||||
|
UART_PARITY_NONE,
|
||||||
|
UART_PARITY_EVEN,
|
||||||
|
UART_PARITY_ODD,
|
||||||
|
UART_PARITY_MARK,
|
||||||
|
UART_PARITY_SPACE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* uart flags */
|
||||||
|
#define UART_PARITY_ERROR (1 << 0)
|
||||||
|
#define UART_CODE_VIOLATION (1 << 1)
|
||||||
|
#define UART_BREAK (1 << 2)
|
||||||
|
|
||||||
|
typedef struct uart {
|
||||||
|
void *inst;
|
||||||
|
int (*tx_cb)(void *inst);
|
||||||
|
void (*rx_cb)(void *inst, int data, uint32_t flags);
|
||||||
|
uint8_t data_bits;
|
||||||
|
enum uart_parity parity;
|
||||||
|
uint8_t stop_bits;
|
||||||
|
int last_bit;
|
||||||
|
uint32_t tx_data;
|
||||||
|
uint32_t rx_data;
|
||||||
|
int tx_pos;
|
||||||
|
int rx_pos;
|
||||||
|
int length;
|
||||||
|
} uart_t;
|
||||||
|
|
||||||
|
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));
|
||||||
|
int uart_tx_bit(uart_t *uart);
|
||||||
|
int uart_is_tx(uart_t *uart);
|
||||||
|
void uart_rx_bit(uart_t *uart, int bit);
|
||||||
|
|
|
@ -65,6 +65,10 @@ struct debug_cat {
|
||||||
{ "soapy", "\033[1;35m" },
|
{ "soapy", "\033[1;35m" },
|
||||||
{ "wave", "\033[1;33m" },
|
{ "wave", "\033[1;33m" },
|
||||||
{ "radio", "\033[1;34m" },
|
{ "radio", "\033[1;34m" },
|
||||||
|
{ "am791x", "\033[0;31m" },
|
||||||
|
{ "uart", "\033[0;32m" },
|
||||||
|
{ "device", "\033[0;33m" },
|
||||||
|
{ "datenklo", "\033[1;34m" },
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,7 +128,6 @@ void _printdebug(const char *file, const char __attribute__((unused)) *function,
|
||||||
|
|
||||||
while ((p = strchr(file, '/')))
|
while ((p = strchr(file, '/')))
|
||||||
file = p + 1;
|
file = p + 1;
|
||||||
|
|
||||||
if (clear_console_text)
|
if (clear_console_text)
|
||||||
clear_console_text();
|
clear_console_text();
|
||||||
if (debug_limit_scroll) {
|
if (debug_limit_scroll) {
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
#define DSOAPY 21
|
#define DSOAPY 21
|
||||||
#define DWAVE 22
|
#define DWAVE 22
|
||||||
#define DRADIO 23
|
#define DRADIO 23
|
||||||
|
#define DAM791X 24
|
||||||
|
#define DUART 25
|
||||||
|
#define DDEVICE 26
|
||||||
|
#define DDATENKLO 27
|
||||||
|
|
||||||
void get_win_size(int *w, int *h);
|
void get_win_size(int *w, int *h);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue