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 "Win32SerialPlatformService.h"
#include <cfgmgr32.h>
// Including initguid.h needs to come before including devpkey.h, so
// disable clang-format here.
// clang-format off
#include <initguid.h>
#include <devpkey.h>
// clang-format on
#include <ntddser.h>
#include <setupapi.h>
#include "SerialLogging.h"
#include "mozilla/ScopeExit.h"
#include "nsString.h"
#include "nsThreadUtils.h"
namespace mozilla::dom {
namespace {
constexpr size_t kPropertyBufferSize = 256;
constexpr unsigned int kDeviceChangeDelayMs = 200;
constexpr wchar_t kDevicePathPrefix[] = L"\\\\.\\";
} // namespace
Win32SerialPlatformService::Win32SerialPlatformService()
: mIOCapability(IOThread()) {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p] created", this));
MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
"SerialMonitorQueue", getter_AddRefs(mMonitorThread)));
}
nsresult Win32SerialPlatformService::Init() {
return StartMonitoringDeviceChanges();
}
void Win32SerialPlatformService::Shutdown() {
AssertIsOnMainThread();
if (IsShutdown()) {
return;
}
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::Shutdown", this));
StopMonitoringDeviceChanges();
mMonitorThread = nullptr;
SerialPlatformService::Shutdown();
}
Win32SerialPlatformService::~Win32SerialPlatformService() {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p] destroyed (closing %u ports)", this,
mOpenPorts.Count()));
for (auto iter = mOpenPorts.Iter(); !iter.Done(); iter.Next()) {
HANDLE handle = iter.Data();
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
mOpenPorts.Clear();
}
namespace {
nsresult EnumeratePortsWin32(SerialPortList& aPorts) {
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService::EnumeratePorts"));
aPorts.Clear();
HDEVINFO deviceInfoSet =
SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, nullptr, nullptr,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService::EnumeratePorts "
"SetupDiGetClassDevs failed: 0x%08lx",
error));
return NS_ERROR_FAILURE;
}
auto cleanupDeviceInfoSet =
MakeScopeExit([&]() { SetupDiDestroyDeviceInfoList(deviceInfoSet); });
SP_DEVINFO_DATA deviceInfo;
deviceInfo.cbSize = sizeof(SP_DEVINFO_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfo); ++i) {
HKEY hKey = SetupDiOpenDevRegKey(deviceInfoSet, &deviceInfo,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (hKey == INVALID_HANDLE_VALUE) {
continue;
}
wchar_t portName[kPropertyBufferSize];
{
auto cleanupRegKey = MakeScopeExit([&]() { RegCloseKey(hKey); });
DWORD portNameSize = sizeof(portName);
LONG result =
RegGetValueW(hKey, nullptr, L"PortName", RRF_RT_REG_SZ, nullptr,
reinterpret_cast<LPBYTE>(portName), &portNameSize);
if (result != ERROR_SUCCESS) {
continue;
}
}
wchar_t friendlyName[kPropertyBufferSize] = {0};
DWORD friendlyNameSize = sizeof(friendlyName);
// The bus reported device description is usually more descriptive than
// the friendly name (for LEGO Spike, Flipper Zero, etc.)
auto deviceDescriptionPropKey = DEVPKEY_Device_BusReportedDeviceDesc;
DEVPROPTYPE unusedPropertyType;
BOOL succeeded = SetupDiGetDevicePropertyW(
deviceInfoSet, &deviceInfo, &deviceDescriptionPropKey,
&unusedPropertyType, reinterpret_cast<PBYTE>(friendlyName),
friendlyNameSize, nullptr, 0);
if (!(succeeded && *friendlyName)) {
deviceDescriptionPropKey = DEVPKEY_Device_FriendlyName;
succeeded = SetupDiGetDevicePropertyW(
deviceInfoSet, &deviceInfo, &deviceDescriptionPropKey,
&unusedPropertyType, reinterpret_cast<PBYTE>(friendlyName),
friendlyNameSize, nullptr, 0);
}
if (!(succeeded && *friendlyName)) {
wcscpy_s(friendlyName, portName);
}
// SPDRP_HARDWAREID is of type REG_MULTI_SZ. We just are interested
// in the first one, and since each string is null-terminated, we'll
// just treat it as a regular string.
wchar_t hardwareId[kPropertyBufferSize] = {0};
SetupDiGetDeviceRegistryPropertyW(
deviceInfoSet, &deviceInfo, SPDRP_HARDWAREID, nullptr,
reinterpret_cast<PBYTE>(hardwareId), sizeof(hardwareId), nullptr);
IPCSerialPortInfo info;
info.id() = nsString(portName);
info.friendlyName() = nsString(friendlyName);
{
nsString path(kDevicePathPrefix);
path.Append(portName);
info.path() = std::move(path);
}
uint16_t vendorId = 0;
uint16_t productId = 0;
wchar_t* vidLocation = wcsstr(hardwareId, L"VID_");
if (vidLocation) {
swscanf_s(vidLocation + 4, L"%4hx", &vendorId);
}
wchar_t* pidLocation = wcsstr(hardwareId, L"PID_");
if (pidLocation) {
swscanf_s(pidLocation + 4, L"%4hx", &productId);
}
if (vendorId != 0 && productId != 0) {
info.usbVendorId() = Some(vendorId);
info.usbProductId() = Some(productId);
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService::EnumeratePorts found port '%s' (%s) "
"VID:0x%04x PID:0x%04x",
NS_ConvertUTF16toUTF8(info.id()).get(),
NS_ConvertUTF16toUTF8(info.friendlyName()).get(), vendorId,
productId));
aPorts.AppendElement(info);
}
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService::EnumeratePorts found %zu ports",
aPorts.Length()));
return NS_OK;
}
} // namespace
nsresult Win32SerialPlatformService::EnumeratePortsImpl(
SerialPortList& aPorts) {
return EnumeratePortsWin32(aPorts);
}
HANDLE Win32SerialPlatformService::FindPortHandle(const nsString& aPortId) {
mIOCapability.AssertOnCurrentThread();
return mOpenPorts.MaybeGet(aPortId).valueOr(INVALID_HANDLE_VALUE);
}
nsresult Win32SerialPlatformService::ConfigurePort(
HANDLE aHandle, const IPCSerialOptions& aOptions) {
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::ConfigurePort (baudRate=%u, "
"dataBits=%u, stopBits=%u, parity=%u, flowControl=%u)",
this, aOptions.baudRate(), aOptions.dataBits(), aOptions.stopBits(),
static_cast<unsigned>(aOptions.parity()),
static_cast<unsigned>(aOptions.flowControl())));
mIOCapability.AssertOnCurrentThread();
DCB dcb = {0};
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(aHandle, &dcb)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::ConfigurePort GetCommState failed: "
"0x%08lx",
this, error));
return NS_ERROR_FAILURE;
}
// These options are not configurable
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
dcb.fAbortOnError = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.BaudRate = aOptions.baudRate();
switch (aOptions.dataBits()) {
case 7:
dcb.ByteSize = 7;
break;
case 8:
dcb.ByteSize = 8;
break;
default:
return NS_ERROR_INVALID_ARG;
}
switch (aOptions.stopBits()) {
case 1:
dcb.StopBits = ONESTOPBIT;
break;
case 2:
dcb.StopBits = TWOSTOPBITS;
break;
default:
return NS_ERROR_INVALID_ARG;
}
switch (aOptions.parity()) {
case ParityType::None:
dcb.Parity = NOPARITY;
break;
case ParityType::Even:
dcb.Parity = EVENPARITY;
break;
case ParityType::Odd:
dcb.Parity = ODDPARITY;
break;
default:
return NS_ERROR_INVALID_ARG;
}
switch (aOptions.flowControl()) {
case FlowControlType::None:
dcb.fOutxCtsFlow = FALSE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
break;
case FlowControlType::Hardware:
dcb.fOutxCtsFlow = TRUE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
break;
default:
return NS_ERROR_INVALID_ARG;
}
if (!SetCommState(aHandle, &dcb)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::ConfigurePort SetCommState failed: "
"0x%08lx",
this, error));
return NS_ERROR_FAILURE;
}
COMMTIMEOUTS timeouts = {0};
// MAXDWORD + MAXDWORD + ReadTotalTimeoutConstant is a special combination:
// ReadFile returns immediately with available data, or waits up to
// ReadTotalTimeoutConstant ms if no data is in the buffer.
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
timeouts.ReadTotalTimeoutConstant = 5;
// Write timeout as a safety net.
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 5000;
if (!SetCommTimeouts(aHandle, &timeouts)) {
DWORD error = GetLastError();
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::ConfigurePort SetCommTimeouts "
"failed: 0x%08lx",
this, error));
return NS_ERROR_FAILURE;
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::ConfigurePort succeeded", this));
return NS_OK;
}
nsresult Win32SerialPlatformService::OpenImpl(
const nsString& aPortId, const IPCSerialOptions& aOptions) {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::Open port '%s'", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
mIOCapability.AssertOnCurrentThread();
// Validate portId format: must be "COM" followed by one or more digits.
// This prevents a compromised content process from using a crafted portId
// to open arbitrary devices in the \\.\ namespace (e.g. PhysicalDrive0).
if (aPortId.Length() < 4 || !StringBeginsWith(aPortId, u"COM"_ns)) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Open rejected invalid portId "
"'%s': bad prefix",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_INVALID_ARG;
}
for (uint32_t i = 3; i < aPortId.Length(); i++) {
if (!iswdigit(aPortId.CharAt(i))) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Open rejected invalid portId "
"'%s': non-digit character",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_INVALID_ARG;
}
}
if (mOpenPorts.Contains(aPortId)) {
MOZ_LOG(gWebSerialLog, LogLevel::Warning,
("Win32SerialPlatformService[%p]::Open port '%s' already open",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_FILE_IS_LOCKED;
}
nsString devicePath(kDevicePathPrefix);
devicePath.Append(aPortId);
HANDLE handle =
CreateFileW(devicePath.get(), GENERIC_READ | GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Open CreateFileW failed for port "
"'%s' at path '%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(),
NS_ConvertUTF16toUTF8(devicePath).get(), error));
if (error == ERROR_ACCESS_DENIED) {
return NS_ERROR_FILE_ACCESS_DENIED;
}
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv = ConfigurePort(handle, aOptions);
if (NS_FAILED(rv)) {
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Open ConfigurePort failed for port "
"'%s': 0x%08x",
this, NS_ConvertUTF16toUTF8(aPortId).get(),
static_cast<uint32_t>(rv)));
CloseHandle(handle);
return rv;
}
PurgeComm(handle, PURGE_RXCLEAR | PURGE_TXCLEAR);
mOpenPorts.InsertOrUpdate(aPortId, handle);
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::Open succeeded for port '%s'", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::CloseImpl(const nsString& aPortId) {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::Close port '%s'", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Warning,
("Win32SerialPlatformService[%p]::Close port '%s' not found", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
mOpenPorts.Remove(aPortId);
CloseHandle(handle);
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::Close succeeded for port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::ReadImpl(const nsString& aPortId,
Span<uint8_t> aBuf,
uint32_t& aBytesRead) {
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Read port '%s' not found", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
DWORD errors;
COMSTAT comStat;
if (!ClearCommError(handle, &errors, &comStat)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Read ClearCommError failed for port "
"'%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
if (errors & (CE_FRAME | CE_RXPARITY | CE_OVERRUN | CE_RXOVER)) {
// These are non-fatal per the spec (FramingError, ParityError,
// BufferOverrunError). ClearCommError already cleared them; log and
// continue reading.
MOZ_LOG(gWebSerialLog, LogLevel::Warning,
("Win32SerialPlatformService[%p]::Read non-fatal comm errors on "
"port '%s': 0x%08lx (cleared, continuing)",
this, NS_ConvertUTF16toUTF8(aPortId).get(), errors));
}
if (comStat.cbInQue == 0) {
return NS_OK;
}
DWORD bytesToRead =
std::min<DWORD>({comStat.cbInQue, static_cast<DWORD>(aBuf.Length())});
DWORD bytesRead = 0;
if (!ReadFile(handle, aBuf.Elements(), bytesToRead, &bytesRead, nullptr)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Read ReadFile failed for port '%s': "
"0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
aBytesRead = bytesRead;
if (bytesRead > 0) {
MOZ_LOG(
gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Read read %lu bytes from port '%s'",
this, bytesRead, NS_ConvertUTF16toUTF8(aPortId).get()));
}
return NS_OK;
}
nsresult Win32SerialPlatformService::WriteImpl(const nsString& aPortId,
Span<const uint8_t> aData) {
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Write port '%s' not found", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
if (aData.IsEmpty()) {
MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
("Win32SerialPlatformService[%p]::Write empty data for port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
MOZ_LOG(
gWebSerialLog, LogLevel::Verbose,
("Win32SerialPlatformService[%p]::Write writing %zu bytes to port '%s'",
this, aData.Length(), NS_ConvertUTF16toUTF8(aPortId).get()));
DWORD totalWritten = 0;
const uint8_t* buffer = aData.Elements();
DWORD remaining = static_cast<DWORD>(aData.Length());
while (remaining > 0) {
DWORD bytesWritten = 0;
if (!WriteFile(handle, buffer + totalWritten, remaining, &bytesWritten,
nullptr)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Write WriteFile failed for port "
"'%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
if (bytesWritten == 0) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Write WriteFile returned 0 for "
"port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_FAILURE;
}
totalWritten += bytesWritten;
remaining -= bytesWritten;
if (remaining > 0) {
MOZ_LOG(gWebSerialLog, LogLevel::Verbose,
("Win32SerialPlatformService[%p]::Write partial write for port "
"'%s': %lu bytes, %lu remaining",
this, NS_ConvertUTF16toUTF8(aPortId).get(), bytesWritten,
remaining));
}
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Write wrote %lu bytes to port '%s'",
this, totalWritten, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::DrainImpl(const nsString& aPortId) {
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Drain port not found: %s", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
MOZ_LOG(
gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Drain draining transmit buffers for "
"port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
if (!FlushFileBuffers(handle)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Drain FlushFileBuffers failed for "
"port '%s': error=%lu",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Drain successfully drained buffers "
"for port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::FlushImpl(const nsString& aPortId,
bool aReceive) {
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Flush port not found: %s", this,
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
DWORD flags = aReceive ? PURGE_RXCLEAR : PURGE_TXCLEAR;
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Flush discarding %s buffers "
"for port '%s'",
this, aReceive ? "receive" : "transmit",
NS_ConvertUTF16toUTF8(aPortId).get()));
if (!PurgeComm(handle, flags)) {
DWORD error = GetLastError();
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::Flush PurgeComm failed for port "
"'%s': error=%lu",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::Flush successfully flushed %s "
"buffers for port '%s'",
this, aReceive ? "receive" : "transmit",
NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::SetSignalsImpl(
const nsString& aPortId, const IPCSerialOutputSignals& aSignals) {
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::SetSignals for port '%s' (DTR=%s, "
"RTS=%s, Break=%s)",
this, NS_ConvertUTF16toUTF8(aPortId).get(),
aSignals.dataTerminalReady().isSome()
? (aSignals.dataTerminalReady().value() ? "true" : "false")
: "unset",
aSignals.requestToSend().isSome()
? (aSignals.requestToSend().value() ? "true" : "false")
: "unset",
aSignals.breakSignal().isSome()
? (aSignals.breakSignal().value() ? "true" : "false")
: "unset"));
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::SetSignals port '%s' not found",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
if (aSignals.dataTerminalReady().isSome()) {
if (!EscapeCommFunction(
handle, aSignals.dataTerminalReady().value() ? SETDTR : CLRDTR)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction DTR "
"failed for port '%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
}
if (aSignals.requestToSend().isSome()) {
if (!EscapeCommFunction(
handle, aSignals.requestToSend().value() ? SETRTS : CLRRTS)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction RTS "
"failed for port '%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
}
if (aSignals.breakSignal().isSome()) {
if (!EscapeCommFunction(
handle, aSignals.breakSignal().value() ? SETBREAK : CLRBREAK)) {
DWORD error = GetLastError();
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::SetSignals EscapeCommFunction "
"Break failed for port '%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::SetSignals succeeded for port '%s'",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_OK;
}
nsresult Win32SerialPlatformService::GetSignalsImpl(
const nsString& aPortId, IPCSerialInputSignals& aSignals) {
mIOCapability.AssertOnCurrentThread();
HANDLE handle = FindPortHandle(aPortId);
if (handle == INVALID_HANDLE_VALUE) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::GetSignals port '%s' not found",
this, NS_ConvertUTF16toUTF8(aPortId).get()));
return NS_ERROR_NOT_AVAILABLE;
}
DWORD status = 0;
if (!GetCommModemStatus(handle, &status)) {
DWORD error = GetLastError();
MOZ_LOG(
gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::GetSignals GetCommModemStatus failed "
"for port '%s': 0x%08lx",
this, NS_ConvertUTF16toUTF8(aPortId).get(), error));
return NS_ERROR_FAILURE;
}
aSignals = IPCSerialInputSignals{
(status & MS_RLSD_ON) != 0, // dataCarrierDetect (DCD)
(status & MS_CTS_ON) != 0, // clearToSend (CTS)
(status & MS_RING_ON) != 0, // ringIndicator (RI)
(status & MS_DSR_ON) != 0 // dataSetReady (DSR)
};
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::GetSignals for port '%s': DCD=%s, "
"CTS=%s, RI=%s, DSR=%s",
this, NS_ConvertUTF16toUTF8(aPortId).get(),
aSignals.dataCarrierDetect() ? "true" : "false",
aSignals.clearToSend() ? "true" : "false",
aSignals.ringIndicator() ? "true" : "false",
aSignals.dataSetReady() ? "true" : "false"));
return NS_OK;
}
nsresult Win32SerialPlatformService::StartMonitoringDeviceChanges() {
if (mMonitoring) {
return NS_OK;
}
MOZ_LOG(
gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges", this));
CM_NOTIFY_FILTER filter = {};
filter.cbSize = sizeof(CM_NOTIFY_FILTER);
filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
filter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_COMPORT;
CONFIGRET cr = CM_Register_Notification(
&filter, this, DeviceNotificationCallback, &mDeviceNotification);
if (cr != CR_SUCCESS) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
"CM_Register_Notification failed: 0x%08lx",
this, cr));
return NS_ERROR_FAILURE;
}
mMonitoring = true;
auto cachedPortList = mCachedPortList.Lock();
nsresult rv = EnumeratePortsWin32(*cachedPortList);
if (NS_FAILED(rv)) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
"EnumeratePorts failed: 0x%08x",
this, static_cast<unsigned>(rv)));
return rv;
}
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::StartMonitoringDeviceChanges "
"succeeded, "
"monitoring %zu ports",
this, cachedPortList->Length()));
return NS_OK;
}
void Win32SerialPlatformService::StopMonitoringDeviceChanges() {
AssertIsOnMainThread();
if (!mMonitoring) {
return;
}
MOZ_LOG(
gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::StopMonitoringDeviceChanges", this));
if (mDeviceNotification) {
CM_Unregister_Notification(mDeviceNotification);
mDeviceNotification = nullptr;
}
mMonitoring = false;
}
DWORD CALLBACK Win32SerialPlatformService::DeviceNotificationCallback(
HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action,
PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize) {
RefPtr<SerialPlatformService> service = SerialPlatformService::GetInstance();
if (!service) {
return ERROR_SUCCESS;
}
auto* winService = static_cast<Win32SerialPlatformService*>(service.get());
if (!(Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)) {
return ERROR_SUCCESS;
}
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::DeviceNotificationCallback "
"action=%d",
winService, Action));
// Only schedule a check if one isn't already pending
bool expected = false;
if (winService->mCheckPending.compareExchange(expected, true)) {
// Dispatch to the monitor thread (separate from the I/O queue so device
// change detection isn't blocked by hanging serial I/O calls).
nsresult rv = winService->mMonitorThread->DelayedDispatch(
NS_NewRunnableFunction(
"Win32SerialPlatformService::CheckForDeviceChanges",
[self = RefPtr{winService}]() {
self->mCheckPending = false;
self->CheckForDeviceChanges();
}),
kDeviceChangeDelayMs);
if (NS_FAILED(rv)) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::"
"DeviceNotificationCallback DelayedDispatch failed: 0x%08x",
winService, static_cast<uint32_t>(rv)));
winService->mCheckPending = false;
} else {
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::"
"DeviceNotificationCallback scheduled CheckForDeviceChanges",
winService));
}
} else {
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::"
"DeviceNotificationCallback check already pending, skipping",
winService));
}
return ERROR_SUCCESS;
}
void Win32SerialPlatformService::CheckForDeviceChanges() {
if (IsShutdown()) {
return;
}
MOZ_ASSERT(mMonitorThread->IsOnCurrentThread());
MOZ_LOG(gWebSerialLog, LogLevel::Debug,
("Win32SerialPlatformService[%p]::CheckForDeviceChanges", this));
SerialPortList newPortList;
nsresult rv = EnumeratePortsWin32(newPortList);
if (NS_FAILED(rv)) {
MOZ_LOG(gWebSerialLog, LogLevel::Error,
("Win32SerialPlatformService[%p]::CheckForDeviceChanges "
"EnumeratePorts failed: 0x%08x",
this, static_cast<unsigned>(rv)));
return;
}
auto cachedPortList = mCachedPortList.Lock();
// Find newly connected ports
for (const auto& newPort : newPortList) {
bool found = false;
for (const auto& oldPort : *cachedPortList) {
if (oldPort.id() == newPort.id()) {
found = true;
break;
}
}
if (!found) {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::CheckForDeviceChanges port "
"connected: '%s'",
this, NS_ConvertUTF16toUTF8(newPort.id()).get()));
NotifyPortConnected(newPort);
}
}
// Find disconnected ports
for (const auto& oldPort : *cachedPortList) {
bool found = false;
for (const auto& newPort : newPortList) {
if (oldPort.id() == newPort.id()) {
found = true;
break;
}
}
if (!found) {
MOZ_LOG(gWebSerialLog, LogLevel::Info,
("Win32SerialPlatformService[%p]::CheckForDeviceChanges port "
"disconnected: '%s'",
this, NS_ConvertUTF16toUTF8(oldPort.id()).get()));
// Since we're just using synchonous I/O for now, if the OS detects
// a device gets disconnected any pending writes will immediately
// fail, so we don't need to do any sort of cancelling here.
NotifyPortDisconnected(oldPort.id());
}
}
*cachedPortList = std::move(newPortList);
}
} // namespace mozilla::dom