#include "icsneo/platform/firmio.h" #include "icsneo/communication/network.h" #include "icsneo/communication/command.h" #include "icsneo/communication/packetizer.h" #include "icsneo/communication/decoder.h" #include "icsneo/communication/message/serialnumbermessage.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace icsneo; #define PHY_ADDR_BASE (0x1E000000) // IPC0 #define MMAP_LEN (0x1000000) #define FIRMIO_DEV "/dev/firmio" #define COM_VER (0xC000) #define memory_barrier() __sync_synchronize() void FirmIO::Find(std::vector& found) { FirmIO temp([](APIEvent::Type, APIEvent::Severity) {}); if(!temp.open()) return; std::vector payload = { ((1 << 4) | (uint8_t)Network::NetID::Main51), // Packet size of 1 on NETID_MAIN51 (uint8_t)Command::RequestSerialNumber }; payload.push_back(Packetizer::ICSChecksum(payload)); payload.insert(payload.begin(), 0xAA); temp.write(payload); Packetizer packetizer([](APIEvent::Type, APIEvent::Severity) {}); Decoder decoder([](APIEvent::Type, APIEvent::Severity) {}); using namespace std::chrono; const auto start = steady_clock::now(); auto timeout = milliseconds(50); while(temp.readWait(payload, timeout)) { timeout -= duration_cast(steady_clock::now() - start); if(!packetizer.input(payload)) continue; // A full packet has not yet been read out for(const auto& packet : packetizer.output()) { std::shared_ptr message; if(!decoder.decode(message, packet)) continue; // Malformed packet const auto serial = std::dynamic_pointer_cast(message); if(!serial || serial->deviceSerial.size() != 6) continue; // Not a serial number message FoundDevice foundDevice; // Don't need a handle, only one device will be found // Setting one anyway in case anyone checks for 0 as invalid handle foundDevice.handle = 1; memcpy(foundDevice.serial, serial->deviceSerial.c_str(), sizeof(foundDevice.serial) - 1); foundDevice.serial[sizeof(foundDevice.serial) - 1] = '\0'; foundDevice.makeDriver = [](const device_eventhandler_t& report, neodevice_t&) { return std::unique_ptr(new FirmIO(report)); }; found.push_back(foundDevice); } } } FirmIO::~FirmIO() { if(isOpen()) close(); } bool FirmIO::open() { if(isOpen()) { report(APIEvent::Type::DeviceCurrentlyOpen, APIEvent::Severity::Error); return false; } fd = ::open(FIRMIO_DEV, O_RDWR); if(!isOpen()) { //std::cout << "Open of " << ttyPath.c_str() << " failed with " << strerror(errno) << ' '; report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); return false; } vbase = reinterpret_cast(mmap(nullptr, MMAP_LEN, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, PHY_ADDR_BASE)); if(vbase == MAP_FAILED) { report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); return false; } header = reinterpret_cast(vbase); if(header->comVer != COM_VER) { report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); return false; } // Swapping the in and out ptrs here, what the device considers out, we consider in out = reinterpret_cast(header->msgqPtrIn.offset + vbase); in = reinterpret_cast(header->msgqPtrOut.offset + vbase); outMemory.emplace(vbase + header->shmIn.offset, header->shmIn.size, vbase, PHY_ADDR_BASE); // Create thread // No thread for writing since we don't need the extra buffer readThread = std::thread(&FirmIO::readTask, this); return true; } bool FirmIO::isOpen() { return fd >= 0; // Negative fd indicates error or not opened yet } bool FirmIO::close() { if(!isOpen() && !isDisconnected()) { report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); return false; } closing = true; if(readThread.joinable()) readThread.join(); closing = false; disconnected = false; int ret = munmap(vbase, MMAP_LEN); vbase = nullptr; ret |= ::close(fd); fd = -1; uint8_t flush; WriteOperation flushop; while (readQueue.try_dequeue(flush)) {} while (writeQueue.try_dequeue(flushop)) {} if(ret == 0) { return true; } else { report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error); return false; } } void FirmIO::readTask() { EventManager::GetInstance().downgradeErrorsOnCurrentThread(); while(!closing && !isDisconnected()) { fd_set rfds = {0}; struct timeval tv = {0}; FD_SET(fd, &rfds); tv.tv_usec = 50000; // 50ms int ret = ::select(fd + 1, &rfds, NULL, NULL, &tv); if(ret < 0) report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error); if(ret <= 0) continue; uint32_t interruptCount = 0; ret = ::read(fd, &interruptCount, sizeof(interruptCount)); if(ret < 0) report(APIEvent::Type::FailedToRead, APIEvent::Severity::Error); if(ret < sizeof(interruptCount) || interruptCount < 1) continue; std::vector toFree; Msg msg; int i = 0; while(!in->isEmpty() && i++ < 100) { if(!in->read(&msg)) break; switch(msg.command) { case Msg::Command::ComData: { if(toFree.empty() || toFree.back().payload.free.refCount == 7) { toFree.emplace_back(); toFree.back().command = Msg::Command::ComFree; toFree.back().payload.free.refCount = 0; } // Add this ref to the list of payloads to free // After we process these, we'll send this list back to the device // so that it can free these entries toFree.back().payload.free.ref[toFree.back().payload.free.refCount] = msg.payload.data.ref; toFree.back().payload.free.refCount++; // Translate the physical address back to our virtual address space uint8_t* addr = reinterpret_cast(msg.payload.data.addr - PHY_ADDR_BASE + vbase); readQueue.enqueue_bulk(addr, msg.payload.data.len); break; } case Msg::Command::ComFree: { std::lock_guard lk(outMutex); for(uint32_t i = 0; i < msg.payload.free.refCount; i++) outMemory->free(reinterpret_cast(msg.payload.free.ref[i])); break; } } } while(!toFree.empty()) { std::lock_guard lk(outMutex); out->write(&toFree.back()); toFree.pop_back(); } } } void FirmIO::writeTask() { return; // We're overriding Driver::writeInternal() and doing the work there } bool FirmIO::writeQueueFull() { return out->isFull(); } bool FirmIO::writeQueueAlmostFull() { // TODO: Better implementation here return writeQueueFull(); } bool FirmIO::writeInternal(const std::vector& bytes) { if(bytes.empty() || bytes.size() > Mempool::BlockSize) return false; std::lock_guard lk(outMutex); uint8_t* sharedData = outMemory->alloc(bytes.size()); if(sharedData == nullptr) return false; memcpy(sharedData, bytes.data(), bytes.size()); Msg msg = { Msg::Command::ComData }; msg.payload.data.addr = outMemory->translate(sharedData); msg.payload.data.len = static_cast(bytes.size()); msg.payload.data.ref = reinterpret_cast(sharedData); if(!out->write(&msg)) return false; uint32_t genInterrupt = 0x01; return ::write(fd, &genInterrupt, sizeof(genInterrupt)) == sizeof(genInterrupt); } bool FirmIO::MsgQueue::read(Msg* msg) volatile { if(isEmpty()) // Contains memory_barrier() return false; memcpy(msg, &msgs[tail], sizeof(*msg)); tail = (tail + 1) & (size - 1); memory_barrier(); return true; } bool FirmIO::MsgQueue::write(const Msg* msg) volatile { if(isFull()) // Contains memory_barrier() return false; memcpy(&msgs[head], msg, sizeof(*msg)); head = (head + 1) & (size - 1); memory_barrier(); return true; } bool FirmIO::MsgQueue::isEmpty() const volatile { memory_barrier(); return head == tail; } bool FirmIO::MsgQueue::isFull() const volatile { memory_barrier(); return ((head + 1) & (size - 1)) == tail; } FirmIO::Mempool::Mempool(uint8_t* start, uint32_t size, void* virt, uint32_t phys) : startAddress(start), totalSize(size), blocks(size / BlockSize), usedBlocks(0), virtualAddress(virt), physicalAddress(phys) { size_t idx = 0; for(BlockInfo& block : blocks) { block.status = BlockInfo::Status::Free; block.addr = start + idx * BlockSize; idx++; } } uint8_t* FirmIO::Mempool::alloc(uint32_t size) { if(usedBlocks == blocks.size()) return nullptr; if(size > BlockSize) return nullptr; auto found = std::find_if(blocks.begin(), blocks.end(), [](const BlockInfo& b) { return b.status == BlockInfo::Status::Free; }); if(found == blocks.end()) return nullptr; // No free blocks, inconsistency with usedBlocks found->status = BlockInfo::Status::Used; usedBlocks++; return found->addr; } bool FirmIO::Mempool::free(uint8_t* addr) { auto found = std::find_if(blocks.begin(), blocks.end(), [&addr](const BlockInfo& b) { return b.addr == addr; }); if(found == blocks.end()) return false; // Invalid address if(found->status != BlockInfo::Status::Used) return false; // Double free usedBlocks--; found->status = BlockInfo::Status::Free; return true; }