// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "wpi/hal/CAN.h" #include #include #include #include #include #include #include #include #include #include #include #include "PortsInternal.hpp" #include "wpi/hal/Errors.h" #include "wpi/hal/Threads.h" #include "wpi/hal/handles/UnlimitedHandleResource.hpp" #include "wpi/net/EventLoopRunner.hpp" #include "wpi/net/uv/Poll.hpp" #include "wpi/net/uv/Timer.hpp" #include "wpi/util/DenseMap.hpp" #include "wpi/util/circular_buffer.hpp" #include "wpi/util/mutex.hpp" #include "wpi/util/print.hpp" #include "wpi/util/timestamp.hpp" using namespace wpi::hal; namespace { static constexpr uint32_t MatchingBitMask = CAN_EFF_MASK | CAN_RTR_FLAG; static_assert(CAN_RTR_FLAG == HAL_CAN_IS_FRAME_REMOTE); static_assert(CAN_EFF_FLAG == HAL_CAN_IS_FRAME_11BIT); uint32_t MapMessageIdToSocketCan(uint32_t id) { // Message and RTR map directly uint32_t toRet = id & MatchingBitMask; // Reverse the 11 bit flag if ((id & HAL_CAN_IS_FRAME_11BIT) == 0) { toRet |= CAN_EFF_FLAG; } return toRet; } uint32_t MapSocketCanToMessageId(uint32_t id) { // Message and RTR map directly uint32_t toRet = id & MatchingBitMask; // Reverse the 11 bit flag if ((id & CAN_EFF_FLAG) == 0) { toRet |= HAL_CAN_IS_FRAME_11BIT; } return toRet; } struct CANStreamStorage { CANStreamStorage(uint32_t maxMessages, uint8_t busId, uint32_t mask, uint32_t filter) : receivedMessages{maxMessages}, allowedMessages{maxMessages}, canBusId{busId}, canMask{mask}, canFilter{filter} {} wpi::util::circular_buffer receivedMessages; bool didOverflow{false}; uint32_t allowedMessages; uint8_t canBusId; uint32_t canMask; uint32_t canFilter; void CheckFrame(const HAL_CANStreamMessage& message); }; struct SocketCanState { wpi::net::EventLoopRunner readLoopRunner; wpi::net::EventLoopRunner writeLoopRunner; wpi::util::mutex writeMutex[wpi::hal::kNumCanBuses]; int socketHandle[wpi::hal::kNumCanBuses]; // ms to count/timer map wpi::util::DenseMap>> timers; // ms to bus mask/packet wpi::util::DenseMap< uint16_t, std::array, wpi::hal::kNumCanBuses>> timedFrames; // packet to time wpi::util::DenseMap> packetToTime; wpi::util::mutex readMutex[wpi::hal::kNumCanBuses]; // TODO(thadhouse) we need a MUCH better way of doing this masking wpi::util::DenseMap readFrames[wpi::hal::kNumCanBuses]; std::vector canStreams[wpi::hal::kNumCanBuses]; bool InitializeBuses(); void TimerCallback(uint16_t time); void RemovePeriodic(uint8_t busMask, uint32_t messageId); void AddPeriodic(wpi::net::uv::Loop& loop, uint8_t busMask, uint16_t time, const canfd_frame& frame); }; } // namespace static UnlimitedHandleResource* canStreamHandles; static SocketCanState* canState; namespace wpi::hal::init { void InitializeCAN() { canState = new SocketCanState{}; static UnlimitedHandleResource cSH; canStreamHandles = &cSH; } } // namespace wpi::hal::init void CANStreamStorage::CheckFrame(const HAL_CANStreamMessage& message) { if ((message.messageId & canMask) != canFilter) { return; } // We already are holding the lock here. if (receivedMessages.size() == allowedMessages) { didOverflow = true; return; } receivedMessages.push_back(message); } bool SocketCanState::InitializeBuses() { bool success = true; readLoopRunner.ExecSync([this, &success](wpi::net::uv::Loop& loop) { if (HAL_SetCurrentThreadPriority(50) != 0) { wpi::util::print("Failed to set CAN thread priority\n"); } for (int i = 0; i < wpi::hal::kNumCanBuses; i++) { std::scoped_lock lock{writeMutex[i]}; socketHandle[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (socketHandle[i] == -1) { wpi::util::print("socket() for CAN {} failed with {}\n", i, std::strerror(errno)); success = false; return; } ifreq ifr; if (i < 5) { std::snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "can_s%u", static_cast(i)); } else { std::snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "can_d%u", static_cast(i - 5)); } if (ioctl(socketHandle[i], SIOCGIFINDEX, &ifr) == -1) { wpi::util::print("ioctl(SIOCGIFINDEX) for CAN {} failed with {}\n", ifr.ifr_name, std::strerror(errno)); success = false; return; } sockaddr_can addr; std::memset(&addr, 0, sizeof(addr)); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(socketHandle[i], reinterpret_cast(&addr), sizeof(addr)) == -1) { wpi::util::print("bind() for CAN {} failed with {}\n", ifr.ifr_name, std::strerror(errno)); success = false; return; } if (ioctl(socketHandle[i], SIOCGIFMTU, &ifr) == -1) { wpi::util::print("ioctl(SIOCGIFMTU) for CAN {} failed with {}\n", ifr.ifr_name, std::strerror(errno)); success = false; return; } if (ifr.ifr_mtu == CANFD_MTU) { int fdSet = 1; if (setsockopt(socketHandle[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &fdSet, sizeof(fdSet)) != 0) { wpi::util::print( "setsockopt(CAN_RAW_FD_FRAMES) for CAN {} failed with {}\n", ifr.ifr_name, std::strerror(errno)); success = false; return; } } auto poll = wpi::net::uv::Poll::Create(loop, socketHandle[i]); if (!poll) { wpi::util::print("wpi::net::uv::Poll::Create for CAN {} failed\n", ifr.ifr_name); success = false; return; } poll->pollEvent.connect([this, fd = socketHandle[i], canIndex = i](int mask) { if (mask & UV_READABLE) { canfd_frame frame; int rVal = read(fd, &frame, sizeof(frame)); if (rVal <= 0) { // TODO(thadhouse) error handling return; } if (frame.can_id & CAN_ERR_FLAG) { // Do nothing if this is an error frame return; } uint32_t messageId = MapSocketCanToMessageId(frame.can_id); uint64_t timestamp = wpi::util::Now(); // Ensure FDF flag is set for the read later. if (rVal == CANFD_MTU) { frame.flags = CANFD_FDF; } std::scoped_lock lock{readMutex[canIndex]}; auto& msg = readFrames[canIndex][messageId]; msg.messageId = messageId; msg.message.timeStamp = timestamp; msg.message.message.flags = HAL_CANFlags::HAL_CAN_NO_FLAGS; msg.message.message.flags |= (frame.flags & CANFD_FDF) ? HAL_CANFlags::HAL_CAN_FD_DATALENGTH : HAL_CANFlags::HAL_CAN_NO_FLAGS; msg.message.message.flags |= (frame.flags & CANFD_BRS) ? HAL_CANFlags::HAL_CAN_FD_BITRATESWITCH : HAL_CANFlags::HAL_CAN_NO_FLAGS; msg.message.message.dataSize = frame.len; if (frame.len > 0) { std::memcpy(msg.message.message.data, frame.data, frame.len); } for (auto&& stream : canStreams[canIndex]) { stream->CheckFrame(msg); } } }); poll->Start(UV_READABLE); } }); return success; } void SocketCanState::TimerCallback(uint16_t time) { auto& busFrames = timedFrames[time]; for (size_t i = 0; i < busFrames.size(); i++) { const auto& frame = busFrames[i]; if (!frame.has_value()) { continue; } std::scoped_lock lock{writeMutex[i]}; int mtu = (frame->flags & CANFD_FDF) ? CANFD_MTU : CAN_MTU; send(canState->socketHandle[i], &*frame, mtu, 0); } } void SocketCanState::RemovePeriodic(uint8_t busId, uint32_t messageId) { // Find time, and remove from map auto& time = packetToTime[messageId][busId]; auto storedTime = time; time = 0; // Its already been removed if (storedTime == 0) { return; } // Reset frame timedFrames[storedTime][busId].reset(); auto& timer = timers[storedTime]; // Stop the timer timer.first--; if (timer.first == 0) { if (auto l = timer.second.lock()) { l->Stop(); } } } void SocketCanState::AddPeriodic(wpi::net::uv::Loop& loop, uint8_t busId, uint16_t time, const canfd_frame& frame) { packetToTime[frame.can_id][busId] = time; timedFrames[time][busId] = frame; auto& timer = timers[time]; timer.first++; if (timer.first == 1) { auto newTimer = wpi::net::uv::Timer::Create(loop); newTimer->timeout.connect([this, time] { TimerCallback(time); }); newTimer->Start(wpi::net::uv::Timer::Time{time}, wpi::net::uv::Timer::Time{time}); } } namespace wpi::hal { bool InitializeCanBuses() { return canState->InitializeBuses(); } } // namespace wpi::hal namespace {} // namespace extern "C" { void HAL_CAN_SendMessage(int32_t busId, uint32_t messageId, const struct HAL_CANMessage* message, int32_t periodMs, int32_t* status) { if (busId < 0 || busId >= wpi::hal::kNumCanBuses) { *status = HAL_PARAMETER_OUT_OF_RANGE; return; } if (busId >= 5 && ((message->flags & HAL_CANFlags::HAL_CAN_FD_DATALENGTH) || (message->flags & HAL_CANFlags::HAL_CAN_FD_BITRATESWITCH))) { *status = HAL_PARAMETER_OUT_OF_RANGE; return; } messageId = MapMessageIdToSocketCan(messageId); // TODO determine on the real roborio is a non periodic send removes any // periodic send. if (periodMs == HAL_CAN_SEND_PERIOD_STOP_REPEATING) { canState->writeLoopRunner.ExecSync([messageId, busId](wpi::net::uv::Loop&) { canState->RemovePeriodic(busId, messageId); }); *status = 0; return; } canfd_frame frame; std::memset(&frame, 0, sizeof(frame)); frame.can_id = messageId; frame.flags |= (message->flags & HAL_CANFlags::HAL_CAN_FD_DATALENGTH) ? CANFD_FDF : 0; frame.flags |= (message->flags & HAL_CANFlags::HAL_CAN_FD_BITRATESWITCH) ? CANFD_BRS : 0; if (message->dataSize) { auto size = (std::min)(message->dataSize, static_cast(sizeof(frame.data))); std::memcpy(frame.data, message->data, size); frame.len = size; } int mtu = (message->flags & HAL_CANFlags::HAL_CAN_FD_DATALENGTH) ? CANFD_MTU : CAN_MTU; { std::scoped_lock lock{canState->writeMutex[busId]}; int result = send(canState->socketHandle[busId], &frame, mtu, 0); if (result != mtu) { if (result == -1) { int err = errno; if (err == ENOBUFS) { *status = HAL_WARN_CANSessionMux_TxQueueFull; return; } else if (err == EAGAIN || err == EWOULDBLOCK) { *status = HAL_WARN_CANSessionMux_SocketBufferFull; return; } } // Print is here, and we can better debug this in the future. std::printf("Send Error %d %d %s\n", result, errno, std::strerror(errno)); std::fflush(stdout); *status = HAL_ERR_CANSessionMux_InvalidBuffer; return; } } if (periodMs > 0) { canState->writeLoopRunner.ExecAsync( [busId, periodMs, frame](wpi::net::uv::Loop& loop) { canState->AddPeriodic(loop, busId, periodMs, frame); }); } } void HAL_CAN_ReceiveMessage(int32_t busId, uint32_t messageId, struct HAL_CANReceiveMessage* message, int32_t* status) { if (busId < 0 || busId >= wpi::hal::kNumCanBuses) { message->message.dataSize = 0; message->timeStamp = 0; *status = HAL_PARAMETER_OUT_OF_RANGE; return; } std::scoped_lock lock{canState->readMutex[busId]}; auto& msg = canState->readFrames[busId][messageId]; if (msg.message.timeStamp == 0) { message->message.dataSize = 0; message->timeStamp = 0; *status = HAL_ERR_CANSessionMux_MessageNotFound; return; } *message = msg.message; msg.message.timeStamp = 0; *status = 0; return; } HAL_CANStreamHandle HAL_CAN_OpenStreamSession(int32_t busId, uint32_t messageId, uint32_t messageIdMask, uint32_t maxMessages, int32_t* status) { if (busId < 0 || busId >= wpi::hal::kNumCanBuses) { *status = HAL_PARAMETER_OUT_OF_RANGE; return HAL_INVALID_HANDLE; } auto can = std::make_shared(maxMessages, busId, messageIdMask, messageId); auto handle = canStreamHandles->Allocate(can); if (handle == HAL_INVALID_HANDLE) { *status = HAL_NO_AVAILABLE_RESOURCES; return HAL_INVALID_HANDLE; } std::scoped_lock lock{canState->readMutex[can->canBusId]}; canState->canStreams[can->canBusId].push_back(can.get()); return handle; } void HAL_CAN_CloseStreamSession(HAL_CANStreamHandle sessionHandle) { auto can = canStreamHandles->Free(sessionHandle); if (can == nullptr) { return; } std::scoped_lock lock{canState->readMutex[can->canBusId]}; std::erase(canState->canStreams[can->canBusId], can.get()); } void HAL_CAN_ReadStreamSession(HAL_CANStreamHandle sessionHandle, struct HAL_CANStreamMessage* messages, uint32_t messagesToRead, uint32_t* messagesRead, int32_t* status) { if (messages == nullptr || messagesRead == nullptr) { *status = HAL_PARAMETER_OUT_OF_RANGE; return; } auto can = canStreamHandles->Get(sessionHandle); if (!can) { *status = HAL_HANDLE_ERROR; return; } std::scoped_lock lock{canState->readMutex[can->canBusId]}; size_t readCount = (std::min)(static_cast(messagesToRead), can->receivedMessages.size()); for (size_t i = 0; i < readCount; i++) { messages[i] = can->receivedMessages.pop_front(); } *messagesRead = readCount; if (can->didOverflow) { can->didOverflow = false; *status = HAL_ERR_CANSessionMux_SessionOverrun; } return; } void HAL_CAN_GetCANStatus(int32_t busId, float* percentBusUtilization, uint32_t* busOffCount, uint32_t* txFullCount, uint32_t* receiveErrorCount, uint32_t* transmitErrorCount, int32_t* status) { *status = HAL_HANDLE_ERROR; return; } } // extern "C"