diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e56871..aed12c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ set(SRC_FILES communication/packet/flexraypacket.cpp communication/packet/canpacket.cpp communication/packet/a2bpacket.cpp + communication/packet/spipacket.cpp communication/packet/ethernetpacket.cpp communication/packet/versionpacket.cpp communication/packet/iso9141packet.cpp diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 82b7a14..e178777 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -31,6 +31,7 @@ pybind11_add_module(icsneopy icsneopy/communication/message/mdiomessage.cpp icsneopy/communication/message/gptpstatusmessage.cpp icsneopy/communication/message/ethernetstatusmessage.cpp + icsneopy/communication/message/spimessage.cpp icsneopy/communication/message/macsecmessage.cpp icsneopy/communication/message/scriptstatusmessage.cpp icsneopy/communication/message/callback/messagecallback.cpp diff --git a/bindings/python/icsneopy/communication/message/spimessage.cpp b/bindings/python/icsneopy/communication/message/spimessage.cpp new file mode 100644 index 0000000..3ff2564 --- /dev/null +++ b/bindings/python/icsneopy/communication/message/spimessage.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +#include "icsneo/communication/message/spimessage.h" + +namespace icsneo { + +void init_spimessage(pybind11::module_& m) { + pybind11::class_, Frame> spiMessage(m, "SPIMessage"); + pybind11::enum_(spiMessage, "Direction") + .value("Write", SPIMessage::Direction::Write) + .value("Read", SPIMessage::Direction::Read); + spiMessage + .def(pybind11::init()) + .def_readwrite("direction", &SPIMessage::direction) + .def_readwrite("address", &SPIMessage::address) + .def_readwrite("mms", &SPIMessage::mms) + .def_readwrite("stats", &SPIMessage::stats) + .def_readwrite("payload", &SPIMessage::payload); +} + +} // namespace icsneo + diff --git a/bindings/python/icsneopy/icsneocpp.cpp b/bindings/python/icsneopy/icsneocpp.cpp index 3158f11..87ee928 100644 --- a/bindings/python/icsneopy/icsneocpp.cpp +++ b/bindings/python/icsneopy/icsneocpp.cpp @@ -20,6 +20,7 @@ void init_linmessage(pybind11::module_&); void init_tc10statusmessage(pybind11::module_&); void init_gptpstatusmessage(pybind11::module_&); void init_mdiomessage(pybind11::module_&); +void init_spimessage(pybind11::module_&); void init_ethernetstatusmessage(pybind11::module_&); void init_macsecmessage(pybind11::module_&); void init_scriptstatusmessage(pybind11::module_&); @@ -55,6 +56,7 @@ PYBIND11_MODULE(icsneopy, m) { init_ethernetstatusmessage(m); init_macsecmessage(m); init_scriptstatusmessage(m); + init_spimessage(m); init_messagefilter(m); init_messagecallback(m); init_diskdriver(m); diff --git a/communication/decoder.cpp b/communication/decoder.cpp index 0b95b5d..6d9cbbb 100644 --- a/communication/decoder.cpp +++ b/communication/decoder.cpp @@ -43,6 +43,7 @@ #include "icsneo/communication/packet/genericbinarystatuspacket.h" #include "icsneo/communication/packet/livedatapacket.h" #include "icsneo/communication/packet/hardwareinfopacket.h" +#include "icsneo/communication/packet/spipacket.h" #include @@ -184,6 +185,19 @@ bool Decoder::decode(std::shared_ptr& result, const std::shared_ptrdata); + + if(!result) { + report(APIEvent::Type::PacketDecodingError, APIEvent::Severity::Error); + return false; // A nullptr was returned, the packet was not long enough to decode + } + + SPIMessage& msg = *static_cast(result.get()); + msg.network = packet->network; + msg.timestamp *= timestampResolution; + return true; + } case Network::Type::MDIO: { result = HardwareMDIOPacket::DecodeToMessage(packet->data); diff --git a/communication/encoder.cpp b/communication/encoder.cpp index 0e1449d..41a1aeb 100644 --- a/communication/encoder.cpp +++ b/communication/encoder.cpp @@ -13,6 +13,7 @@ #include "icsneo/communication/packet/a2bpacket.h" #include "icsneo/communication/packet/linpacket.h" #include "icsneo/communication/packet/mdiopacket.h" +#include "icsneo/communication/packet/spipacket.h" using namespace icsneo; @@ -133,6 +134,17 @@ bool Encoder::encode(const Packetizer& packetizer, std::vector& result, } break; } // End of Network::Type::MDIO + case Network::Type::SPI: { + auto msg = std::dynamic_pointer_cast(message); + if(!msg) { + report(APIEvent::Type::MessageFormattingError, APIEvent::Severity::Error); + return false; // The message was not a properly formed LiveDataMessage + } + if(!HardwareSPIPacket::EncodeFromMessage(*msg, result, report)) + return false; + result = packetizer.packetWrap(result, false); + return true; + } // End of Network::Type::SPI default: report(APIEvent::Type::UnexpectedNetworkType, APIEvent::Severity::Error); return false; diff --git a/communication/packet/spipacket.cpp b/communication/packet/spipacket.cpp new file mode 100644 index 0000000..4dcfee9 --- /dev/null +++ b/communication/packet/spipacket.cpp @@ -0,0 +1,82 @@ +#include "icsneo/communication/packet/spipacket.h" +#include "icsneo/communication/command.h" +#include +#include + +using namespace icsneo; + +static size_t SPISubHeaderLength = 5u; + +std::shared_ptr HardwareSPIPacket::DecodeToMessage(const std::vector& bytestream) { + if(bytestream.size() < sizeof(HardwareSPIPacket)) { + return nullptr; + } + const HardwareSPIPacket* packet = (const HardwareSPIPacket*)bytestream.data(); + size_t totalPackedLength = static_cast(bytestream.size()) - sizeof(HardwareSPIPacket); // First 28 bytes are message header. + if(totalPackedLength < SPISubHeaderLength) { + return nullptr; + } + + const uint8_t* bytes = bytestream.data() + sizeof(HardwareSPIPacket); + std::shared_ptr msg = std::make_shared(); + msg->direction = static_cast(bytes[0]); + msg->address = *reinterpret_cast(&bytes[1]); + msg->mms = bytes[3]; + msg->stats = packet->stats; + msg->timestamp = packet->timestamp.TS; + + size_t numWords = (totalPackedLength - SPISubHeaderLength) / 4; + msg->payload.reserve(numWords); + for(size_t offset = SPISubHeaderLength; offset < totalPackedLength; offset += 4) { + msg->payload.push_back(*reinterpret_cast(bytes + offset)); + } + return msg; +} + +bool HardwareSPIPacket::EncodeFromMessage(const SPIMessage& message, std::vector& bytestream, const device_eventhandler_t& /*report*/) { + // Payload length is everything excluding cmdHeader (note at the beginning there is an offset of 2) + uint16_t payloadLength = static_cast( + 2 + + sizeof(HardwareSPIPacket) + + SPISubHeaderLength + + message.payload.size() * sizeof(uint32_t) + ); + if(payloadLength % 2) { + // Pad payload to even number + payloadLength++; + } + // +1 for AA, another +1 for firmware nuance + uint16_t fullSize = 1 + sizeof(ExtendedCommandHeader) + payloadLength + 1; + uint16_t unwrappedSize = sizeof(ExtendedCommandHeader) + payloadLength; // fullSize without AA and firmware nuance + + bytestream.resize(unwrappedSize, 0); + uint32_t offset = 0; + auto* cmdHeader = reinterpret_cast(bytestream.data() + offset); + cmdHeader->netid = static_cast(Network::NetID::Main51); + cmdHeader->fullLength = fullSize; + cmdHeader->command = static_cast(Command::Extended); + cmdHeader->extendedCommand = static_cast(ExtendedCommand::TransmitCoreminiMessage); + cmdHeader->payloadLength = payloadLength; + + offset += sizeof(ExtendedCommandHeader) + 2; // Offset of 2 between header and packet + auto* packet = reinterpret_cast(bytestream.data() + offset); + packet->header.frameLength = static_cast(SPISubHeaderLength + message.payload.size() * sizeof(uint32_t)); + packet->networkID = static_cast(message.network.getNetID()); + packet->length = packet->header.frameLength; + packet->timestamp.IsExtended = 1; + + offset += sizeof(HardwareSPIPacket); + // Write the sub header details + bytestream[offset++] = static_cast(message.direction); + bytestream[offset++] = static_cast(message.address & 0xFF); + bytestream[offset++] = static_cast((message.address >> 8) & 0xFF); + bytestream[offset++] = static_cast(message.mms); + bytestream[offset++] = static_cast(message.payload.size()); + + // Write the words + for(uint32_t word : message.payload) { + *reinterpret_cast(bytestream.data() + offset) = word; + offset += sizeof(uint32_t); + } + return true; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3aac1db..3a1d836 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -11,6 +11,7 @@ option(LIBICSNEO_BUILD_CPP_MDIO_EXAMPLE "Build the MDIO example." ON) option(LIBICSNEO_BUILD_CPP_VSA_EXAMPLE "Build the VSA example." ON) option(LIBICSNEO_BUILD_CPP_APP_ERROR_EXAMPLE "Build the app error example." ON) option(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE "Build the FlexRay example." ON) +option(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE "Build the SPI example." ON) if(LIBICSNEO_BUILD_C_INTERACTIVE_EXAMPLE) add_subdirectory(c/interactive) @@ -63,3 +64,7 @@ endif() if(LIBICSNEO_BUILD_CPP_FLEXRAY_EXAMPLE) add_subdirectory(cpp/flexray) endif() + +if(LIBICSNEO_BUILD_CPP_SPI_EXAMPLE) + add_subdirectory(cpp/spi) +endif() \ No newline at end of file diff --git a/examples/cpp/spi/CMakeLists.txt b/examples/cpp/spi/CMakeLists.txt new file mode 100644 index 0000000..255f4fb --- /dev/null +++ b/examples/cpp/spi/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(libicsneocpp-spi src/spi.cpp) +target_link_libraries(libicsneocpp-spi icsneocpp) \ No newline at end of file diff --git a/examples/cpp/spi/src/spi.cpp b/examples/cpp/spi/src/spi.cpp new file mode 100644 index 0000000..3cdf16b --- /dev/null +++ b/examples/cpp/spi/src/spi.cpp @@ -0,0 +1,142 @@ +/** + * libicsneo SPI message example + * + * Read a register on NetID::SPI_01 and display the result to stdout + * + * Usage: libicsneo-spi [deviceSerial] [address] [numWords] + * + * Arguments: + * deviceSerial: 6 character string for device serial + * address: 16 bit register address + * numWords: number of words to read + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "icsneo/icsneocpp.h" +#include "icsneo/communication/message/spimessage.h" + +static const std::string usage = "Usage: libicsneo-spi [deviceSerial] [address] [numWords]\n\n" + "Arguments:\n" + "deviceSerial: 6 character string for device serial\n" + "address: hex number representing 16 bit register number\n" + "numWords: number of words to read\n"; + +static std::tuple parseArgs(std::vector& args, bool& fail) { + + if(args.size() != 4) { + std::cerr << "Invalid argument count" << std::endl; + std::cerr << usage; + fail = true; + return {}; + } + + std::string_view serial = args[1]; + if(serial.size() != 6) { + std::cerr << "Invalid serial length" << std::endl; + std::cerr << usage; + fail = true; + return {}; + } + + char* endPtr; + uint16_t address = static_cast(std::strtoul(args[2].data(), &endPtr, 16)); + if(endPtr != (args[2].data() + args[2].size())) { + std::cerr << "Failed to convert address to hex" << std::endl; + std::cerr << usage; + fail = true; + return {}; + } + + uint16_t numWords = static_cast(std::strtoul(args[3].data(), &endPtr, 16)); + if(endPtr != (args[3].data() + args[3].size())) { + std::cerr << "Failed to convert numWords to decimal integer" << std::endl; + std::cerr << usage; + fail = true; + return {}; + } + fail = false; + return {serial, address, numWords}; +} + +int main(int argc, const char** argv) { + std::vector args(argv, argv + argc); + bool fail; + auto tup = parseArgs(args, fail); + if(fail) { + return -1; + } + auto serial = std::get<0>(tup); + auto address = std::get<1>(tup); + auto numWords = std::get<2>(tup); + + std::shared_ptr device = nullptr; + for(const auto& dev : icsneo::FindAllDevices()) { + if(dev->getSerial() == serial) { + device = dev; + break; + } + } + if(!device) { + std::cerr << "Failed to find device" << std::endl; + std::cerr << usage; + return -1; + } + if(!device->open()) { + std::cerr << "Failed to open device" << std::endl; + return -1; + } + if(!device->goOnline()) { + std::cerr << "Failed to go online" << std::endl; + return -1; + } + + std::cout << "Reading register " << std::setfill('0') << std::setw(4) << std::right << std::hex << address << std::endl; + + // Make message + std::shared_ptr msg = std::make_shared(); + msg->network = icsneo::Network::NetID::SPI_01; + msg->direction = icsneo::SPIMessage::Direction::Read; + msg->address = address; + msg->payload.resize(numWords, 0); // Resize payload to desired length + + // Add callback + bool receivedMessage = false; + auto handle = device->addMessageCallback(std::make_shared([&](std::shared_ptr msg) { + if(receivedMessage) { + return; + } + if(msg->type == icsneo::Message::Type::Frame) { + auto frame = std::dynamic_pointer_cast(msg); + if(!frame) { + return; + } + if(frame->network == icsneo::Network::NetID::SPI_01) { + auto spiMsg = std::dynamic_pointer_cast(frame); + if(spiMsg->address == address) { + std::cout << "Received " << spiMsg->payload.size() << " words" << std::endl; + for(uint32_t word : spiMsg->payload) { + std::cout << std::setfill('0') << std::setw(8) << std::right << std::hex << word << ' '; + } + std::cout << std::endl; + receivedMessage = true; + } + } + } + })); + device->transmit(msg); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + + if(!receivedMessage) { + std::cout << "Did not receive response from device." << std::endl; + } + device->removeMessageCallback(handle); + return 0; +} diff --git a/include/icsneo/communication/command.h b/include/icsneo/communication/command.h index a457bc7..fcbd40c 100644 --- a/include/icsneo/communication/command.h +++ b/include/icsneo/communication/command.h @@ -56,6 +56,7 @@ enum class ExtendedCommand : uint16_t { GetComponentVersions = 0x001A, Reboot = 0x001C, SetRootFSEntryFlags = 0x0027, + TransmitCoreminiMessage = 0x0028, GenericBinaryInfo = 0x0030, LiveData = 0x0035, RequestTC10Wake = 0x003D, diff --git a/include/icsneo/communication/message/spimessage.h b/include/icsneo/communication/message/spimessage.h new file mode 100644 index 0000000..cdba816 --- /dev/null +++ b/include/icsneo/communication/message/spimessage.h @@ -0,0 +1,30 @@ +#ifndef __SPIMESSAGE_H_ +#define __SPIMESSAGE_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/message/message.h" +#include + +namespace icsneo { + +class SPIMessage : public Frame { +public: + enum class Direction : uint8_t { + Read = 0, + Write = 1 + }; + + Direction direction = Direction::Read; + uint16_t address = static_cast(0x0000u); + uint8_t mms = static_cast(0x0000u); // Memory Map Selector for ADI MAC Phy + uint16_t stats = static_cast(0x0000u); + + std::vector payload; +}; + +} + +#endif // __cplusplus + +#endif diff --git a/include/icsneo/communication/packet/spipacket.h b/include/icsneo/communication/packet/spipacket.h new file mode 100644 index 0000000..3f75733 --- /dev/null +++ b/include/icsneo/communication/packet/spipacket.h @@ -0,0 +1,42 @@ +#ifndef __SPIPACKET_H__ +#define __SPIPACKET_H__ + +#ifdef __cplusplus + +#include "icsneo/api/eventmanager.h" +#include +#include +#include +#include "icsneo/communication/message/spimessage.h" + +namespace icsneo { + +typedef uint16_t icscm_bitfield; + +#pragma pack(push, 2) +struct HardwareSPIPacket { + + static std::shared_ptr DecodeToMessage(const std::vector& bytestream); + static bool EncodeFromMessage(const SPIMessage& message, std::vector& bytestream, const device_eventhandler_t& report); + + struct { + uint16_t frameLength; + } header; + uint8_t offset[12]; + uint16_t stats; + struct { + uint64_t TS : 60; + uint64_t : 3; // Reserved for future status bits + uint64_t IsExtended : 1; + } timestamp; + uint16_t networkID; + uint16_t length; +}; + +#pragma pack(pop) + +} + +#endif // __cplusplus + +#endif \ No newline at end of file