/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PosixSerialPlatformService.h" #include #include #include #include #include #include #include #include "SerialLogging.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsString.h" #include "nsThreadUtils.h" #ifdef XP_MACOSX # include # include # include # include # include # ifndef kIOMainPortDefault # define kIOMainPortDefault kIOMasterPortDefault # endif #endif #ifdef TCGETS2 // Support non-standard baud rates through TCGETS2/TCSETS2 # define LINUX_NSTD_BAUD 1 #endif #ifdef LINUX_NSTD_BAUD # include #else # include #endif #ifdef XP_LINUX # include #endif namespace mozilla::dom { constexpr int kMaxReadBufferSize = 4096; constexpr int kPollTimeoutMs = 100; // Maximum total time to spend in a single Write() call before giving up. // Prevents indefinite blocking when a serial port's buffer is full (e.g. // due to flow control), which would block all other serial ports. constexpr int kWriteTimeoutMs = 5000; #ifdef XP_MACOSX constexpr size_t kDeviceNameBufferSize = 256; #endif // Returns true if aDevpath refers to a real serial device by attempting // to open it and issuing a TIOCMGET ioctl. This filters out phantom ttyS* // entries and non-serial tty devices. static bool IsRealSerialPort(const char* aDevpath) { int fd = open(aDevpath, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd < 0) { return false; } int status; bool isReal = ioctl(fd, TIOCMGET, &status) == 0; close(fd); return isReal; } PosixSerialPlatformService::PosixSerialPlatformService() #ifdef XP_LINUX : mMonitor(nullptr), mMonitorSourceID(0) #elif defined(XP_MACOSX) : mNotificationPort(nullptr), mAddedIterator(0), mRemovedIterator(0) #endif { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p] created", this)); } PosixSerialPlatformService::~PosixSerialPlatformService() { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p] destroyed", this)); } nsresult PosixSerialPlatformService::Init() { return StartMonitoring(); } void PosixSerialPlatformService::Shutdown() { if (IsShutdown()) { return; } MOZ_LOG(gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::Shutdown (closing %u open ports)", this, mOpenPorts.Count())); StopMonitoring(); mOpenPorts.Clear(); SerialPlatformService::Shutdown(); } nsresult PosixSerialPlatformService::EnumeratePortsImpl( SerialPortList& aPorts) { aPorts.Clear(); MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts starting enumeration", this)); #ifdef XP_MACOSX io_iterator_t serialPortIterator; CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); if (classesToMatch == nullptr) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::EnumeratePorts IOServiceMatching " "failed", this)); return NS_ERROR_FAILURE; } // The call to IOServiceGetMatchingServices consumes a ref to classesToMatch. if (IOServiceGetMatchingServices(kIOMainPortDefault, classesToMatch, &serialPortIterator) != KERN_SUCCESS) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::EnumeratePorts " "IOServiceGetMatchingServices failed", this)); return NS_ERROR_FAILURE; } io_object_t serialService; while ((serialService = IOIteratorNext(serialPortIterator))) { IPCSerialPortInfo info; if (ExtractDeviceInfo(serialService, info)) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts found port: " "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x", this, NS_ConvertUTF16toUTF8(info.path()).get(), NS_ConvertUTF16toUTF8(info.friendlyName()).get(), info.usbVendorId().valueOr(0), info.usbProductId().valueOr(0))); aPorts.AppendElement(info); } IOObjectRelease(serialService); } IOObjectRelease(serialPortIterator); #elif defined(XP_LINUX) // Use an IIFE to avoid excessive nesting [&]() { if (!mUdevLib) { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::EnumeratePorts udev not " "available, falling back to /dev scan", this)); return; } udev_enumerate* enumerate = mUdevLib->udev_enumerate_new(mUdevLib->udev); if (!enumerate) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::EnumeratePorts " "udev_enumerate_new failed", this)); return; } auto cleanupEnum = MakeScopeExit([&]() { mUdevLib->udev_enumerate_unref(enumerate); }); if (mUdevLib->udev_enumerate_add_match_subsystem(enumerate, "tty") < 0 || mUdevLib->udev_enumerate_scan_devices(enumerate) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::EnumeratePorts " "udev enumerate setup failed", this)); return; } for (udev_list_entry* entry = mUdevLib->udev_enumerate_get_list_entry(enumerate); entry; entry = mUdevLib->udev_list_entry_get_next(entry)) { const char* syspath = mUdevLib->udev_list_entry_get_name(entry); if (!syspath) { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::EnumeratePorts " "udev_list_entry_get_name returned null", this)); continue; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts examining " "syspath=%s", this, syspath)); udev_device* dev = mUdevLib->udev_device_new_from_syspath(mUdevLib->udev, syspath); if (!dev) { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::EnumeratePorts " "udev_device_new_from_syspath failed for %s", this, syspath)); continue; } auto cleanupDev = MakeScopeExit([&]() { mUdevLib->udev_device_unref(dev); }); const char* devnode = mUdevLib->udev_device_get_devnode(dev); if (!devnode) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts " "no devnode for syspath=%s", this, syspath)); continue; } if (!IsRealSerialPort(devnode)) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts " "rejecting device devnode=%s (not a real serial port)", this, devnode)); continue; } IPCSerialPortInfo info; PopulatePortInfoFromUdev(dev, devnode, info); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts found port: " "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x", this, NS_ConvertUTF16toUTF8(info.path()).get(), NS_ConvertUTF16toUTF8(info.friendlyName()).get(), info.usbVendorId().valueOr(0), info.usbProductId().valueOr(0))); aPorts.AppendElement(info); } }(); // Also scan /dev/ directly for onboard serial ports that may not appear // in the udev enumeration (e.g. on minimal or embedded Linux systems). DIR* devDir = opendir("/dev"); if (devDir) { auto cleanupDir = MakeScopeExit([&]() { closedir(devDir); }); while (struct dirent* ent = readdir(devDir)) { nsAutoCString devpath("/dev/"); devpath.Append(ent->d_name); // Skip if already found by udev enumeration. NS_ConvertUTF8toUTF16 devpathUtf16(devpath); bool alreadyFound = false; for (const auto& existing : aPorts) { if (existing.path() == devpathUtf16) { alreadyFound = true; break; } } if (alreadyFound) { continue; } if (!IsRealSerialPort(devpath.get())) { continue; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts found onboard " "port from /dev scan: %s", this, devpath.get())); IPCSerialPortInfo info; bool haveSetInfo = false; // Try udev lookup for richer metadata (product name, vendor/product IDs). nsAutoCString syspath("/sys/class/tty/"); syspath.Append(ent->d_name); udev_device* dev = mUdevLib ? mUdevLib->udev_device_new_from_syspath( mUdevLib->udev, syspath.get()) : nullptr; if (dev) { PopulatePortInfoFromUdev(dev, devpath.get(), info); mUdevLib->udev_device_unref(dev); haveSetInfo = true; } if (!haveSetInfo) { info.id() = devpathUtf16; info.path() = devpathUtf16; info.friendlyName() = NS_ConvertUTF8toUTF16(nsDependentCString(ent->d_name)); } aPorts.AppendElement(info); } } #endif MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::EnumeratePorts found %zu ports", this, aPorts.Length())); return NS_OK; } int PosixSerialPlatformService::FindPortFd(const nsString& aPortId) { AssertIsOnIOThread(); if (auto entry = mOpenPorts.Lookup(aPortId)) { return entry.Data().get(); } return -1; } // Returns whether the conversion succeeded static bool ConvertBaudRate(uint32_t aBaudRate, speed_t& aSpeed) { #define BAUDRATE_TO_SPEED_CASE(x) \ case x: \ aSpeed = B##x; \ return true; switch (aBaudRate) { BAUDRATE_TO_SPEED_CASE(0) BAUDRATE_TO_SPEED_CASE(50) BAUDRATE_TO_SPEED_CASE(75) BAUDRATE_TO_SPEED_CASE(110) BAUDRATE_TO_SPEED_CASE(134) BAUDRATE_TO_SPEED_CASE(150) BAUDRATE_TO_SPEED_CASE(200) BAUDRATE_TO_SPEED_CASE(300) BAUDRATE_TO_SPEED_CASE(600) BAUDRATE_TO_SPEED_CASE(1200) BAUDRATE_TO_SPEED_CASE(1800) BAUDRATE_TO_SPEED_CASE(2400) BAUDRATE_TO_SPEED_CASE(4800) BAUDRATE_TO_SPEED_CASE(9600) BAUDRATE_TO_SPEED_CASE(19200) BAUDRATE_TO_SPEED_CASE(38400) BAUDRATE_TO_SPEED_CASE(57600) BAUDRATE_TO_SPEED_CASE(115200) #ifdef B230400 BAUDRATE_TO_SPEED_CASE(230400) #endif #ifdef B460800 BAUDRATE_TO_SPEED_CASE(460800) #endif #ifdef B576000 BAUDRATE_TO_SPEED_CASE(576000) #endif #ifdef B921600 BAUDRATE_TO_SPEED_CASE(921600) #endif } #undef BAUDRATE_TO_SPEED_CASE return false; } nsresult PosixSerialPlatformService::ConfigurePort( int aFd, const IPCSerialOptions& aOptions) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::ConfigurePort fd=%d, baudRate=%u, " "dataBits=%u, stopBits=%u, parity=%u, flowControl=%u", this, aFd, aOptions.baudRate(), aOptions.dataBits(), aOptions.stopBits(), static_cast(aOptions.parity()), static_cast(aOptions.flowControl()))); #ifdef LINUX_NSTD_BAUD struct termios2 tty; if (ioctl(aFd, TCGETS2, &tty) < 0) { #else struct termios tty; if (tcgetattr(aFd, &tty) != 0) { #endif MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort tcgetattr/ioctl " "failed: " "errno=%d", this, errno)); return NS_ERROR_FAILURE; } tty.c_cflag |= (CLOCAL | CREAD); tty.c_cflag &= ~HUPCL; tty.c_cflag &= ~CSIZE; switch (aOptions.dataBits()) { case 7: tty.c_cflag |= CS7; break; case 8: tty.c_cflag |= CS8; break; default: MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort invalid dataBits: %u", this, aOptions.dataBits())); return NS_ERROR_INVALID_ARG; } switch (aOptions.stopBits()) { case 1: tty.c_cflag &= ~CSTOPB; break; case 2: tty.c_cflag |= CSTOPB; break; default: MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort invalid stopBits: %u", this, aOptions.stopBits())); return NS_ERROR_INVALID_ARG; } switch (aOptions.parity()) { case ParityType::None: tty.c_cflag &= ~PARENB; tty.c_iflag |= IGNPAR; tty.c_iflag &= ~INPCK; break; case ParityType::Even: tty.c_cflag |= PARENB; tty.c_cflag &= ~PARODD; tty.c_iflag &= ~IGNPAR; tty.c_iflag |= INPCK; break; case ParityType::Odd: tty.c_cflag |= (PARENB | PARODD); tty.c_iflag &= ~IGNPAR; tty.c_iflag |= INPCK; break; } switch (aOptions.flowControl()) { case FlowControlType::None: tty.c_cflag &= ~CRTSCTS; break; case FlowControlType::Hardware: tty.c_cflag |= CRTSCTS; break; } // Set flags for raw operation tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG | IEXTEN); tty.c_oflag &= ~OPOST; tty.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); tty.c_iflag |= PARMRK; tty.c_cc[VMIN] = 0; tty.c_cc[VTIME] = 0; speed_t speed; #if defined(XP_MACOSX) // The IOSSIOSPEED ioctl() is only available on Mac. bool need_speedioctl = false; #endif if (ConvertBaudRate(aOptions.baudRate(), speed)) { #ifdef XP_LINUX tty.c_cflag &= ~CBAUD; tty.c_cflag |= speed; #else if (cfsetispeed(&tty, speed) != 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort cfsetispeed failed: " "errno=%d", this, errno)); return NS_ERROR_FAILURE; } if (cfsetospeed(&tty, speed) != 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort cfsetospeed failed: " "errno=%d", this, errno)); return NS_ERROR_FAILURE; } #endif } else { // Attempt to use a custom baud rate MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::ConfigurePort attempting to use " "custom baudRate: %u", this, aOptions.baudRate())); #ifdef LINUX_NSTD_BAUD tty.c_cflag &= ~CBAUD; tty.c_cflag |= CBAUDEX; tty.c_ispeed = aOptions.baudRate(); tty.c_ospeed = aOptions.baudRate(); #elif defined(XP_MACOSX) // cfsetispeed() and cfsetospeed() sometimes work for custom baud rates // on Mac, but the ioctl is more reliable. But we have to set it after // setting everything else or it will get overwritten. need_speedioctl = true; #else // User requested a custom baud rate but we don't have TCSETS2 or // IOSSIOSPEED ioctl's, so there's nothing we can do. MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort could not set " "custom baudRate: %u", this, aOptions.baudRate())); return NS_ERROR_FAILURE; #endif } #ifdef LINUX_NSTD_BAUD if (ioctl(aFd, TCSETS2, &tty) < 0) { #else if (tcsetattr(aFd, TCSANOW, &tty) != 0) { #endif MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort tcsetattr/ioctl " "failed: " "errno=%d", this, errno)); return NS_ERROR_FAILURE; } #if defined(XP_MACOSX) if (need_speedioctl) { speed = aOptions.baudRate(); if (ioctl(aFd, IOSSIOSPEED, &speed) == -1) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::ConfigurePort IOSSIOSPEED " "ioctl failed to set baudRate %u: " "errno=%d", this, aOptions.baudRate(), errno)); return NS_ERROR_FAILURE; } } #endif MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::ConfigurePort configuration successful", this)); return NS_OK; } nsresult PosixSerialPlatformService::OpenImpl( const nsString& aPortId, const IPCSerialOptions& aOptions) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Open portId=%s, baudRate=%u", this, NS_ConvertUTF16toUTF8(aPortId).get(), aOptions.baudRate())); AssertIsOnIOThread(); // Reject path traversal attempts (e.g. "/dev/tty/../../../etc/passwd"). if (aPortId.Find(u"/.."_ns) != kNotFound) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Open rejected portId '%s': " "path traversal detected", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_INVALID_ARG; } // Validate portId is a serial device path. This prevents a compromised // content process from using a crafted portId to open arbitrary files. if (!IsRealSerialPort(NS_ConvertUTF16toUTF8(aPortId).get())) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Open rejected invalid portId " "'%s': not a serial device path", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_INVALID_ARG; } if (mOpenPorts.Contains(aPortId)) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Open port already open: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_FILE_IS_LOCKED; } NS_ConvertUTF16toUTF8 path(aPortId); int fd = open(path.get(), O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Open open() failed for %s: errno=%d", this, path.get(), errno)); if (errno == EACCES) { return NS_ERROR_FILE_ACCESS_DENIED; } else if (errno == ENOENT) { return NS_ERROR_FILE_NOT_FOUND; } else if (errno == EBUSY) { return NS_ERROR_FILE_IS_LOCKED; } else { return NS_ERROR_NOT_AVAILABLE; } } mozilla::UniqueFileHandle handle(fd); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Open opened fd=%d", this, fd)); if (ioctl(fd, TIOCEXCL) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::Open TIOCEXCL failed: errno=%d", this, errno)); } nsresult rv = ConfigurePort(fd, aOptions); if (NS_FAILED(rv)) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Open ConfigurePort failed: 0x%08x", this, static_cast(rv))); return rv; } #ifdef LINUX_NSTD_BAUD ioctl(fd, TCFLSH, TCIOFLUSH); #else tcflush(fd, TCIOFLUSH); #endif mOpenPorts.InsertOrUpdate(aPortId, std::move(handle)); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Open successfully opened port %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_OK; } nsresult PosixSerialPlatformService::CloseImpl(const nsString& aPortId) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Close portId=%s", this, NS_ConvertUTF16toUTF8(aPortId).get())); AssertIsOnIOThread(); if (!mOpenPorts.Remove(aPortId)) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Close port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Close successfully closed port %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_OK; } nsresult PosixSerialPlatformService::ReadImpl(const nsString& aPortId, Span aBuf, uint32_t& aBytesRead) { int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Read port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } int bytesAvailable = 0; if (ioctl(fd, FIONREAD, &bytesAvailable) < 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Read ioctl FIONREAD failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } if (bytesAvailable == 0) { return NS_OK; } int bytesToRead = std::min( {bytesAvailable, kMaxReadBufferSize, static_cast(aBuf.Length())}); ssize_t bytesRead = read(fd, aBuf.Elements(), bytesToRead); if (bytesRead < 0) { if (errno == EAGAIN) { MOZ_LOG( gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Read would block, no data", this)); return NS_OK; } MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Read read() failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } aBytesRead = static_cast(bytesRead); MOZ_LOG( gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Read read %zd bytes", this, bytesRead)); return NS_OK; } nsresult PosixSerialPlatformService::WriteImpl(const nsString& aPortId, Span aData) { int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } if (aData.IsEmpty()) { MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Write empty data", this)); return NS_OK; } MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Write writing %zu bytes", this, aData.Length())); size_t totalWritten = 0; const uint8_t* buffer = aData.Elements(); size_t remaining = aData.Length(); int totalPollTimeMs = 0; while (remaining > 0) { ssize_t bytesWritten = write(fd, buffer + totalWritten, remaining); if (bytesWritten < 0) { if (errno == EINTR) { continue; } if (errno == EAGAIN) { if (totalPollTimeMs >= kWriteTimeoutMs) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write timed out after %dms " "with %zu bytes remaining", this, totalPollTimeMs, remaining)); return NS_ERROR_NET_TIMEOUT; } struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; int pollResult = poll(&pfd, 1, kPollTimeoutMs); if (pollResult < 0) { if (errno == EINTR) { continue; } MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write poll failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } if (pollResult == 0) { totalPollTimeMs += kPollTimeoutMs; continue; } if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write poll error: " "revents=0x%x", this, pfd.revents)); return NS_ERROR_FAILURE; } continue; } MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write write() failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } if (bytesWritten == 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Write write() returned 0", this)); return NS_ERROR_FAILURE; } totalWritten += bytesWritten; remaining -= bytesWritten; totalPollTimeMs = 0; if (remaining > 0) { MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Write partial write: %zd " "bytes, %zu " "remaining", this, bytesWritten, remaining)); } } MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::Write successfully wrote %zu bytes", this, totalWritten)); return NS_OK; } nsresult PosixSerialPlatformService::DrainImpl(const nsString& aPortId) { int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Drain port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Drain draining transmit buffers", this)); #ifdef LINUX_NSTD_BAUD if (ioctl(fd, TCSBRK, 1) < 0) { #else if (tcdrain(fd) < 0) { #endif MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Drain tcdrain() failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Drain successfully drained buffers", this)); return NS_OK; } nsresult PosixSerialPlatformService::FlushImpl(const nsString& aPortId, bool aReceive) { int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Flush port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } int queue = aReceive ? TCIFLUSH : TCOFLUSH; MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Flush discarding %s buffers", this, aReceive ? "receive" : "transmit")); #ifdef LINUX_NSTD_BAUD if (ioctl(fd, TCFLSH, queue) < 0) { #else if (tcflush(fd, queue) < 0) { #endif MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::Flush tcflush() failed: errno=%d", this, errno)); return NS_ERROR_FAILURE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::Flush successfully flushed %s " "buffers", this, aReceive ? "receive" : "transmit")); return NS_OK; } nsresult PosixSerialPlatformService::SetSignalsImpl( const nsString& aPortId, const IPCSerialOutputSignals& aSignals) { MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::SetSignals portId=%s, DTR=%s, RTS=%s, " "Break=%s", this, NS_ConvertUTF16toUTF8(aPortId).get(), aSignals.dataTerminalReady().isSome() ? (aSignals.dataTerminalReady().value() ? "true" : "false") : "unchanged", aSignals.requestToSend().isSome() ? (aSignals.requestToSend().value() ? "true" : "false") : "unchanged", aSignals.breakSignal().isSome() ? (aSignals.breakSignal().value() ? "true" : "false") : "unchanged")); int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::SetSignals port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } if (aSignals.dataTerminalReady().isSome() || aSignals.requestToSend().isSome()) { int status; if (ioctl(fd, TIOCMGET, &status) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::SetSignals failure to get " "status on %d: errno=%d", this, fd, errno)); return NS_ERROR_FAILURE; } MOZ_LOG(gWebSerialLog, LogLevel::Verbose, ("PosixSerialPlatformService[%p]::SetSignals got status on %d: " "status=0x%x", this, fd, uint32_t(status))); // The spec requires that we set DTR first, then RTS, then break. // We could do this with TIOCMBIS or TIOCMBIC to set or clear // individual flags, but that seemed to return ENOTTY on Mac. // So instead we somewhat clunkily get the current flags, toggle // the bit, then set the flags for each thing we want to change. if (aSignals.dataTerminalReady().isSome()) { if (aSignals.dataTerminalReady().value()) { status |= TIOCM_DTR; } else { status &= ~TIOCM_DTR; } if (ioctl(fd, TIOCMSET, &status) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::SetSignals set DTR status on " "%d failed: " "newStatus=%x errno=%d", this, fd, uint32_t(status), errno)); return NS_ERROR_FAILURE; } } if (aSignals.requestToSend().isSome()) { if (aSignals.requestToSend().value()) { status |= TIOCM_RTS; } else { status &= ~TIOCM_RTS; } if (ioctl(fd, TIOCMSET, &status) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::SetSignals set RTS status on " "%d failed: " "newStatus=%x errno=%d", this, fd, uint32_t(status), errno)); return NS_ERROR_FAILURE; } } } if (aSignals.breakSignal().isSome()) { int cmd = aSignals.breakSignal().value() ? TIOCSBRK : TIOCCBRK; if (ioctl(fd, cmd) < 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::SetSignals ioctl %s on %d failed: " "errno=%d", this, aSignals.breakSignal().value() ? "TIOCSBRK" : "TIOCCBRK", fd, errno)); return NS_ERROR_FAILURE; } } MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::SetSignals signals set successfully", this)); return NS_OK; } nsresult PosixSerialPlatformService::GetSignalsImpl( const nsString& aPortId, IPCSerialInputSignals& aSignals) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::GetSignals portId=%s", this, NS_ConvertUTF16toUTF8(aPortId).get())); int fd = FindPortFd(aPortId); if (fd < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::GetSignals port not found: %s", this, NS_ConvertUTF16toUTF8(aPortId).get())); return NS_ERROR_NOT_AVAILABLE; } int status; if (ioctl(fd, TIOCMGET, &status) < 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::GetSignals ioctl TIOCMGET failed: " "errno=%d", this, errno)); return NS_ERROR_FAILURE; } aSignals = IPCSerialInputSignals{ (status & TIOCM_CAR) != 0, // dataCarrierDetect (status & TIOCM_CTS) != 0, // clearToSend (status & TIOCM_RNG) != 0, // ringIndicator (status & TIOCM_DSR) != 0 // dataSetReady }; MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::GetSignals DCD=%d, CTS=%d, RI=%d, " "DSR=%d", this, aSignals.dataCarrierDetect(), aSignals.clearToSend(), aSignals.ringIndicator(), aSignals.dataSetReady())); return NS_OK; } nsresult PosixSerialPlatformService::StartMonitoring() { #ifdef XP_LINUX return InitializeUdev(); #elif defined(XP_MACOSX) return InitializeMacOS(); #else return NS_OK; #endif } #ifdef XP_MACOSX nsresult PosixSerialPlatformService::InitializeMacOS() { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::InitializeMacOS setting up IOKit " "notifications", this)); mNotificationPort = IONotificationPortCreate(kIOMainPortDefault); if (!mNotificationPort) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeMacOS " "IONotificationPortCreate failed", this)); return NS_ERROR_FAILURE; } CFRunLoopAddSource(CFRunLoopGetMain(), IONotificationPortGetRunLoopSource(mNotificationPort), kCFRunLoopDefaultMode); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::InitializeMacOS added run loop " "source to main run loop", this)); CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOSerialBSDServiceValue); if (!matchingDict) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeMacOS IOServiceMatching " "failed", this)); return NS_ERROR_FAILURE; } // The call to IOServiceAddMatchingNotification consumes a ref to // matchingDict. kern_return_t kr = IOServiceAddMatchingNotification( mNotificationPort, kIOMatchedNotification, matchingDict, DeviceAddedCallback, this, &mAddedIterator); if (kr != KERN_SUCCESS) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeMacOS " "IOServiceAddMatchingNotification for added devices failed: %d", this, kr)); return NS_ERROR_FAILURE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::InitializeMacOS draining initial " "added device iterator", this)); OnDeviceAdded(mAddedIterator, true); // The call to IOServiceAddMatchingNotification consumes a ref to // matchingDict2. CFMutableDictionaryRef matchingDict2 = IOServiceMatching(kIOSerialBSDServiceValue); if (!matchingDict2) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeMacOS IOServiceMatching " "failed for removed devices", this)); return NS_ERROR_FAILURE; } kr = IOServiceAddMatchingNotification( mNotificationPort, kIOTerminatedNotification, matchingDict2, DeviceRemovedCallback, this, &mRemovedIterator); if (kr != KERN_SUCCESS) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeMacOS " "IOServiceAddMatchingNotification for removed devices failed: %d", this, kr)); return NS_ERROR_FAILURE; } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::InitializeMacOS draining initial " "removed device iterator", this)); OnDeviceRemoved(mRemovedIterator, true); MOZ_LOG(gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::InitializeMacOS monitoring started", this)); return NS_OK; } #endif void PosixSerialPlatformService::StopMonitoring() { #ifdef XP_LINUX ShutdownUdev(); #elif defined(XP_MACOSX) if (mAddedIterator) { IOObjectRelease(mAddedIterator); mAddedIterator = 0; } if (mRemovedIterator) { IOObjectRelease(mRemovedIterator); mRemovedIterator = 0; } if (mNotificationPort) { CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(mNotificationPort); if (runLoopSource) { CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource, kCFRunLoopDefaultMode); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::StopMonitoring removed run " "loop source from main run loop", this)); } IONotificationPortDestroy(mNotificationPort); mNotificationPort = nullptr; } MOZ_LOG(gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::StopMonitoring monitoring stopped", this)); #endif } #ifdef XP_LINUX nsresult PosixSerialPlatformService::InitializeUdev() { mUdevLib = MakeUnique(); if (!*mUdevLib) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev udev_lib " "initialization failed", this)); mUdevLib = nullptr; return NS_ERROR_FAILURE; } mMonitor = mUdevLib->udev_monitor_new_from_netlink(mUdevLib->udev, "udev"); if (!mMonitor) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev " "udev_monitor_new_from_netlink failed", this)); mUdevLib = nullptr; return NS_ERROR_FAILURE; } auto cleanupUdevAndMonitor = MakeScopeExit([&]() { mUdevLib->udev_monitor_unref(mMonitor); mMonitor = nullptr; mUdevLib = nullptr; }); if (mUdevLib->udev_monitor_filter_add_match_subsystem_devtype(mMonitor, "tty", nullptr) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev filter add failed", this)); return NS_ERROR_FAILURE; } if (mUdevLib->udev_monitor_enable_receiving(mMonitor) < 0) { MOZ_LOG(gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev enable receiving " "failed", this)); return NS_ERROR_FAILURE; } int fd = mUdevLib->udev_monitor_get_fd(mMonitor); if (fd < 0) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev get fd failed", this)); return NS_ERROR_FAILURE; } GIOChannel* channel = g_io_channel_unix_new(fd); if (!channel) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::InitializeUdev g_io_channel_unix_new " "failed", this)); return NS_ERROR_FAILURE; } // udev operations did not error, so don't clean it up here cleanupUdevAndMonitor.release(); mMonitorSourceID = g_io_add_watch(channel, GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP), OnUdevMonitor, this); g_io_channel_unref(channel); MOZ_LOG(gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::InitializeUdev udev monitoring " "initialized", this)); return NS_OK; } void PosixSerialPlatformService::ShutdownUdev() { if (mMonitorSourceID) { g_source_remove(mMonitorSourceID); mMonitorSourceID = 0; } if (mMonitor && mUdevLib) { mUdevLib->udev_monitor_unref(mMonitor); mMonitor = nullptr; } mUdevLib = nullptr; MOZ_LOG(gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::ShutdownUdev udev monitoring " "shutdown", this)); } gboolean PosixSerialPlatformService::OnUdevMonitor(GIOChannel* source, GIOCondition condition, gpointer data) { PosixSerialPlatformService* service = static_cast(data); if (condition & (G_IO_ERR | G_IO_HUP)) { MOZ_LOG( gWebSerialLog, LogLevel::Error, ("PosixSerialPlatformService[%p]::OnUdevMonitor error condition: %d", service, condition)); return FALSE; } if (condition & G_IO_IN) { service->ReadUdevChange(); } return TRUE; } void PosixSerialPlatformService::PopulatePortInfoFromUdev( udev_device* aDev, const char* aDevnode, IPCSerialPortInfo& aPortInfo) { MOZ_ASSERT(mUdevLib); NS_ConvertUTF8toUTF16 path(aDevnode); aPortInfo.id() = path; aPortInfo.path() = path; const char* productName = mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL_FROM_DATABASE"); if (!productName) { productName = mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL"); } if (productName) { aPortInfo.friendlyName() = NS_ConvertUTF8toUTF16(productName); } else { const char* basename = strrchr(aDevnode, '/'); aPortInfo.friendlyName() = NS_ConvertUTF8toUTF16(basename ? basename + 1 : aDevnode); } const char* vendorIdStr = mUdevLib->udev_device_get_property_value(aDev, "ID_VENDOR_ID"); const char* productIdStr = mUdevLib->udev_device_get_property_value(aDev, "ID_MODEL_ID"); if (vendorIdStr && productIdStr) { unsigned int vendorId, productId; if (sscanf(vendorIdStr, "%x", &vendorId) == 1 && sscanf(productIdStr, "%x", &productId) == 1) { aPortInfo.usbVendorId() = Some(static_cast(vendorId)); aPortInfo.usbProductId() = Some(static_cast(productId)); } } } void PosixSerialPlatformService::ReadUdevChange() { if (IsShutdown() || !mUdevLib) { return; } udev_device* dev = mUdevLib->udev_monitor_receive_device(mMonitor); if (!dev) { return; } auto cleanupDev = MakeScopeExit([&]() { mUdevLib->udev_device_unref(dev); }); const char* action = mUdevLib->udev_device_get_action(dev); const char* devnode = mUdevLib->udev_device_get_devnode(dev); if (!action || !devnode) { return; } MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::ReadUdevChange action=%s, devnode=%s", this, action, devnode)); if (strcmp(action, "add") == 0) { IPCSerialPortInfo portInfo; PopulatePortInfoFromUdev(dev, devnode, portInfo); MOZ_LOG( gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::ReadUdevChange device connected: " "path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x", this, devnode, NS_ConvertUTF16toUTF8(portInfo.friendlyName()).get(), portInfo.usbVendorId().valueOr(0), portInfo.usbProductId().valueOr(0))); NotifyPortConnected(portInfo); } else if (strcmp(action, "remove") == 0) { MOZ_LOG( gWebSerialLog, LogLevel::Info, ("PosixSerialPlatformService[%p]::ReadUdevChange device disconnected: " "path=%s", this, devnode)); NotifyPortDisconnected(NS_ConvertUTF8toUTF16(devnode)); } } #endif #ifdef XP_MACOSX bool PosixSerialPlatformService::ExtractDeviceInfo( io_service_t device, IPCSerialPortInfo& portInfo) { if (!device) { MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::ExtractDeviceInfo device is null, " "device may be terminated", this)); return false; } CFTypeRef pathRef = IORegistryEntryCreateCFProperty( device, CFSTR(kIODialinDeviceKey), kCFAllocatorDefault, 0); auto cleanupPath = MakeScopeExit([&]() { if (pathRef) CFRelease(pathRef); }); if (!pathRef) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::ExtractDeviceInfo failed to get " "device path, device may be terminated", this)); return false; } if (CFGetTypeID(pathRef) != CFStringGetTypeID()) { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::ExtractDeviceInfo device path is " "not a string", this)); return false; } char devicePath[PATH_MAX]; if (!CFStringGetCString((CFStringRef)pathRef, devicePath, sizeof(devicePath), kCFStringEncodingUTF8)) { return false; } NS_ConvertUTF8toUTF16 path(devicePath); portInfo.id() = path; portInfo.path() = path; CFTypeRef baseNameRef = IORegistryEntryCreateCFProperty( device, CFSTR(kIOTTYBaseNameKey), kCFAllocatorDefault, 0); auto cleanupBaseName = MakeScopeExit([&]() { if (baseNameRef) CFRelease(baseNameRef); }); if (baseNameRef) { if (CFGetTypeID(baseNameRef) == CFStringGetTypeID()) { char baseName[kDeviceNameBufferSize]; if (CFStringGetCString((CFStringRef)baseNameRef, baseName, sizeof(baseName), kCFStringEncodingUTF8)) { NS_ConvertUTF8toUTF16 friendlyName(baseName); portInfo.friendlyName() = friendlyName; } } } // We search for these properties on all ancestors, because the registry tree // can have a serial service with a USB service as its ancestor. CFTypeRef vendorIdRef = IORegistryEntrySearchCFProperty( device, kIOServicePlane, CFSTR("idVendor"), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); CFTypeRef productIdRef = IORegistryEntrySearchCFProperty( device, kIOServicePlane, CFSTR("idProduct"), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); auto cleanupVendorIdAndProductId = MakeScopeExit([&]() { if (vendorIdRef) CFRelease(vendorIdRef); if (productIdRef) CFRelease(productIdRef); }); if (vendorIdRef && productIdRef && CFGetTypeID(vendorIdRef) == CFNumberGetTypeID() && CFGetTypeID(productIdRef) == CFNumberGetTypeID()) { SInt32 vendorId, productId; if (CFNumberGetValue((CFNumberRef)vendorIdRef, kCFNumberSInt32Type, &vendorId) && CFNumberGetValue((CFNumberRef)productIdRef, kCFNumberSInt32Type, &productId)) { portInfo.usbVendorId() = Some(static_cast(vendorId)); portInfo.usbProductId() = Some(static_cast(productId)); } } CFTypeRef productNameRef = IORegistryEntrySearchCFProperty( device, kIOServicePlane, CFSTR(kUSBProductString), kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); auto cleanupProductName = MakeScopeExit([&]() { if (productNameRef) CFRelease(productNameRef); }); if (productNameRef) { if (CFGetTypeID(productNameRef) == CFStringGetTypeID()) { char productName[kDeviceNameBufferSize]; if (CFStringGetCString((CFStringRef)productNameRef, productName, sizeof(productName), kCFStringEncodingUTF8)) { NS_ConvertUTF8toUTF16 friendlyName(productName); portInfo.friendlyName() = friendlyName; } } } if (portInfo.friendlyName().IsEmpty()) { portInfo.friendlyName() = path; } return true; } void PosixSerialPlatformService::DeviceAddedCallback(void* context, io_iterator_t iterator) { PosixSerialPlatformService* service = static_cast(context); MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::DeviceAddedCallback invoked", service)); service->OnDeviceAdded(iterator, false); } void PosixSerialPlatformService::DeviceRemovedCallback(void* context, io_iterator_t iterator) { PosixSerialPlatformService* service = static_cast(context); MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::DeviceRemovedCallback invoked", service)); service->OnDeviceRemoved(iterator, false); } void PosixSerialPlatformService::OnDeviceAdded(io_iterator_t iterator, bool aSkipNotify) { io_service_t device; int deviceCount = 0; while ((device = IOIteratorNext(iterator))) { deviceCount++; IPCSerialPortInfo portInfo; if (ExtractDeviceInfo(device, portInfo)) { MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::OnDeviceAdded skip=%d device " ": path=%s, friendlyName=%s, VID=0x%04x, PID=0x%04x", this, aSkipNotify, NS_ConvertUTF16toUTF8(portInfo.path()).get(), NS_ConvertUTF16toUTF8(portInfo.friendlyName()).get(), portInfo.usbVendorId().valueOr(0), portInfo.usbProductId().valueOr(0))); if (!aSkipNotify) { NotifyPortConnected(portInfo); } } IOObjectRelease(device); } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::OnDeviceAdded processed %d devices " "(skipNotify=%d)", this, deviceCount, aSkipNotify)); } void PosixSerialPlatformService::OnDeviceRemoved(io_iterator_t iterator, bool aSkipNotify) { io_service_t device; int deviceCount = 0; while ((device = IOIteratorNext(iterator))) { deviceCount++; IPCSerialPortInfo portInfo; if (ExtractDeviceInfo(device, portInfo)) { MOZ_LOG( gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::OnDeviceRemoved skip=%d " "device: path=%s", this, aSkipNotify, NS_ConvertUTF16toUTF8(portInfo.path()).get())); if (!aSkipNotify) { NotifyPortDisconnected(portInfo.path()); } } else { MOZ_LOG(gWebSerialLog, LogLevel::Warning, ("PosixSerialPlatformService[%p]::OnDeviceRemoved failed to " "extract device info, device may have been terminated", this)); } IOObjectRelease(device); } MOZ_LOG(gWebSerialLog, LogLevel::Debug, ("PosixSerialPlatformService[%p]::OnDeviceRemoved processed %d " "devices (skipNotify=%d)", this, deviceCount, aSkipNotify)); } #endif } // namespace mozilla::dom