Merge remote-tracking branch 'origin/develop' into mueller/pdec_irq_handling
All checks were successful
EIVE/eive-obsw/pipeline/pr-develop This commit looks good

This commit is contained in:
2022-11-02 14:16:14 +01:00
41 changed files with 261 additions and 167 deletions

View File

@ -0,0 +1,119 @@
#include "AxiPtmeConfig.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw_hal/linux/uio/UioMapper.h"
AxiPtmeConfig::AxiPtmeConfig(object_id_t objectId, std::string axiUio, int mapNum)
: SystemObject(objectId), axiUio(std::move(axiUio)), mapNum(mapNum) {
mutex = MutexFactory::instance()->createMutex();
if (mutex == nullptr) {
sif::warning << "Failed to create mutex" << std::endl;
}
}
AxiPtmeConfig::~AxiPtmeConfig() {}
ReturnValue_t AxiPtmeConfig::initialize() {
ReturnValue_t result = returnvalue::OK;
UioMapper uioMapper(axiUio, mapNum);
result = uioMapper.getMappedAdress(&baseAddress, UioMapper::Permissions::READ_WRITE);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::writeCaduRateReg(uint8_t rateVal) {
ReturnValue_t result = returnvalue::OK;
result = mutex->lockMutex(timeoutType, mutexTimeout);
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::writeCaduRateReg: Failed to lock mutex" << std::endl;
return returnvalue::FAILED;
}
*(baseAddress + CADU_BITRATE_REG) = static_cast<uint32_t>(rateVal);
result = mutex->unlockMutex();
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::writeCaduRateReg: Failed to unlock mutex" << std::endl;
return returnvalue::FAILED;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::enableTxclockManipulator() {
ReturnValue_t result = writeBit(COMMON_CONFIG_REG, true, BitPos::EN_TX_CLK_MANIPULATOR);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::disableTxclockManipulator() {
ReturnValue_t result = writeBit(COMMON_CONFIG_REG, false, BitPos::EN_TX_CLK_MANIPULATOR);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::enableTxclockInversion() {
ReturnValue_t result = writeBit(COMMON_CONFIG_REG, true, BitPos::INVERT_CLOCK);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::disableTxclockInversion() {
ReturnValue_t result = writeBit(COMMON_CONFIG_REG, false, BitPos::INVERT_CLOCK);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::writeReg(uint32_t regOffset, uint32_t writeVal) {
ReturnValue_t result = returnvalue::OK;
result = mutex->lockMutex(timeoutType, mutexTimeout);
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::readReg: Failed to lock mutex" << std::endl;
return returnvalue::FAILED;
}
*(baseAddress + regOffset / ADRESS_DIVIDER) = writeVal;
result = mutex->unlockMutex();
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::readReg: Failed to unlock mutex" << std::endl;
return returnvalue::FAILED;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::readReg(uint32_t regOffset, uint32_t* readVal) {
ReturnValue_t result = returnvalue::OK;
result = mutex->lockMutex(timeoutType, mutexTimeout);
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::readReg: Failed to lock mutex" << std::endl;
return returnvalue::FAILED;
}
*readVal = *(baseAddress + regOffset / ADRESS_DIVIDER);
result = mutex->unlockMutex();
if (result != returnvalue::OK) {
sif::warning << "AxiPtmeConfig::readReg: Failed to unlock mutex" << std::endl;
return returnvalue::FAILED;
}
return returnvalue::OK;
}
ReturnValue_t AxiPtmeConfig::writeBit(uint32_t regOffset, bool bitVal, BitPos bitPos) {
uint32_t readVal = 0;
ReturnValue_t result = readReg(regOffset, &readVal);
if (result != returnvalue::OK) {
return result;
}
uint32_t writeVal =
(readVal & ~(1 << static_cast<uint32_t>(bitPos))) | bitVal << static_cast<uint32_t>(bitPos);
result = writeReg(regOffset, writeVal);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}

View File

@ -0,0 +1,102 @@
#ifndef LINUX_OBC_AXIPTMECONFIG_H_
#define LINUX_OBC_AXIPTMECONFIG_H_
#include <string>
#include "fsfw/ipc/MutexIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/returnvalues/returnvalue.h"
/**
* @brief Class providing low level access to the configuration interface of the PTME.
*
* @author J. Meier
*/
class AxiPtmeConfig : public SystemObject {
public:
/**
* @brief Constructor
* @param axiUio Device file of UIO belonging to the AXI configuration interface.
* @param mapNum Number of map belonging to axi configuration interface.
*/
AxiPtmeConfig(object_id_t objectId, std::string axiUio, int mapNum);
virtual ~AxiPtmeConfig();
virtual ReturnValue_t initialize() override;
/**
* @brief Will write to the bitrate configuration register. Actual generated rate depends on
* frequency of the clock connected to the bit clock input of PTME.
*/
ReturnValue_t writeCaduRateReg(uint8_t rateVal);
/**
* @brief Next to functions control the tx clock manipulator component
*
* @details If the tx clock manipulator is enabled the output clock of the PTME is manipulated
* in a way that both high and low periods in the clock signal have equal lengths.
* The default implementation of the PTME generates a clock where the high level is
* only one bit clock period long. This might be too short to match the setup and hold
* times of the S-and transceiver.
* Default: Enables TX clock manipulator
*
*/
ReturnValue_t enableTxclockManipulator();
ReturnValue_t disableTxclockManipulator();
/**
* @brief The next to functions control whether data will be updated on the rising or falling edge
* of the tx clock.
* Enable inversion will update data on falling edge (not the configuration required by the
* syrlinks)
* Disable clock inversion. Data updated on rising edge.
* Default: Inversion is disabled
*/
ReturnValue_t enableTxclockInversion();
ReturnValue_t disableTxclockInversion();
private:
// Address of register storing the bitrate configuration parameter
static const uint32_t CADU_BITRATE_REG = 0x0;
// Address of register storing common configuration parameters
static const uint32_t COMMON_CONFIG_REG = 0x4;
static const uint32_t ADRESS_DIVIDER = 4;
enum class BitPos : uint32_t { EN_TX_CLK_MANIPULATOR, INVERT_CLOCK };
std::string axiUio;
std::string uioMap;
int mapNum = 0;
MutexIF* mutex = nullptr;
MutexIF::TimeoutType timeoutType = MutexIF::TimeoutType::WAITING;
uint32_t mutexTimeout = 20;
uint32_t* baseAddress = nullptr;
/**
* @brief Function to write to configuration registers
*
* @param writeVal Value to write
*/
ReturnValue_t writeReg(uint32_t regOffset, uint32_t writeVal);
/**
* @brief Reads value from configuration register
*
* @param regOffset Offset of register from base address to read from
* Qparam readVal Pointer to variable where read value will be written to
*/
ReturnValue_t readReg(uint32_t regOffset, uint32_t* readVal);
/**
* @brief Sets one bit in a register
*
* @param regOffset Offset of the register where to set the bit
* @param bitVal The value of the bit to set (1 or 0)
* @param bitPos The position of the bit within the register to set
*
* @return returnvalue::OK if successful, otherwise returnvalue::FAILED
*/
ReturnValue_t writeBit(uint32_t regOffset, bool bitVal, BitPos bitPos);
};
#endif /* LINUX_OBC_AXIPTMECONFIG_H_ */

View File

@ -0,0 +1,3 @@
target_sources(
${OBSW_NAME} PUBLIC PapbVcInterface.cpp Ptme.cpp PdecHandler.cpp
PdecConfig.cpp PtmeConfig.cpp AxiPtmeConfig.cpp)

View File

@ -0,0 +1,98 @@
#include <fsfw_hal/linux/uio/UioMapper.h>
#include <linux/ipcore/PapbVcInterface.h>
#include "fsfw/serviceinterface/ServiceInterface.h"
PapbVcInterface::PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbBusyId,
gpioId_t papbEmptyId, std::string uioFile, int mapNum)
: gpioComIF(gpioComIF),
papbBusyId(papbBusyId),
papbEmptyId(papbEmptyId),
uioFile(std::move(uioFile)),
mapNum(mapNum) {}
PapbVcInterface::~PapbVcInterface() {}
ReturnValue_t PapbVcInterface::initialize() {
UioMapper uioMapper(uioFile, mapNum);
return uioMapper.getMappedAdress(&vcBaseReg, UioMapper::Permissions::WRITE_ONLY);
}
ReturnValue_t PapbVcInterface::write(const uint8_t* data, size_t size) {
if (pollPapbBusySignal() == returnvalue::OK) {
startPacketTransfer();
}
for (size_t idx = 0; idx < size; idx++) {
if (pollPapbBusySignal() == returnvalue::OK) {
*(vcBaseReg + DATA_REG_OFFSET) = static_cast<uint32_t>(*(data + idx));
} else {
sif::warning << "PapbVcInterface::write: Only written " << idx << " of " << size << " data"
<< std::endl;
return returnvalue::FAILED;
}
}
if (pollPapbBusySignal() == returnvalue::OK) {
endPacketTransfer();
}
return returnvalue::OK;
}
void PapbVcInterface::startPacketTransfer() { *vcBaseReg = CONFIG_START; }
void PapbVcInterface::endPacketTransfer() { *vcBaseReg = CONFIG_END; }
ReturnValue_t PapbVcInterface::pollPapbBusySignal() {
gpio::Levels papbBusyState = gpio::Levels::LOW;
ReturnValue_t result = returnvalue::OK;
/** Check if PAPB interface is ready to receive data */
result = gpioComIF->readGpio(papbBusyId, papbBusyState);
if (result != returnvalue::OK) {
sif::warning << "PapbVcInterface::pollPapbBusySignal: Failed to read papb busy signal"
<< std::endl;
return returnvalue::FAILED;
}
if (papbBusyState == gpio::Levels::LOW) {
sif::warning << "PapbVcInterface::pollPapbBusySignal: PAPB busy" << std::endl;
return PAPB_BUSY;
}
return returnvalue::OK;
}
void PapbVcInterface::isVcInterfaceBufferEmpty() {
ReturnValue_t result = returnvalue::OK;
gpio::Levels papbEmptyState = gpio::Levels::HIGH;
result = gpioComIF->readGpio(papbEmptyId, papbEmptyState);
if (result != returnvalue::OK) {
sif::warning << "PapbVcInterface::isVcInterfaceBufferEmpty: Failed to read papb empty signal"
<< std::endl;
return;
}
if (papbEmptyState == gpio::Levels::HIGH) {
sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is empty" << std::endl;
} else {
sif::debug << "PapbVcInterface::isVcInterfaceBufferEmpty: Buffer is not empty" << std::endl;
}
return;
}
ReturnValue_t PapbVcInterface::sendTestFrame() {
/** Size of one complete transfer frame data field amounts to 1105 bytes */
uint8_t testPacket[1105];
/** Fill one test packet */
for (int idx = 0; idx < 1105; idx++) {
testPacket[idx] = static_cast<uint8_t>(idx & 0xFF);
}
ReturnValue_t result = write(testPacket, 1105);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}

View File

@ -0,0 +1,113 @@
#ifndef LINUX_OBC_PAPBVCINTERFACE_H_
#define LINUX_OBC_PAPBVCINTERFACE_H_
#include <fsfw_hal/common/gpio/gpioDefinitions.h>
#include <fsfw_hal/linux/gpio/LinuxLibgpioIF.h>
#include "OBSWConfig.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "linux/ipcore/VcInterfaceIF.h"
/**
* @brief This class handles the transmission of data to a virtual channel of the PTME IP Core
* via the PAPB interface.
*
* @author J. Meier
*/
class PapbVcInterface : public VcInterfaceIF {
public:
/**
* @brief Constructor
*
* @param papbBusyId The ID of the GPIO which is connected to the PAPBBusy_N signal of the
* VcInterface IP Core. A low logic level indicates the VcInterface is not
* ready to receive more data.
* @param papbEmptyId The ID of the GPIO which is connected to the PAPBEmpty signal of the
* VcInterface IP Core. The signal is high when there are no packets in the
* external buffer memory (BRAM).
* @param uioFile UIO file providing access to the PAPB bus
* @param mapNum Map number of UIO map associated with this virtual channel
*/
PapbVcInterface(LinuxLibgpioIF* gpioComIF, gpioId_t papbBusyId, gpioId_t papbEmptyId,
std::string uioFile, int mapNum);
virtual ~PapbVcInterface();
ReturnValue_t write(const uint8_t* data, size_t size) override;
ReturnValue_t initialize() override;
private:
static const uint8_t INTERFACE_ID = CLASS_ID::CCSDS_IP_CORE_BRIDGE;
static const ReturnValue_t PAPB_BUSY = MAKE_RETURN_CODE(0xA0);
/**
* Configuration bits:
* bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00
* bit[2]: Set this bit to 1 to abort a transfered packet
* bit[3]: Signals to VcInterface the start of a new telemetry packet
*/
static const uint32_t CONFIG_START = 0x8;
/**
* Writing this word to the VcInterface base address signals to the virtual channel interface
* that a complete tm packet has been transferred.
*/
static const uint32_t CONFIG_END = 0x0;
/**
* Writing to this offset within the memory space of a virtual channel will insert data for
* encoding to the external buffer memory of the PTME IP Core.
* The address offset is 0x400 (= 4 * 256)
*/
static const int DATA_REG_OFFSET = 256;
LinuxLibgpioIF* gpioComIF = nullptr;
/** Pulled to low when virtual channel not ready to receive data */
gpioId_t papbBusyId = gpio::NO_GPIO;
/** High when external buffer memory of virtual channel is empty */
gpioId_t papbEmptyId = gpio::NO_GPIO;
std::string uioFile;
int mapNum = 0;
uint32_t* vcBaseReg = nullptr;
uint32_t vcOffset = 0;
/**
* @brief This function sends the config byte to the virtual channel of the PTME IP Core
* to initiate a packet transfer.
*/
void startPacketTransfer();
/**
* @brief This function sends the config byte to the virtual channel interface of the PTME
* IP Core to signal the end of a packet transfer.
*/
void endPacketTransfer();
/**
* @brief This function reads the papb busy signal indicating whether the virtual channel
* interface is ready to receive more data or not. PAPB is ready when
* PAPB_Busy_N == '1'.
*
* @return returnvalue::OK when ready to receive data else PAPB_BUSY.
*/
ReturnValue_t pollPapbBusySignal();
/**
* @brief This function can be used for debugging to check whether there are packets in
* the packet buffer of the virtual channel or not.
*/
void isVcInterfaceBufferEmpty();
/**
* @brief This function sends a complete telemetry transfer frame data field (1105 bytes)
* to the papb interface of the PTME IP Core. Can be used to test the implementation.
*/
ReturnValue_t sendTestFrame();
};
#endif /* LINUX_OBC_PAPBVCINTERFACE_H_ */

View File

@ -0,0 +1,38 @@
#include "PdecConfig.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
PdecConfig::PdecConfig() { initialize(); }
PdecConfig::~PdecConfig() {}
void PdecConfig::initialize() {
uint32_t word = 0;
word |= (VERSION_ID << 30);
word |= (BYPASS_FLAG << 29);
word |= (CONTROL_COMMAND_FLAG << 28);
word |= (RESERVED_FIELD_A << 26);
word |= (SPACECRAFT_ID << 16);
word |= (VIRTUAL_CHANNEL << 10);
word |= (DUMMY_BITS << 8);
word |= POSITIVE_WINDOW;
configWords[0] = word;
word = 0;
word |= (NEGATIVE_WINDOW << 24);
word |= (HIGH_AU_MAP_ID << 16);
word |= (ENABLE_DERANDOMIZER << 8);
configWords[1] = word;
}
uint32_t PdecConfig::getConfigWord(uint8_t wordNo) {
if (wordNo >= CONFIG_WORDS_NUM) {
sif::error << "PdecConfig::getConfigWord: Invalid word number" << std::endl;
return 0;
}
return configWords[wordNo];
}
uint32_t PdecConfig::getImrReg() {
return static_cast<uint32_t>(enableNewFarIrq << 2) |
static_cast<uint32_t>(enableTcAbortIrq << 1) | static_cast<uint32_t>(enableTcNewIrq);
}

56
linux/ipcore/PdecConfig.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef LINUX_OBC_PDECCONFIG_H_
#define LINUX_OBC_PDECCONFIG_H_
#include <cstring>
#include "fsfw/returnvalues/returnvalue.h"
/**
* @brief This class generates the configuration words for the configuration memory of the PDEC
* IP Cores.
*
* @details Fields are initialized according to pecification in PDEC datasheet section 6.11.3.1
* PROM usage.
*
* @author J. Meier
*/
class PdecConfig {
public:
PdecConfig();
virtual ~PdecConfig();
/**
* @brief Returns the configuration word by specifying the position.
*/
uint32_t getConfigWord(uint8_t wordNo);
uint32_t getImrReg();
private:
// TC transfer frame configuration parameters
static const uint8_t VERSION_ID = 0;
// BD Frames
static const uint8_t BYPASS_FLAG = 1;
static const uint8_t CONTROL_COMMAND_FLAG = 0;
static const uint8_t VIRTUAL_CHANNEL = 0;
static const uint8_t RESERVED_FIELD_A = 0;
static const uint16_t SPACECRAFT_ID = 0x274;
static const uint16_t DUMMY_BITS = 0;
// Parameters to control the FARM for AD frames
// Set here for future use
static const uint8_t POSITIVE_WINDOW = 10;
static const uint8_t NEGATIVE_WINDOW = 151;
static const uint8_t HIGH_AU_MAP_ID = 0xF;
static const uint8_t ENABLE_DERANDOMIZER = 1;
static const uint8_t CONFIG_WORDS_NUM = 2;
uint32_t configWords[CONFIG_WORDS_NUM];
bool enableTcNewIrq = true;
bool enableTcAbortIrq = true;
bool enableNewFarIrq = true;
void initialize();
};
#endif /* LINUX_OBC_PDECCONFIG_H_ */

View File

@ -0,0 +1,636 @@
#include "PdecHandler.h"
#include <fcntl.h>
#include <poll.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
#include <sstream>
#include "OBSWConfig.h"
#include "fsfw/ipc/QueueFactory.h"
#include "fsfw/objectmanager/ObjectManager.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
#include "fsfw/tmtcservices/TmTcMessage.h"
#include "fsfw_hal/linux/uio/UioMapper.h"
#include "pdec.h"
using namespace pdec;
// If this is ever shared, protect it with a mutex!
uint32_t PdecHandler::CURRENT_FAR = 0;
PdecHandler::PdecHandler(object_id_t objectId, object_id_t tcDestinationId,
LinuxLibgpioIF* gpioComIF, gpioId_t pdecReset, UioNames names)
: SystemObject(objectId),
tcDestinationId(tcDestinationId),
gpioComIF(gpioComIF),
pdecReset(pdecReset),
actionHelper(this, nullptr),
uioNames(names) {
auto mqArgs = MqArgs(objectId, static_cast<void*>(this));
commandQueue = QueueFactory::instance()->createMessageQueue(
QUEUE_SIZE, MessageQueueMessage::MAX_MESSAGE_SIZE, &mqArgs);
}
PdecHandler::~PdecHandler() {}
ReturnValue_t PdecHandler::initialize() {
tcStore = ObjectManager::instance()->get<StorageManagerIF>(objects::TC_STORE);
if (tcStore == nullptr) {
sif::error << "PdecHandler::initialize: Invalid TC store" << std::endl;
return ObjectManagerIF::CHILD_INIT_FAILED;
}
tcDestination = ObjectManager::instance()->get<AcceptsTelecommandsIF>(tcDestinationId);
if (tcDestination == nullptr) {
sif::error << "PdecHandler::initialize: Invalid tc destination specified" << std::endl;
return ObjectManagerIF::CHILD_INIT_FAILED;
}
ReturnValue_t result = returnvalue::OK;
UioMapper regMapper(uioNames.registers);
result = regMapper.getMappedAdress(&registerBaseAddress, UioMapper::Permissions::READ_WRITE);
if (result != returnvalue::OK) {
return ObjectManagerIF::CHILD_INIT_FAILED;
}
UioMapper configMemMapper(uioNames.configMemory);
result = configMemMapper.getMappedAdress(&memoryBaseAddress, UioMapper::Permissions::READ_WRITE);
if (result != returnvalue::OK) {
return ObjectManagerIF::CHILD_INIT_FAILED;
}
UioMapper ramMapper(uioNames.ramMemory);
result = ramMapper.getMappedAdress(&ramBaseAddress, UioMapper::Permissions::READ_WRITE);
if (result != returnvalue::OK) {
return ObjectManagerIF::CHILD_INIT_FAILED;
}
if (OP_MODE == Modes::IRQ and uioNames.irq == nullptr) {
sif::error << "Can not use IRQ mode if IRQ UIO name is invalid" << std::endl;
return returnvalue::FAILED;
}
writePdecConfig();
result = releasePdec();
if (result != returnvalue::OK) {
return ObjectManagerIF::CHILD_INIT_FAILED;
}
result = actionHelper.initialize(commandQueue);
if (result != returnvalue::OK) {
return result;
}
return returnvalue::OK;
}
ReturnValue_t PdecHandler::performOperation(uint8_t operationCode) {
if (OP_MODE == Modes::POLLED) {
return polledOperation();
} else if (OP_MODE == Modes::IRQ) {
return irqOperation();
}
}
ReturnValue_t PdecHandler::polledOperation() {
ReturnValue_t result = returnvalue::OK;
readCommandQueue();
switch (state) {
case State::INIT:
resetFarStatFlag();
if (result != returnvalue::OK) {
// Requires reconfiguration and reinitialization of PDEC
triggerEvent(INVALID_FAR);
state = State::WAIT_FOR_RECOVERY;
return result;
}
state = State::RUNNING;
break;
case State::RUNNING:
if (newTcReceived()) {
handleNewTc();
}
checkLocks();
break;
case State::WAIT_FOR_RECOVERY:
break;
default:
sif::error << "PdecHandler::performOperation: Invalid state" << std::endl;
break;
}
return returnvalue::OK;
}
ReturnValue_t PdecHandler::irqOperation() {
ReturnValue_t result = returnvalue::OK;
int fd = open(uioNames.irq, O_RDWR);
if (fd < 0) {
sif::error << "PdecHandler::irqOperation: Opening UIO IRQ file" << uioNames.irq << " failed"
<< std::endl;
return returnvalue::FAILED;
}
struct pollfd fds = {.fd = fd, .events = POLLIN, .revents = 0};
// Used to unmask IRQ
uint32_t info = 1;
ssize_t nb = 0;
int ret = 0;
while (true) {
readCommandQueue();
switch (state) {
case State::INIT:
resetFarStatFlag();
if (result != returnvalue::OK) {
// Requires reconfiguration and reinitialization of PDEC
triggerEvent(INVALID_FAR);
state = State::WAIT_FOR_RECOVERY;
return result;
}
state = State::RUNNING;
break;
case State::RUNNING: {
// TODO: Add poll() based IRQ handling
nb = write(fd, &info, sizeof(info));
if (nb != static_cast<ssize_t>(sizeof(info))) {
sif::error << "PdecHandler::irqOperation: Unmasking IRQ failed" << std::endl;
close(fd);
}
ret = poll(&fds, 1, IRQ_TIMEOUT_MS);
if (ret == 0) {
// No TCs for timeout period
checkLocks();
lockCheckCd.resetTimer();
} else if (ret >= 1) {
nb = read(fd, &info, sizeof(info));
if (nb == static_cast<ssize_t>(sizeof(info))) {
uint32_t pisr = *(registerBaseAddress + PDEC_PISR_OFFSET);
if ((pisr & TC_NEW_MASK) == TC_NEW_MASK) {
// handle TC
handleNewTc();
}
if ((pisr & TC_ABORT_MASK) == TC_ABORT_MASK) {
tcAbortCounter += 1;
}
if ((pisr & NEW_FAR_MASK) == NEW_FAR_MASK) {
// Read FAR here
CURRENT_FAR = readFar();
}
if (lockCheckCd.hasTimedOut()) {
checkLocks();
lockCheckCd.resetTimer();
}
// Clear interrupts with dummy read
ret = *(registerBaseAddress + PDEC_PIR_OFFSET);
}
} else {
sif::error << "PdecHandler::irqOperation: Poll error with errno " << errno << ": "
<< strerror(errno) << std::endl;
triggerEvent(POLL_ERROR_PDEC, errno);
}
break;
}
case State::WAIT_FOR_RECOVERY:
break;
default:
sif::error << "PdecHandler::performOperation: Invalid state" << std::endl;
break;
}
}
return returnvalue::OK;
}
void PdecHandler::readCommandQueue(void) {
CommandMessage commandMessage;
ReturnValue_t result = returnvalue::FAILED;
result = commandQueue->receiveMessage(&commandMessage);
if (result == returnvalue::OK) {
result = actionHelper.handleActionMessage(&commandMessage);
if (result == returnvalue::OK) {
return;
}
CommandMessage reply;
reply.setReplyRejected(CommandMessage::UNKNOWN_COMMAND, commandMessage.getCommand());
commandQueue->reply(&reply);
return;
}
}
MessageQueueId_t PdecHandler::getCommandQueue() const { return commandQueue->getId(); }
void PdecHandler::writePdecConfig() {
PdecConfig pdecConfig;
*(memoryBaseAddress + FRAME_HEADER_OFFSET) = pdecConfig.getConfigWord(0);
*(memoryBaseAddress + FRAME_HEADER_OFFSET + 1) = pdecConfig.getConfigWord(1);
if (OP_MODE == Modes::IRQ) {
// Configure interrupt mask register to enable interrupts
*(registerBaseAddress + PDEC_IMR_OFFSET) = pdecConfig.getImrReg();
}
// Configure all MAP IDs as invalid
for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) {
*(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + idx / 4) =
NO_DESTINATION << 24 | NO_DESTINATION << 16 | NO_DESTINATION << 8 | NO_DESTINATION;
}
// All TCs with MAP ID 7 will be routed to the PM module (can then be read from memory)
uint8_t routeToPm = calcMapAddrEntry(PM_BUFFER);
*(memoryBaseAddress + MAP_ADDR_LUT_OFFSET + 1) =
(NO_DESTINATION << 24) | (NO_DESTINATION << 16) | (NO_DESTINATION << 8) | routeToPm;
// Write map id clock frequencies
for (int idx = 0; idx <= MAX_MAP_ADDR; idx += 4) {
*(memoryBaseAddress + MAP_CLK_FREQ_OFFSET + idx / 4) =
MAP_CLK_FREQ << 24 | MAP_CLK_FREQ << 16 | MAP_CLK_FREQ << 8 | MAP_CLK_FREQ;
}
}
ReturnValue_t PdecHandler::resetFarStatFlag() {
uint32_t pdecFar = readFar();
if (pdecFar != FAR_RESET) {
sif::warning << "PdecHandler::resetFarStatFlag: FAR register did not match expected value."
<< " Read value: 0x" << std::hex << static_cast<unsigned int>(pdecFar)
<< std::endl;
CURRENT_FAR = pdecFar;
return returnvalue::FAILED;
}
#if OBSW_DEBUG_PDEC_HANDLER == 1
sif::debug << "PdecHandler::resetFarStatFlag: read FAR with value: 0x" << std::hex << pdecFar
<< std::endl;
#endif /* OBSW_DEBUG_PDEC_HANDLER == 1 */
CURRENT_FAR = pdecFar;
return returnvalue::OK;
}
ReturnValue_t PdecHandler::releasePdec() {
ReturnValue_t result = returnvalue::OK;
result = gpioComIF->pullHigh(pdecReset);
if (result != returnvalue::OK) {
sif::error << "PdecHandler::releasePdec: Failed to release PDEC reset signal" << std::endl;
}
return result;
}
bool PdecHandler::newTcReceived() {
uint32_t pdecFar = readFar();
if (pdecFar >> STAT_POSITION != NEW_FAR_RECEIVED) {
CURRENT_FAR = pdecFar;
return false;
}
if (!checkFrameAna(pdecFar)) {
CURRENT_FAR = pdecFar;
return false;
}
return true;
}
void PdecHandler::checkLocks() {
uint32_t clcw = getClcw();
if (not(clcw & NO_RF_MASK) && not carrierLock) {
triggerEvent(CARRIER_LOCK);
carrierLock = true;
} else if ((clcw & NO_RF_MASK) && carrierLock) {
carrierLock = false;
triggerEvent(LOST_CARRIER_LOCK_PDEC);
}
if (not(clcw & NO_BITLOCK_MASK) && not bitLock) {
triggerEvent(BIT_LOCK_PDEC);
bitLock = true;
} else if ((clcw & NO_BITLOCK_MASK) && bitLock) {
bitLock = false;
triggerEvent(LOST_BIT_LOCK_PDEC);
}
}
bool PdecHandler::checkFrameAna(uint32_t pdecFar) {
bool frameValid = false;
FrameAna_t frameAna = static_cast<FrameAna_t>((pdecFar & FRAME_ANA_MASK) >> FRAME_ANA_POSITION);
switch (frameAna) {
case (FrameAna_t::ABANDONED_CLTU): {
triggerEvent(INVALID_TC_FRAME, ABANDONED_CLTU);
sif::warning << "PdecHandler::checkFrameAna: Abondoned CLTU" << std::endl;
break;
}
case (FrameAna_t::FRAME_DIRTY): {
triggerEvent(INVALID_TC_FRAME, FRAME_DIRTY);
sif::warning << "PdecHandler::checkFrameAna: Frame dirty" << std::endl;
break;
}
case (FrameAna_t::FRAME_ILLEGAL): {
sif::warning << "PdecHandler::checkFrameAna: Frame illegal for one reason" << std::endl;
handleIReason(pdecFar, FRAME_ILLEGAL_ONE_REASON);
break;
}
case (FrameAna_t::FRAME_ILLEGAL_MULTI_REASON): {
sif::warning << "PdecHandler::checkFrameAna: Frame illegal for multiple reasons" << std::endl;
handleIReason(pdecFar, FRAME_ILLEGAL_MULTIPLE_REASONS);
break;
}
case (FrameAna_t::AD_DISCARDED_LOCKOUT): {
triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT);
sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of lockout"
<< std::endl;
break;
}
case (FrameAna_t::AD_DISCARDED_WAIT): {
triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_LOCKOUT);
sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because of wait" << std::endl;
break;
}
case (FrameAna_t::AD_DISCARDED_NS_VR): {
triggerEvent(INVALID_TC_FRAME, AD_DISCARDED_NS_VS);
sif::warning << "PdecHandler::checkFrameAna: AD frame discarded because N(S) or V(R)"
<< std::endl;
break;
}
case (FrameAna_t::FRAME_ACCEPTED): {
#if OBSW_DEBUG_PDEC_HANDLER == 1
sif::info << "PdecHandler::checkFrameAna: Accepted TC frame" << std::endl;
#endif
frameValid = true;
break;
}
default: {
sif::debug << "PdecHandler::checkFrameAna: Invalid frame analysis report" << std::endl;
break;
}
}
return frameValid;
}
void PdecHandler::handleIReason(uint32_t pdecFar, ReturnValue_t parameter1) {
IReason_t ireason = static_cast<IReason_t>((pdecFar & IREASON_MASK) >> IREASON_POSITION);
switch (ireason) {
case (IReason_t::NO_REPORT): {
triggerEvent(INVALID_TC_FRAME, parameter1, NO_REPORT);
sif::info << "PdecHandler::handleIReason: No illegal report" << std::endl;
break;
}
case (IReason_t::ERROR_VERSION_NUMBER): {
triggerEvent(INVALID_TC_FRAME, parameter1, ERROR_VERSION_NUMBER);
sif::info << "PdecHandler::handleIReason: Error in version number and reserved A and B "
<< "fields" << std::endl;
break;
}
case (IReason_t::ILLEGAL_COMBINATION): {
triggerEvent(INVALID_TC_FRAME, parameter1, ILLEGAL_COMBINATION);
sif::info << "PdecHandler::handleIReason: Illegal combination (AC) of bypass and control "
<< "command flags" << std::endl;
break;
}
case (IReason_t::INVALID_SC_ID): {
triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_SC_ID);
sif::info << "PdecHandler::handleIReason: Invalid spacecraft identifier " << std::endl;
break;
}
case (IReason_t::INVALID_VC_ID_MSB): {
triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_MSB);
sif::info << "PdecHandler::handleIReason: VC identifier bit 0 to 4 did not match "
<< std::endl;
break;
}
case (IReason_t::INVALID_VC_ID_LSB): {
triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_VC_ID_LSB);
sif::info << "PdecHandler::handleIReason: VC identifier bit 5 did not match " << std::endl;
break;
}
case (IReason_t::NS_NOT_ZERO): {
triggerEvent(INVALID_TC_FRAME, parameter1, NS_NOT_ZERO);
sif::info << "PdecHandler::handleIReason: N(S) of BC or BD frame not set to all zeros"
<< std::endl;
break;
}
case (IReason_t::INCORRECT_BC_CC): {
triggerEvent(INVALID_TC_FRAME, parameter1, INVALID_BC_CC);
sif::info << "PdecHandler::handleIReason: Invalid BC control command format" << std::endl;
break;
}
default: {
sif::info << "PdecHandler::handleIReason: Invalid reason id" << std::endl;
break;
}
}
}
void PdecHandler::handleNewTc() {
ReturnValue_t result = returnvalue::OK;
uint32_t tcLength = 0;
result = readTc(tcLength);
if (result != returnvalue::OK) {
return;
}
#if OBSW_DEBUG_PDEC_HANDLER == 1
unsigned int mapId = tcSegment[0] & MAP_ID_MASK;
sif::info << "PdecHandler::handleNewTc: Received TC segment with map ID " << mapId << std::endl;
printTC(tcLength);
#endif /* OBSW_DEBUG_PDEC_HANDLER */
store_address_t storeId;
result = tcStore->addData(&storeId, tcSegment + 1, tcLength - 1);
if (result != returnvalue::OK) {
sif::warning << "PdecHandler::handleNewTc: Failed to add received space packet to store"
<< std::endl;
return;
}
TmTcMessage message(storeId);
result = MessageQueueSenderIF::sendMessage(tcDestination->getRequestQueue(), &message);
if (result != returnvalue::OK) {
sif::warning << "PdecHandler::handleNewTc: Failed to send message to TC destination"
<< std::endl;
tcStore->deleteData(storeId);
return;
}
return;
}
ReturnValue_t PdecHandler::readTc(uint32_t& tcLength) {
uint32_t tcOffset = (*(registerBaseAddress + PDEC_BPTR_OFFSET) - PHYSICAL_RAM_BASE_ADDRESS) / 4;
#if OBSW_DEBUG_PDEC_HANDLER == 1
sif::debug << "PdecHandler::readTc: TC offset: 0x" << std::hex << tcOffset << std::endl;
#endif /* OBSW_DEBUG_PDEC_HANDLER */
tcLength = *(registerBaseAddress + PDEC_SLEN_OFFSET);
#if OBSW_DEBUG_PDEC_HANDLER == 1
sif::debug << "PdecHandler::readTc: TC segment length: " << std::dec << tcLength << std::endl;
#endif /* OBSW_DEBUG_PDEC_HANDLER */
if (tcLength > MAX_TC_SEGMENT_SIZE) {
sif::warning << "PdecHandler::handleNewTc: Read invalid TC length from PDEC register"
<< std::endl;
return returnvalue::FAILED;
}
uint32_t idx = 0;
uint32_t tcData = 0;
for (idx = 0; idx <= tcLength; idx = idx + 4) {
tcData = *(ramBaseAddress + tcOffset + idx / 4);
if (idx == 0) {
tcSegment[idx] = static_cast<uint8_t>((tcData >> 16) & 0xFF);
tcSegment[idx + 1] = static_cast<uint8_t>((tcData >> 8) & 0xFF);
tcSegment[idx + 2] = static_cast<uint8_t>(tcData & 0xFF);
} else if (tcLength - idx + 1 == 3) {
tcSegment[idx - 1] = static_cast<uint8_t>((tcData >> 24) & 0xFF);
tcSegment[idx] = static_cast<uint8_t>((tcData >> 16) & 0xFF);
tcSegment[idx + 1] = static_cast<uint8_t>((tcData >> 8) & 0xFF);
} else if (tcLength - idx + 1 == 2) {
tcSegment[idx - 1] = static_cast<uint8_t>((tcData >> 24) & 0xFF);
tcSegment[idx] = static_cast<uint8_t>((tcData >> 16) & 0xFF);
} else if (tcLength - idx + 1 == 1) {
tcSegment[idx - 1] = static_cast<uint8_t>((tcData >> 24) & 0xFF);
} else {
tcSegment[idx - 1] = static_cast<uint8_t>((tcData >> 24) & 0xFF);
tcSegment[idx] = static_cast<uint8_t>((tcData >> 16) & 0xFF);
tcSegment[idx + 1] = static_cast<uint8_t>((tcData >> 8) & 0xFF);
tcSegment[idx + 2] = static_cast<uint8_t>(tcData & 0xFF);
}
}
// Backend buffer is handled back to PDEC3
*(registerBaseAddress + PDEC_BFREE_OFFSET) = 0;
return returnvalue::OK;
}
void PdecHandler::printTC(uint32_t tcLength) {
std::stringstream tcSegmentStream;
tcSegmentStream << "TC segment data: 0x";
for (uint32_t idx = 0; idx < tcLength; idx++) {
tcSegmentStream << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<unsigned int>(tcSegment[idx]);
}
sif::info << tcSegmentStream.str() << std::endl;
}
uint8_t PdecHandler::calcMapAddrEntry(uint8_t moduleId) {
uint8_t lutEntry = 0;
uint8_t parity = getOddParity(moduleId | (1 << VALID_POSITION));
lutEntry = (parity << PARITY_POSITION) | (1 << VALID_POSITION) | moduleId;
return lutEntry;
}
uint8_t PdecHandler::getOddParity(uint8_t number) {
uint8_t parityBit = 0;
uint8_t countBits = 0;
for (unsigned int idx = 0; idx < sizeof(number) * 8; idx++) {
countBits += (number >> idx) & 0x1;
}
parityBit = ~(countBits & 0x1) & 0x1;
return parityBit;
}
uint32_t PdecHandler::getClcw() { return *(registerBaseAddress + PDEC_CLCW_OFFSET); }
uint32_t PdecHandler::getPdecMon() { return *(registerBaseAddress + PDEC_MON_OFFSET); }
void PdecHandler::printClcw() {
uint32_t clcw = getClcw();
uint8_t type = static_cast<uint8_t>((clcw >> 31) & 0x1);
uint8_t versionNo = static_cast<uint8_t>((clcw >> 29) & 0x3);
uint8_t status = static_cast<uint8_t>((clcw >> 26) & 0x7);
uint8_t cop = static_cast<uint8_t>((clcw >> 24) & 0x3);
uint8_t vcId = static_cast<uint8_t>((clcw >> 18) & 0x3F);
uint8_t noRf = static_cast<uint8_t>((clcw >> 15) & 0x1);
uint8_t noBitLock = static_cast<uint8_t>((clcw >> 14) & 0x1);
uint8_t lockoutFlag = static_cast<uint8_t>((clcw >> 13) & 0x1);
uint8_t waitFlag = static_cast<uint8_t>((clcw >> 12) & 0x1);
uint8_t retransmitFlag = static_cast<uint8_t>((clcw >> 11) & 0x1);
uint8_t farmBcnt = static_cast<uint8_t>((clcw >> 9) & 0x3);
// Expected frame sequence number in te next AD frame
uint8_t repValue = static_cast<uint8_t>(clcw & 0xFF);
sif::info << std::setw(30) << std::left << "CLCW type: " << std::hex << "0x"
<< static_cast<unsigned int>(type) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW version no: " << std::hex << "0x"
<< static_cast<unsigned int>(versionNo) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW status: " << std::hex << "0x"
<< static_cast<unsigned int>(status) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW COP: " << std::hex << "0x"
<< static_cast<unsigned int>(cop) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW virtual channel ID: " << std::hex << "0x"
<< static_cast<unsigned int>(vcId) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW no RF: " << std::hex << "0x"
<< static_cast<unsigned int>(noRf) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW no bit lock: " << std::hex << "0x"
<< static_cast<unsigned int>(noBitLock) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW lockout flag: " << std::hex << "0x"
<< static_cast<unsigned int>(lockoutFlag) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW wait flag: " << std::hex << "0x"
<< static_cast<unsigned int>(waitFlag) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW retransmit flag: " << std::hex << "0x"
<< static_cast<unsigned int>(retransmitFlag) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW FARM B count: " << std::hex << "0x"
<< static_cast<unsigned int>(farmBcnt) << std::endl;
sif::info << std::setw(30) << std::left << "CLCW rep value: " << std::hex << "0x"
<< static_cast<unsigned int>(repValue) << std::endl;
}
void PdecHandler::printPdecMon() {
uint32_t pdecMon = getPdecMon();
uint32_t tc0ChannelStatus = (pdecMon & TC0_STATUS_MASK) >> TC0_STATUS_POS;
uint32_t tc1ChannelStatus = (pdecMon & TC1_STATUS_MASK) >> TC1_STATUS_POS;
uint32_t tc2ChannelStatus = (pdecMon & TC2_STATUS_MASK) >> TC2_STATUS_POS;
uint32_t tc3ChannelStatus = (pdecMon & TC3_STATUS_MASK) >> TC3_STATUS_POS;
uint32_t tc4ChannelStatus = (pdecMon & TC4_STATUS_MASK) >> TC4_STATUS_POS;
uint32_t tc5ChannelStatus = (pdecMon & TC5_STATUS_MASK) >> TC5_STATUS_POS;
uint32_t lock = (pdecMon & LOCK_MASK) >> LOCK_POS;
sif::info << std::setw(30) << std::left << "TC0 status: " << getMonStatusString(tc0ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "TC1 status: " << getMonStatusString(tc1ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "TC2 status: " << getMonStatusString(tc2ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "TC3 status: " << getMonStatusString(tc3ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "TC4 status: " << getMonStatusString(tc4ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "TC5 status: " << getMonStatusString(tc5ChannelStatus)
<< std::endl;
sif::info << std::setw(30) << std::left << "Start sequence lock: " << lock << std::endl;
}
uint32_t PdecHandler::readFar() { return *(registerBaseAddress + PDEC_FAR_OFFSET); }
std::string PdecHandler::getMonStatusString(uint32_t status) {
switch (status) {
case TC_CHANNEL_INACTIVE:
return std::string("inactive");
case TC_CHANNEL_ACTIVE:
return std::string("active");
case TC_CHANNEL_TIMEDOUT:
return std::string("timed out");
default:
sif::warning << "PdecHandler::getMonStatusString: Invalid status" << std::endl;
return std::string();
break;
}
}
ReturnValue_t PdecHandler::executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
const uint8_t* data, size_t size) {
switch (actionId) {
case PRINT_CLCW:
printClcw();
return EXECUTION_FINISHED;
case PRINT_PDEC_MON:
printPdecMon();
return EXECUTION_FINISHED;
default:
return COMMAND_NOT_IMPLEMENTED;
}
}

374
linux/ipcore/PdecHandler.h Normal file
View File

@ -0,0 +1,374 @@
#ifndef LINUX_OBC_PDECHANDLER_H_
#define LINUX_OBC_PDECHANDLER_H_
#include <fsfw/timemanager/Countdown.h>
#include "OBSWConfig.h"
#include "PdecConfig.h"
#include "fsfw/action/ActionHelper.h"
#include "fsfw/action/HasActionsIF.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "fsfw/storagemanager/StorageManagerIF.h"
#include "fsfw/tasks/ExecutableObjectIF.h"
#include "fsfw/tmtcservices/AcceptsTelecommandsIF.h"
#include "fsfw_hal/common/gpio/gpioDefinitions.h"
#include "fsfw_hal/linux/gpio/LinuxLibgpioIF.h"
struct UioNames {
const char* configMemory;
const char* ramMemory;
const char* registers;
const char* irq;
};
/**
* @brief This class controls the PDEC IP Core implemented in the programmable logic of the
* Zynq-7020. All registers and memories of the PDEC IP Core are accessed via UIO
* drivers.
*
* @details The PDEC IP Core is responsible for processing data received in form of CLTUs from the
* S-Band transceiver. This comprises the BCH decoding of the CLTUs and reconstruction of
* telecommand transfer frames. Finally the PDEC stores the TC segment transported with
* the TC transfer frame in a register. As soon as a new TC has been received a new
* frame acceptance report (FAR) will be generated. If the FAR confirms the validity of
* a received TC segment, the data can be read out from the associated register.
* Currently, the ground software only supports transmissions of CLTUs containing one
* space packet.
* Link to datasheet of PDEC IP Core: https://eive-cloud.irs.uni-stuttgart.de/index.php/
* apps/files/?dir=/EIVE_IRS/Arbeitsdaten/08_Used%20Components/CCSDS_IP_Cores&fileid=1108967
*
* @author J. Meier
*/
class PdecHandler : public SystemObject, public ExecutableObjectIF, public HasActionsIF {
public:
static constexpr dur_millis_t IRQ_TIMEOUT_MS = 500;
enum class Modes { POLLED, IRQ };
/**
* @brief Constructor
* @param objectId Object ID of PDEC handler system object
* @param tcDestinationId Object ID of object responsible for processing TCs.
* @param gpioComIF Pointer to GPIO interace responsible for driving GPIOs.
* @param pdecReset GPIO ID of GPIO connected to the reset signal of the PDEC.
* @param uioConfigMemory String of uio device file same mapped to the PDEC memory space
* @param uioregsiters String of uio device file same mapped to the PDEC register space
*/
PdecHandler(object_id_t objectId, object_id_t tcDestinationId, LinuxLibgpioIF* gpioComIF,
gpioId_t pdecReset, UioNames names);
virtual ~PdecHandler();
ReturnValue_t performOperation(uint8_t operationCode = 0);
ReturnValue_t initialize() override;
MessageQueueId_t getCommandQueue() const;
ReturnValue_t executeAction(ActionId_t actionId, MessageQueueId_t commandedBy,
const uint8_t* data, size_t size) override;
static const uint8_t SUBSYSTEM_ID = SUBSYSTEM_ID::PDEC_HANDLER;
//! [EXPORT] : [COMMENT] Frame acceptance report signals an invalid frame
//! P1: The frame analysis information (FrameAna field of PDEC_FAR register)
//! P2: When frame declared illegal this parameter this parameter gives information about the
//! reason (IReason field of the PDEC_FAR register)
static const Event INVALID_TC_FRAME = MAKE_EVENT(1, severity::HIGH);
//! [EXPORT] : [COMMENT] Read invalid FAR from PDEC after startup
static const Event INVALID_FAR = MAKE_EVENT(2, severity::HIGH);
//! [EXPORT] : [COMMENT] Carrier lock detected
static const Event CARRIER_LOCK = MAKE_EVENT(3, severity::INFO);
//! [EXPORT] : [COMMENT] Bit lock detected (data valid)
static const Event BIT_LOCK_PDEC = MAKE_EVENT(4, severity::INFO);
//! [EXPORT] : [COMMENT] Lost carrier lock
static const Event LOST_CARRIER_LOCK_PDEC = MAKE_EVENT(5, severity::INFO);
//! [EXPORT] : [COMMENT] Lost bit lock
static const Event LOST_BIT_LOCK_PDEC = MAKE_EVENT(6, severity::INFO);
static constexpr Event POLL_ERROR_PDEC = event::makeEvent(SUBSYSTEM_ID, 7, severity::MEDIUM);
private:
static const uint8_t INTERFACE_ID = CLASS_ID::PDEC_HANDLER;
static constexpr Modes OP_MODE = Modes::POLLED;
static const ReturnValue_t ABANDONED_CLTU = MAKE_RETURN_CODE(0xA0);
static const ReturnValue_t FRAME_DIRTY = MAKE_RETURN_CODE(0xA1);
static const ReturnValue_t FRAME_ILLEGAL_ONE_REASON = MAKE_RETURN_CODE(0xA2);
static const ReturnValue_t FRAME_ILLEGAL_MULTIPLE_REASONS = MAKE_RETURN_CODE(0xA2);
static const ReturnValue_t AD_DISCARDED_LOCKOUT = MAKE_RETURN_CODE(0xA3);
static const ReturnValue_t AD_DISCARDED_WAIT = MAKE_RETURN_CODE(0xA4);
static const ReturnValue_t AD_DISCARDED_NS_VS = MAKE_RETURN_CODE(0xA5);
//! [EXPORT] : [COMMENT] Received action message with unknown action id
static const ReturnValue_t COMMAND_NOT_IMPLEMENTED = MAKE_RETURN_CODE(0xB0);
static const ReturnValue_t NO_REPORT = MAKE_RETURN_CODE(0xA6);
//! Error in version number and reserved A and B fields
static const ReturnValue_t ERROR_VERSION_NUMBER = MAKE_RETURN_CODE(0xA7);
//! Illegal combination of bypass and control command flag
static const ReturnValue_t ILLEGAL_COMBINATION = MAKE_RETURN_CODE(0xA8);
//! Spacecraft identifier did not match
static const ReturnValue_t INVALID_SC_ID = MAKE_RETURN_CODE(0xA9);
//! VC identifier bits 0 to 4 did not match
static const ReturnValue_t INVALID_VC_ID_MSB = MAKE_RETURN_CODE(0xAA);
//! VC identifier bit 5 did not match
static const ReturnValue_t INVALID_VC_ID_LSB = MAKE_RETURN_CODE(0xAB);
//! N(S) of BC or BD frame not set to all zeros
static const ReturnValue_t NS_NOT_ZERO = MAKE_RETURN_CODE(0xAC);
//! Invalid BC control command
static const ReturnValue_t INVALID_BC_CC = MAKE_RETURN_CODE(0xAE);
static const uint32_t QUEUE_SIZE = common::CCSDS_HANDLER_QUEUE_SIZE;
// Action IDs
static const ActionId_t PRINT_CLCW = 0;
// Print PDEC monitor register
static const ActionId_t PRINT_PDEC_MON = 1;
#ifdef TE0720_1CFA
static const int CONFIG_MEMORY_MAP_SIZE = 0x400;
static const int RAM_MAP_SIZE = 0x4000;
static const int REGISTER_MAP_SIZE = 0x10000;
#else
static const int CONFIG_MEMORY_MAP_SIZE = 0x400;
static const int RAM_MAP_SIZE = 0x4000;
static const int REGISTER_MAP_SIZE = 0x4000;
#endif /* BOARD_TE0720 == 1 */
// 0x200 / 4 = 0x80
static const uint32_t FRAME_HEADER_OFFSET = 0x80;
static const size_t MAX_TC_SEGMENT_SIZE = 1017;
static const uint8_t MAP_ID_MASK = 0x3F;
#ifdef TE0720_1CFA
static const uint32_t PHYSICAL_RAM_BASE_ADDRESS = 0x32000000;
#else
static const uint32_t PHYSICAL_RAM_BASE_ADDRESS = 0x26000000;
#endif
static const uint32_t MAP_ADDR_LUT_OFFSET = 0xA0;
static const uint32_t MAP_CLK_FREQ_OFFSET = 0x90;
static const uint8_t MAX_MAP_ADDR = 63;
// Writing this to the map address in the look up table will invalidate a MAP ID.
static const uint8_t NO_DESTINATION = 0;
static const uint8_t VALID_POSITION = 6;
static const uint8_t PARITY_POSITION = 7;
// Expected value stored in FAR register after reset
static const uint32_t FAR_RESET = 0x7FE0;
static const uint32_t TC_SEGMENT_LEN = 1017;
static const uint32_t NO_RF_MASK = 0x8000;
static const uint32_t NO_BITLOCK_MASK = 0x4000;
/**
* TCs with map addresses (also know as Map IDs) assigned to this channel will be stored in
* the PDEC memory.
*/
static const uint8_t PM_BUFFER = 7;
// MAP clock frequency. Must be a value between 1 and 13 otherwise the TC segment will be
// discarded
static const uint8_t MAP_CLK_FREQ = 2;
enum class FrameAna_t : uint8_t {
ABANDONED_CLTU,
FRAME_DIRTY,
FRAME_ILLEGAL,
FRAME_ILLEGAL_MULTI_REASON,
AD_DISCARDED_LOCKOUT,
AD_DISCARDED_WAIT,
AD_DISCARDED_NS_VR,
FRAME_ACCEPTED
};
enum class IReason_t : uint8_t {
NO_REPORT,
ERROR_VERSION_NUMBER,
ILLEGAL_COMBINATION,
INVALID_SC_ID,
INVALID_VC_ID_LSB,
INVALID_VC_ID_MSB,
NS_NOT_ZERO,
INCORRECT_BC_CC
};
enum class State : uint8_t { INIT, RUNNING, WAIT_FOR_RECOVERY };
static uint32_t CURRENT_FAR;
Countdown lockCheckCd = Countdown(IRQ_TIMEOUT_MS);
object_id_t tcDestinationId;
AcceptsTelecommandsIF* tcDestination = nullptr;
LinuxLibgpioIF* gpioComIF = nullptr;
/**
* Reset signal is required to hold PDEC in reset state until the configuration has been
* written to the appropriate memory space.
* Can also be used to reboot PDEC in case of erros.
*/
gpioId_t pdecReset = gpio::NO_GPIO;
uint32_t tcAbortCounter = 0;
ActionHelper actionHelper;
StorageManagerIF* tcStore = nullptr;
MessageQueueIF* commandQueue = nullptr;
State state = State::INIT;
/**
* Pointer pointing to base address of the PDEC memory space.
* This address is equivalent with the base address of the section named configuration area in
* the PDEC datasheet.
*/
uint32_t* memoryBaseAddress = nullptr;
uint32_t* ramBaseAddress = nullptr;
// Pointer pointing to base address of register space
uint32_t* registerBaseAddress = nullptr;
uint8_t tcSegment[TC_SEGMENT_LEN];
// Used to check carrier and bit lock changes (default set to no rf and no bitlock)
uint32_t lastClcw = 0xC000;
bool carrierLock = false;
bool bitLock = false;
UioNames uioNames;
/**
* @brief Reads and handles messages stored in the commandQueue
*/
void readCommandQueue(void);
ReturnValue_t polledOperation();
ReturnValue_t irqOperation();
uint32_t readFar();
/**
* @brief This functions writes the configuration parameters to the configuration
* section of the PDEC.
*/
void writePdecConfig();
/**
* @brief Reading the FAR resets the set stat flag which signals a new TC. Without clearing
* this flag no new TC will be excepted. After start up the flag is set and needs
* to be reset.
* Stat flag 0 - new TC received
* Stat flag 1 - old TC (ready to receive next TC)
*/
ReturnValue_t resetFarStatFlag();
/**
* @brief Releases the PDEC from reset state. PDEC will start with loading the written
* configuration parameters.
*/
ReturnValue_t releasePdec();
/**
* @brief Reads the FAR register and checks if a new TC has been received.
*/
bool newTcReceived();
/**
* @brief Checks if carrier lock or bit lock has been detected and triggers appropriate
* event.
*/
void checkLocks();
/**
* @brief Analyzes the FramAna field (frame analysis data) of a FAR report.
*
* @return True if frame valid, otherwise false.
*/
bool checkFrameAna(uint32_t pdecFar);
/**
* @brief This function handles the IReason field of the frame analysis report.
*
* @details In case frame as been declared illegal for multiple reasons, the reason with the
* lowest value will be shown.
*/
void handleIReason(uint32_t pdecFar, ReturnValue_t parameter1);
/**
* @brief Handles the reception of new TCs. Reads the pointer to the storage location of the
* new TC segment, extracts the PUS packet and forwards the data to the object
* responsible for processing the TC.
*/
void handleNewTc();
/**
* @brief Function reads the last received TC segment from the PDEC memory and copies
* the data to the tcSegement array.
*
* @param tcLength The length of the received TC.
*
*/
ReturnValue_t readTc(uint32_t& tcLength);
/**
* @brief Prints the tc segment data
*/
void printTC(uint32_t tcLength);
/**
* @brief This function calculates the entry for the configuration of the MAP ID routing.
*
* @param mapAddr The MAP ID to configure
* @param moduleId The destination module where all TCs with the map id mapAddr will be routed
* to.
*
* @details The PDEC has different modules where the TCs can be routed to. A lookup table is
* used which links the MAP ID field to the destination module. The entry for this
* lookup table is created by this function and must be stored in the configuration
* memory region of the PDEC. The entry has a specific format
*/
uint8_t calcMapAddrEntry(uint8_t moduleId);
/**
* @brief This functions calculates the odd parity of the bits in number.
*
* @param number The number from which to calculate the odd parity.
*/
uint8_t getOddParity(uint8_t number);
/**
* brief Returns the 32-bit wide communication link control word (CLCW)
*/
uint32_t getClcw();
/**
* @brief Returns the PDEC monitor register content
*
*/
uint32_t getPdecMon();
/**
* @brief Reads and prints the CLCW. Can be useful for debugging.
*/
void printClcw();
/**
* @brief Prints monitor register information to debug console.
*/
void printPdecMon();
std::string getMonStatusString(uint32_t status);
};
#endif /* LINUX_OBC_PDECHANDLER_H_ */

52
linux/ipcore/Ptme.cpp Normal file
View File

@ -0,0 +1,52 @@
#include <fcntl.h>
#include <linux/ipcore/Ptme.h>
#include <sys/mman.h>
#include <unistd.h>
#include "PtmeConfig.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
Ptme::Ptme(object_id_t objectId) : SystemObject(objectId) {}
Ptme::~Ptme() {}
ReturnValue_t Ptme::initialize() {
VcInterfaceMapIter iter;
for (iter = vcInterfaceMap.begin(); iter != vcInterfaceMap.end(); iter++) {
iter->second->initialize();
}
return returnvalue::OK;
}
ReturnValue_t Ptme::writeToVc(uint8_t vcId, const uint8_t* data, size_t size) {
ReturnValue_t result = returnvalue::OK;
VcInterfaceMapIter vcInterfaceMapIter = vcInterfaceMap.find(vcId);
if (vcInterfaceMapIter == vcInterfaceMap.end()) {
sif::warning << "Ptme::writeToVc: No virtual channel interface found for the virtual "
"channel with id "
<< static_cast<unsigned int>(vcId) << std::endl;
return UNKNOWN_VC_ID;
}
result = vcInterfaceMapIter->second->write(data, size);
return result;
}
void Ptme::addVcInterface(VcId_t vcId, VcInterfaceIF* vc) {
if (vcId > common::NUMBER_OF_VIRTUAL_CHANNELS) {
sif::warning << "Ptme::addVcInterface: Invalid virtual channel ID" << std::endl;
return;
}
if (vc == nullptr) {
sif::warning << "Ptme::addVcInterface: Invalid virtual channel interface" << std::endl;
return;
}
auto status = vcInterfaceMap.emplace(vcId, vc);
if (status.second == false) {
sif::warning << "Ptme::addVcInterface: Failed to add virtual channel interface to "
"virtual channel map"
<< std::endl;
return;
}
}

79
linux/ipcore/Ptme.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef LINUX_OBC_PTME_H_
#define LINUX_OBC_PTME_H_
#include <fsfw_hal/common/gpio/gpioDefinitions.h>
#include <fsfw_hal/linux/gpio/LinuxLibgpioIF.h>
#include <cstring>
#include <unordered_map>
#include "OBSWConfig.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "linux/ipcore/PtmeIF.h"
#include "linux/ipcore/VcInterfaceIF.h"
/**
* @brief This class handles the interfacing to the telemetry (PTME) IP core responsible for the
* encoding of telemetry packets according to the CCSDS standards CCSDS 131.0-B-3 (TM
* Synchro- nization and channel coding) and CCSDS 132.0-B-2 (TM Space Data Link Protocoll). The IP
* cores are implemented on the programmable logic and are accessible through the linux UIO driver.
*/
class Ptme : public PtmeIF, public SystemObject {
public:
using VcId_t = uint8_t;
/**
* @brief Constructor
*
* @param objectId
*/
Ptme(object_id_t objectId);
virtual ~Ptme();
ReturnValue_t initialize() override;
ReturnValue_t writeToVc(uint8_t vcId, const uint8_t* data, size_t size) override;
/**
* @brief This function adds the reference to a virtual channel interface to the vcInterface
* map.
*/
void addVcInterface(VcId_t vcId, VcInterfaceIF* vc);
private:
static const uint8_t INTERFACE_ID = CLASS_ID::PTME;
static const ReturnValue_t UNKNOWN_VC_ID = MAKE_RETURN_CODE(0xA0);
/**
* Configuration bits:
* bit[1:0]: Size of data (1,2,3 or 4 bytes). 1 Byte <=> b00
* bit[2]: Set this bit to 1 to abort a transfered packet
* bit[3]: Signals to PTME the start of a new telemetry packet
*/
static const uint32_t PTME_CONFIG_START = 0x8;
/**
* Writing this word to the ptme base address signals to the PTME that a complete tm packet has
* been transferred.
*/
static const uint32_t PTME_CONFIG_END = 0x0;
/**
* Writing to this offset within the PTME memory space will insert data for encoding to the
* PTME IP core.
* The address offset is 0x400 (= 4 * 256)
*/
static const int PTME_DATA_REG_OFFSET = 256;
/** The file descriptor of the UIO driver */
int fd = 0;
uint32_t* ptmeBaseAddress = nullptr;
using VcInterfaceMap = std::unordered_map<VcId_t, VcInterfaceIF*>;
using VcInterfaceMapIter = VcInterfaceMap::iterator;
VcInterfaceMap vcInterfaceMap;
};
#endif /* LINUX_OBC_PTME_H_ */

View File

@ -0,0 +1,50 @@
#include "PtmeConfig.h"
#include "fsfw/serviceinterface/ServiceInterface.h"
PtmeConfig::PtmeConfig(object_id_t objectId, AxiPtmeConfig* axiPtmeConfig)
: SystemObject(objectId), axiPtmeConfig(axiPtmeConfig) {}
PtmeConfig::~PtmeConfig() {}
ReturnValue_t PtmeConfig::initialize() {
if (axiPtmeConfig == nullptr) {
sif::warning << "PtmeConfig::initialize: Invalid AxiPtmeConfig object" << std::endl;
return returnvalue::FAILED;
}
return returnvalue::OK;
}
ReturnValue_t PtmeConfig::setRate(uint32_t bitRate) {
if (bitRate == 0) {
return BAD_BIT_RATE;
}
uint32_t rateVal = BIT_CLK_FREQ / bitRate - 1;
if (rateVal > 0xFF) {
return RATE_NOT_SUPPORTED;
}
return axiPtmeConfig->writeCaduRateReg(static_cast<uint8_t>(rateVal));
}
ReturnValue_t PtmeConfig::invertTxClock(bool invert) {
ReturnValue_t result = returnvalue::OK;
if (invert) {
result = axiPtmeConfig->enableTxclockInversion();
} else {
result = axiPtmeConfig->disableTxclockInversion();
}
if (result != returnvalue::OK) {
return CLK_INVERSION_FAILED;
}
return result;
}
ReturnValue_t PtmeConfig::configTxManipulator(bool enable) {
ReturnValue_t result = returnvalue::OK;
if (enable) {
result = axiPtmeConfig->enableTxclockManipulator();
} else {
result = axiPtmeConfig->disableTxclockManipulator();
}
return result;
}

77
linux/ipcore/PtmeConfig.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef LINUX_OBC_PTMECONFIG_H_
#define LINUX_OBC_PTMECONFIG_H_
#include "AxiPtmeConfig.h"
#include "fsfw/objectmanager/SystemObject.h"
#include "fsfw/returnvalues/returnvalue.h"
#include "linux/ipcore/PtmeConfig.h"
#include "returnvalues/classIds.h"
/**
* @brief Class to configure donwlink specific parameters in the PTME IP core.
*
* @author J. Meier
*/
class PtmeConfig : public SystemObject {
public:
/**
* @brief Constructor
*
* ptmeAxiConfig Pointer to object providing access to PTME configuration registers.
*/
PtmeConfig(object_id_t opbjectId, AxiPtmeConfig* axiPtmeConfig);
virtual ~PtmeConfig();
virtual ReturnValue_t initialize() override;
/**
* @brief Changes the input frequency to the S-Band transceiver and thus the downlink rate
*
* @details This is the bitrate of the CADU clock and not the downlink which has twice the bitrate
* of the CADU clock due to the convolutional code added by the s-Band transceiver.
*/
ReturnValue_t setRate(uint32_t bitRate);
/**
* @brief Will change the time the tx data signal is updated with respect to the tx clock
*
* @param invert True -> Data signal will be updated on the falling edge (not desired by the
* Syrlinks)
* False -> Data signal updated on rising edge (default configuration and desired
* by the syrlinks)
*
* @return REUTRN_OK if successful, otherwise error return value
*/
ReturnValue_t invertTxClock(bool invert);
/**
* @brief Controls the tx clock manipulator of the PTME wrapper component
*
* @param enable Manipulator will be enabled (this is also the default configuration)
* @param disable Manipulator will be disabled
*
* @return REUTRN_OK if successful, otherwise error return value
*/
ReturnValue_t configTxManipulator(bool enable);
private:
static const uint8_t INTERFACE_ID = CLASS_ID::RATE_SETTER;
//! [EXPORT] : [COMMENT] The commanded rate is not supported by the current FPGA design
static const ReturnValue_t RATE_NOT_SUPPORTED = MAKE_RETURN_CODE(0xA0);
//! [EXPORT] : [COMMENT] Bad bitrate has been commanded (e.g. 0)
static const ReturnValue_t BAD_BIT_RATE = MAKE_RETURN_CODE(0xA1);
//! [EXPORT] : [COMMENT] Failed to invert clock and thus change the time the data is updated with
//! respect to the tx clock
static const ReturnValue_t CLK_INVERSION_FAILED = MAKE_RETURN_CODE(0xA2);
//! [EXPORT] : [COMMENT] Failed to change configuration bit of tx clock manipulator
static const ReturnValue_t TX_MANIPULATOR_CONFIG_FAILED = MAKE_RETURN_CODE(0xA3);
// Bitrate register field is only 8 bit wide
static const uint32_t MAX_BITRATE = 0xFF;
// Bit clock frequency of PMTE IP core in Hz
static const uint32_t BIT_CLK_FREQ = 20000000;
AxiPtmeConfig* axiPtmeConfig = nullptr;
};
#endif /* LINUX_OBC_PTMECONFIG_H_ */

27
linux/ipcore/PtmeIF.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef LINUX_OBC_PTMEIF_H_
#define LINUX_OBC_PTMEIF_H_
#include "fsfw/returnvalues/returnvalue.h"
/**
* @brief Interface class for managing the PTME IP Core implemented in the programmable logic.
*
* @details PTME IP Core: https://www.esa.int/Enabling_Support/Space_Engineering_Technology/
* Microelectronics/PTME
* @author J. Meier
*/
class PtmeIF {
public:
virtual ~PtmeIF(){};
/**
* @brief Implements to function to write to a specific virtual channel.
*
* @param vcId Virtual channel to write to
* @param data Pointer to buffer holding the data to write
* @param size Number of bytes to write
*/
virtual ReturnValue_t writeToVc(uint8_t vcId, const uint8_t* data, size_t size) = 0;
};
#endif /* LINUX_OBC_PTMEIF_H_ */

View File

@ -0,0 +1,30 @@
#ifndef LINUX_OBC_VCINTERFACEIF_H_
#define LINUX_OBC_VCINTERFACEIF_H_
#include <stddef.h>
#include "fsfw/returnvalues/returnvalue.h"
/**
* @brief Interface class for managing different virtual channels of the PTME IP core implemented
* in the programmable logic.
*
* @author J. Meier
*/
class VcInterfaceIF {
public:
virtual ~VcInterfaceIF(){};
/**
* @brief Implememts the functionality to write data in the virtual channel of the PTME IP
* Core.
*
* @param data Pointer to buffer holding the data to write
* @param size Number of bytes to write
*/
virtual ReturnValue_t write(const uint8_t* data, size_t size) = 0;
virtual ReturnValue_t initialize() = 0;
};
#endif /* LINUX_OBC_VCINTERFACEIF_H_ */

59
linux/ipcore/pdec.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef LINUX_OBC_PDEC_H_
#define LINUX_OBC_PDEC_H_
#include <cstdint>
namespace pdec {
static const uint8_t STAT_POSITION = 31;
static const uint8_t FRAME_ANA_POSITION = 28;
static const uint8_t IREASON_POSITION = 25;
static const uint8_t NEW_FAR_RECEIVED = 0;
static constexpr uint32_t NEW_FAR_MASK = 1 << 2;
static constexpr uint32_t TC_ABORT_MASK = 1 << 1;
static constexpr uint32_t TC_NEW_MASK = 1 << 0;
static const uint32_t FRAME_ANA_MASK = 0x70000000;
static const uint32_t IREASON_MASK = 0x0E000000;
static const uint32_t TC_CHANNEL_INACTIVE = 0x0;
static const uint32_t TC_CHANNEL_ACTIVE = 0x1;
static const uint32_t TC_CHANNEL_TIMEDOUT = 0x2;
static const uint32_t TC0_STATUS_MASK = 0x3;
static const uint32_t TC1_STATUS_MASK = 0xC;
static const uint32_t TC2_STATUS_MASK = 0x300;
static const uint32_t TC3_STATUS_MASK = 0xC00;
static const uint32_t TC4_STATUS_MASK = 0x30000;
static const uint32_t TC5_STATUS_MASK = 0xc00000;
// Lock register set to 1 when start sequence has been found (CLTU is beeing processed)
static const uint32_t LOCK_MASK = 0xc00000;
static const uint32_t TC0_STATUS_POS = 0;
static const uint32_t TC1_STATUS_POS = 2;
static const uint32_t TC2_STATUS_POS = 4;
static const uint32_t TC3_STATUS_POS = 6;
static const uint32_t TC4_STATUS_POS = 8;
static const uint32_t TC5_STATUS_POS = 10;
// Lock register set to 1 when start sequence has been found (CLTU is beeing processed)
static const uint32_t LOCK_POS = 12;
/**
* UIO is 4 byte aligned. Thus offset is calculated with "true offset" / 4
* Example: PDEC_FAR = 0x2840 => Offset in virtual address space is 0xA10
*/
static constexpr uint32_t PDEC_PISR_OFFSET = 0xA02;
static constexpr uint32_t PDEC_PIR_OFFSET = 0xA03;
static constexpr uint32_t PDEC_IMR_OFFSET = 0xA04;
static const uint32_t PDEC_FAR_OFFSET = 0xA10;
static const uint32_t PDEC_CLCW_OFFSET = 0xA12;
static const uint32_t PDEC_BFREE_OFFSET = 0xA24;
static const uint32_t PDEC_BPTR_OFFSET = 0xA25;
static const uint32_t PDEC_SLEN_OFFSET = 0xA26;
static const uint32_t PDEC_MON_OFFSET = 0xA27;
} // namespace pdec
#endif /* LINUX_OBC_PDEC_H_ */