mirror of
https://github.com/halleysfifthinc/AVCLAN-Mockingboard.git
synced 2026-11-08 10:17:41 +00:00
430 lines
12 KiB
C++
430 lines
12 KiB
C++
// copyright (C) 2006 Marcin Slonicki <marcin@softservice.com.pl>
|
|
// copyright (C) 2007 Louis Frigon
|
|
// Copyright (C) 2015 Allen Hill <allenofthehills@gmail.com>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/*
|
|
AVC LAN Frame Format
|
|
│ Bits │ Description
|
|
────────────────────────────────────────
|
|
| 1 │ Start bit
|
|
| 1 │ Direct/broadcast
|
|
| 12 │ Controller address
|
|
| 1 │ Parity
|
|
| 12 │ Peripheral address
|
|
| 1 │ Parity
|
|
| 1 │ *Acknowledge* (read below)
|
|
| 4 │ Control
|
|
| 1 │ Parity
|
|
| 1 │ *Acknowledge*
|
|
| 8 │ Message length (n)
|
|
| 1 │ Parity
|
|
| 1 │ *Acknowledge*
|
|
────────
|
|
| 8 │ Data
|
|
| 1 │ Parity
|
|
| 1 │ *Acknowledge*
|
|
*repeat `n` times*
|
|
|
|
No acknowledge bits are sent for broadcast frames.
|
|
*/
|
|
|
|
#include <concepts>
|
|
#include <cstdio>
|
|
#include <memory>
|
|
#include <new>
|
|
|
|
#include "avclan.h"
|
|
#include "bus.hpp"
|
|
#include "frame.hpp"
|
|
#include "hal/phy.h"
|
|
#include "stdshim.hpp"
|
|
|
|
namespace {
|
|
using Read = avclan::detail::Error::Read;
|
|
using Send = avclan::detail::Error::Send;
|
|
using Bit = avclan::detail::Bit;
|
|
|
|
struct trailer_bits_t {};
|
|
struct no_parity_t : trailer_bits_t {}; // raw bits (the broadcast bit)
|
|
struct with_parity_t : trailer_bits_t {}; // bits + parity (controller address)
|
|
struct with_ack_t : trailer_bits_t {
|
|
}; // bits + parity + ACK slot (all other fields)
|
|
inline constexpr no_parity_t no_parity{};
|
|
inline constexpr with_parity_t with_parity{};
|
|
inline constexpr with_ack_t with_ack{};
|
|
} // namespace
|
|
|
|
namespace avclan {
|
|
|
|
class Bus::Handle {
|
|
// A live handle exclusively borrows the Bus for the duration of a
|
|
// transaction; holding the reference is what forces `get()` (and thus
|
|
// `read`/`send`) to require a mutable Bus, even though the bus hardware
|
|
// itself is reached through free `phy_*` functions.
|
|
Bus &bus_;
|
|
explicit Handle(Bus &bus) : bus_{bus} { phy_guard_enter(); };
|
|
friend Bus;
|
|
|
|
public:
|
|
~Handle() { phy_guard_leave(); };
|
|
Handle(const Handle &) = delete;
|
|
Handle(Handle &&) = delete;
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
Send sendstartbit() { return phy_send_startbit(); };
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
Read readstartbit() { return phy_read_startbit(); };
|
|
|
|
template <auto N, std::unsigned_integral T,
|
|
std::derived_from<trailer_bits_t> Trailer>
|
|
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
|
|
Send send(T bits, Trailer /*tag*/) {
|
|
const Bit parity = sendbits<N>(bits);
|
|
|
|
if constexpr (std::is_same_v<Trailer, with_parity_t>)
|
|
sendbits<1>(to_underlying(parity));
|
|
|
|
return Send{0};
|
|
};
|
|
|
|
template <auto N, std::unsigned_integral T>
|
|
requires(sizeof(T) < 3 && N < 16)
|
|
Send send(T bits, with_ack_t /*tag*/, bool expect_ack) {
|
|
send<N>(bits, with_parity);
|
|
|
|
if (expect_ack)
|
|
return read_ACK();
|
|
|
|
sendbits<1>(1U); // still need to fill the ack bit slot
|
|
return Send{0};
|
|
};
|
|
|
|
template <auto N, std::unsigned_integral T,
|
|
std::derived_from<trailer_bits_t> Trailer>
|
|
requires(sizeof(T) < 3 && N < 16 && !std::same_as<Trailer, with_ack_t>)
|
|
Read read(T *bits, Trailer /*tag*/) {
|
|
const Bit calc_parity = readbits<N>(bits);
|
|
if constexpr (std::is_same_v<Trailer, with_parity_t>) {
|
|
uint8_t read_parity;
|
|
readbits<1>(&read_parity);
|
|
if (to_underlying(calc_parity) != read_parity)
|
|
return Read::BAD_PARITY;
|
|
}
|
|
return Read{0};
|
|
};
|
|
|
|
template <auto N, std::unsigned_integral T, class F>
|
|
requires(sizeof(T) < 3 && N < 16)
|
|
Read read(T *bits, with_ack_t /*tag*/, F &&ack) {
|
|
if (read<N>(bits, with_parity) == Read::BAD_PARITY)
|
|
return Read::BAD_PARITY;
|
|
|
|
if (ack()) {
|
|
send_ACK();
|
|
} else {
|
|
uint8_t slot;
|
|
readbits<1>(&slot);
|
|
}
|
|
|
|
return Read{0};
|
|
};
|
|
template <auto N, std::unsigned_integral T>
|
|
requires(sizeof(T) < 3 && N < 16)
|
|
Read read(T *bits, with_ack_t /*tag*/, bool ack) {
|
|
return read<N>(bits, with_ack, [=]() { return ack; });
|
|
}
|
|
|
|
private:
|
|
static void send_ACK() { phy_send_ack(); };
|
|
static Send read_ACK() { return phy_read_ack(); };
|
|
|
|
template <auto N, class T> Bit sendbits(T bits);
|
|
template <auto N, class T> Bit readbits(T *bits);
|
|
|
|
template <auto N>
|
|
requires(N > 1 && N < 8)
|
|
Bit sendbits(uint8_t bits) {
|
|
return phy_send_bits_u8(&bits, N);
|
|
};
|
|
template <auto N>
|
|
requires(N <= 16)
|
|
Bit sendbits(uint16_t bits) {
|
|
return phy_send_bits_u16(&bits, N);
|
|
};
|
|
template <auto N>
|
|
requires(N < 8)
|
|
Bit readbits(uint8_t *bits) {
|
|
return static_cast<Bit>(phy_read_bits_u8(bits, N));
|
|
};
|
|
template <auto N>
|
|
requires(N <= 16)
|
|
Bit readbits(uint16_t *bits) {
|
|
return static_cast<Bit>(phy_read_bits_u16(bits, N));
|
|
};
|
|
};
|
|
|
|
template <> inline Bit Bus::Handle::sendbits<8>(uint8_t bits) {
|
|
return phy_send_byte(&bits);
|
|
};
|
|
template <> inline Bit Bus::Handle::sendbits<1>(uint8_t bits) {
|
|
const Bit bit{static_cast<Bit>(bits & 1U)};
|
|
phy_send_bit(bit);
|
|
return bit;
|
|
};
|
|
template <> inline Bit Bus::Handle::readbits<8>(uint8_t *bits) {
|
|
return phy_read_byte(bits);
|
|
};
|
|
|
|
void Bus::init() {
|
|
// Idempotent: the single Bus is shared by reference, so every Peripheral's
|
|
// ctor calls init() on it — but the hardware must be brought up exactly once
|
|
// (phy_init is not assumed re-entrant/idempotent).
|
|
if (inited_)
|
|
return;
|
|
phy_init();
|
|
muted_ = false; // phy_init leaves the bus TX unmuted
|
|
inited_ = true;
|
|
};
|
|
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
bool Bus::is_active() const { return phy_active(); };
|
|
void Bus::mute(bool mute) {
|
|
phy_mute(mute);
|
|
muted_ = mute; // Only update muted_ *AFTER* hardware has finished muting
|
|
};
|
|
|
|
auto Bus::read(uint16_t address, Frame::Print print)
|
|
-> expected<std::unique_ptr<Frame>, Error::Read> {
|
|
struct errtype {
|
|
Read errno;
|
|
uint16_t val;
|
|
} err = {};
|
|
|
|
using enum Read;
|
|
|
|
std::unique_ptr<Frame> in(new (std::nothrow) Frame);
|
|
if (!in) {
|
|
err.errno = POOL_EMPTY;
|
|
goto handle_err;
|
|
}
|
|
|
|
{ // bound handle lifetime
|
|
auto handle = get();
|
|
|
|
bool shouldACK = false;
|
|
uint8_t tmp = 0;
|
|
|
|
err.errno = handle.readstartbit();
|
|
if (err.errno != Read{0})
|
|
goto handle_err;
|
|
|
|
handle.read<1>(&tmp, no_parity);
|
|
in->is_unicast = (tmp != 0U);
|
|
|
|
if (auto rerr = handle.read<12>(&in->controller_addr, with_parity);
|
|
rerr == BAD_PARITY) {
|
|
err.errno = BAD_CONTROLLER_PARITY;
|
|
if (print.verbose)
|
|
err.val = in->controller_addr;
|
|
|
|
goto handle_err;
|
|
}
|
|
|
|
// Using lambda for delayed evaluation of peripheral_addr field
|
|
// deref, which will be written by the time the lambda is evaluated
|
|
auto should_ack_lambda = [&]() {
|
|
shouldACK = !is_muted() && (in->peripheral_addr == address);
|
|
return shouldACK;
|
|
};
|
|
if (auto rerr =
|
|
handle.read<12>(&in->peripheral_addr, with_ack, should_ack_lambda);
|
|
rerr == BAD_PARITY) {
|
|
err.errno = BAD_PERIPHERAL_PARITY;
|
|
if (print.verbose)
|
|
err.val = in->peripheral_addr;
|
|
|
|
goto handle_err;
|
|
}
|
|
|
|
if (auto rerr = handle.read<4>(&in->control, with_ack, shouldACK);
|
|
rerr == BAD_PARITY) {
|
|
err.errno = BAD_CONTROL_PARITY;
|
|
if (print.verbose)
|
|
err.val = in->control;
|
|
|
|
goto handle_err;
|
|
}
|
|
|
|
if (auto rerr = handle.read<8>(&in->length, with_ack, shouldACK);
|
|
rerr == BAD_PARITY) {
|
|
err.errno = BAD_LENGTH_PARITY;
|
|
if (print.verbose)
|
|
err.val = in->length;
|
|
|
|
goto handle_err;
|
|
}
|
|
|
|
if (in->length == 0 || in->length > Frame::MAXLENGTH) {
|
|
err.errno = BAD_LENGTH_RANGE;
|
|
err.val = in->length;
|
|
goto handle_err;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < in->length; i++) {
|
|
if (auto rerr = handle.read<8>(&in->data[i], with_ack, shouldACK);
|
|
rerr == BAD_PARITY) {
|
|
err.errno = BAD_DATA_PARITY;
|
|
if (print.verbose)
|
|
err.val = in->data[i];
|
|
|
|
goto handle_err;
|
|
}
|
|
}
|
|
} // destroy handle
|
|
|
|
if (false) { // NOLINT(readability-simplify-boolean-expr)
|
|
handle_err:;
|
|
fputs("ERR(read): ", stdout);
|
|
switch (err.errno) {
|
|
case POOL_EMPTY: puts("failed Frame alloc"); break;
|
|
case BAD_STARTBIT: fputs("bad start bit (other)", stdout); break;
|
|
case STARTBIT_MISSED: fputs("missed start bit", stdout); break;
|
|
case STARTBIT_MALFORMED:
|
|
fputs("malformed start bit (external cause)", stdout);
|
|
break;
|
|
case STARTBIT_TOO_LONG: fputs("bad start bit (long)", stdout); break;
|
|
case BAD_CONTROLLER_PARITY:
|
|
fputs("reading controller addr.", stdout);
|
|
goto VERBOSE;
|
|
case BAD_PERIPHERAL_PARITY:
|
|
fputs("reading peripheral addr.", stdout);
|
|
goto VERBOSE;
|
|
case BAD_CONTROL_PARITY: fputs("reading control", stdout); goto VERBOSE;
|
|
case BAD_LENGTH_PARITY: fputs("reading length", stdout); goto VERBOSE;
|
|
case BAD_LENGTH_RANGE: printf("bad length 0x%02X:", err.val); break;
|
|
case BAD_DATA_PARITY: fputs("reading data", stdout); goto VERBOSE;
|
|
case BAD_PARITY:
|
|
__builtin_unreachable();
|
|
VERBOSE:
|
|
if (print.verbose) {
|
|
printf("; read 0x%02X:", err.val);
|
|
}
|
|
}
|
|
putchar('\n');
|
|
}
|
|
|
|
// Only print if some data has been correctly received
|
|
if (print.print && (err.errno < STARTBIT_MISSED)) {
|
|
if (err.errno > BAD_DATA_PARITY)
|
|
in->length = 0;
|
|
in->print(print);
|
|
}
|
|
|
|
if (err.errno != Read{0})
|
|
return unexpected(err.errno);
|
|
|
|
return in;
|
|
}
|
|
|
|
auto Bus::send(const Frame &out, Frame::Print print) -> Send {
|
|
struct errtype {
|
|
// Error enum is ordered such that a lower numeric value corresponds to
|
|
// more success
|
|
Send errno;
|
|
uint8_t val;
|
|
} err = {};
|
|
|
|
using enum Send;
|
|
|
|
if (is_muted()) {
|
|
err.errno = MUTED;
|
|
goto handle_err;
|
|
}
|
|
|
|
{ // bound handle lifetime
|
|
auto handle = get();
|
|
|
|
if (handle.sendstartbit() == BUSY) {
|
|
// Some other device is already driving the bus
|
|
err.errno = BUSY;
|
|
goto handle_err;
|
|
}
|
|
|
|
handle.send<1>(static_cast<uint8_t>(out.is_unicast), no_parity);
|
|
|
|
handle.send<12>(out.controller_addr, with_parity);
|
|
|
|
if (auto serr =
|
|
handle.send<12>(out.peripheral_addr, with_ack, out.is_unicast);
|
|
serr == NAK) {
|
|
err.errno = NAK_ADDRESS;
|
|
goto handle_err;
|
|
}
|
|
|
|
if (auto serr = handle.send<4>(out.control, with_ack, out.is_unicast);
|
|
serr == NAK) {
|
|
err.errno = NAK_CONTROL;
|
|
goto handle_err;
|
|
}
|
|
|
|
if (auto serr = handle.send<8>(out.length, with_ack, out.is_unicast);
|
|
serr == NAK) {
|
|
err.errno = NAK_MESSAGE_LENGTH;
|
|
goto handle_err;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < out.length; i++) {
|
|
if (auto serr = handle.send<8>(out.data[i], with_ack, out.is_unicast);
|
|
serr == NAK) {
|
|
err.errno = NAK_DATA;
|
|
err.val = i;
|
|
goto handle_err;
|
|
}
|
|
}
|
|
} // destroy handle
|
|
|
|
// back to read mode
|
|
if (false) { // NOLINT(readability-simplify-boolean-expr)
|
|
handle_err:;
|
|
fputs("Error", stdout);
|
|
switch (err.errno) {
|
|
case MUTED: fputs(": Device muted", stdout); break;
|
|
case BUSY: fputs(": Busy bus", stdout); break;
|
|
case NAK_ADDRESS:
|
|
case NAK_CONTROL:
|
|
case NAK_MESSAGE_LENGTH:
|
|
case NAK_DATA:
|
|
case NAK:
|
|
fputs(" NAK: ", stdout);
|
|
switch (err.errno) {
|
|
case NAK_ADDRESS: fputs("address", stdout); break;
|
|
case NAK_CONTROL: fputs("Control", stdout); break;
|
|
case NAK_MESSAGE_LENGTH: fputs("Message length", stdout); break;
|
|
case NAK_DATA: printf(" data[%u]", err.val); break;
|
|
case NAK:
|
|
case MUTED:
|
|
case BUSY: __builtin_unreachable();
|
|
}
|
|
break;
|
|
}
|
|
putchar('\n');
|
|
}
|
|
|
|
if (print.print)
|
|
out.print(print);
|
|
|
|
return err.errno;
|
|
}
|
|
|
|
Bus::Handle Bus::get() { return Handle{*this}; };
|
|
|
|
#if !defined(NDEBUG) && defined(MEASURE_BUS)
|
|
// Debug bit-timing measurement on the one physical bus; instance-scoped for the
|
|
// same reason as is_active().
|
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
|
void Bus::measure() { phy_measure(); }
|
|
#endif
|
|
|
|
} // namespace avclan
|