Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "SerialLogging.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#ifdef XP_MACOSX
# include <CoreFoundation/CFNumber.h>
# include <IOKit/IOKitLib.h>
# include <IOKit/serial/IOSerialKeys.h>
# include <IOKit/serial/ioss.h>
# include <IOKit/usb/IOUSBLib.h>
# 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 <asm/termbits.h>
#else
# include <termios.h>
#endif
#ifdef XP_LINUX
# include <linux/serial.h>
#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<uint8_t>(aOptions.parity()),
static_cast<uint8_t>(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<uint32_t>(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<uint8_t> 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<int>(
{bytesAvailable, kMaxReadBufferSize, static_cast<int>(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<uint32_t>(bytesRead);
MOZ_LOG(
gWebSerialLog, LogLevel::Verbose,
("PosixSerialPlatformService[%p]::Read read %zd bytes", this, bytesRead));
return NS_OK;
}
nsresult PosixSerialPlatformService::WriteImpl(const nsString& aPortId,
Span<const uint8_t> 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<udev_lib>();
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<PosixSerialPlatformService*>(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<uint16_t>(vendorId));
aPortInfo.usbProductId() = Some(static_cast<uint16_t>(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<uint16_t>(vendorId));
portInfo.usbProductId() = Some(static_cast<uint16_t>(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<PosixSerialPlatformService*>(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<PosixSerialPlatformService*>(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