Skip to content

Commit

Permalink
Merge pull request #114 from tock/windows-sockets
Browse files Browse the repository at this point in the history
avoid using AF_UNIX on Windows platforms
  • Loading branch information
ppannuto authored Jun 28, 2024
2 parents f0358de + 2cdc87a commit e9aba35
Showing 1 changed file with 55 additions and 21 deletions.
76 changes: 55 additions & 21 deletions tockloader/bootloader_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
import time
import threading

# Although Windows is not supported actively, this allow features that "just
# work" to work on Windows.
# Windows support in tockloader is currently experimental. Please report bugs,
# and ideally rough paths to fixing them. The core maintainers have limited
# access to Windows machines, and support in testing fixes/updates is helpful.
if platform.system() != "Windows":
import fcntl
_IS_WINDOWS = False
else:
_IS_WINDOWS = True

import serial
import serial.tools.list_ports
Expand Down Expand Up @@ -356,21 +360,36 @@ def open_link_to_board(self, listen=False):
# Before connecting, check whether there is another tockloader process
# running, and if it's a listen, pause that listen (unless we are also
# doing a listen), otherwise bail out.
self.comm_path = "/tmp/tockloader." + self._get_serial_port_hash()
if os.path.exists(self.comm_path):
# Open a socket to the other tockloader instance if one exists.
self.client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)


# Windows has only partial unix socket support, so Python rejects them.
# Work around this by listening on a reasonably-unlikely-to-collide
# localhost port derived from the serial port name.
if _IS_WINDOWS:
self.comm_port = self._get_serial_port_hashed_to_ip_port()
self.client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.client_sock.connect(self.comm_path)
self.client_sock.connect(('localhost', self.comm_port))
logging.debug("Connected to existing `tockloader listen`")
except ConnectionRefusedError:
logging.warning("Found stale tockloader server, removing.")
logging.warning(
"This may occur if a previous tockloader instance crashed."
)
os.unlink(self.comm_path)
logging.debug(f"No other listen instances running (tried localhost::{self.comm_port})")
self.client_sock = None
else:
self.client_sock = None
self.comm_path = "/tmp/tockloader." + self._get_serial_port_hash()
if os.path.exists(self.comm_path):
self.client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
self.client_sock.connect(self.comm_path)
except ConnectionRefusedError:
logging.warning("Found stale tockloader server, removing.")
logging.warning(
"This may occur if a previous tockloader instance crashed."
)
os.unlink(self.comm_path)
self.client_sock = None
else:
self.client_sock = None


# Check if another tockloader instance exists based on whether we were
# able to create a socket to it.
Expand Down Expand Up @@ -445,17 +464,23 @@ def restart_listener(path):
# listen, allow the other tockloader instance to complete, and then
# resume listening.

# Create the socket we will listen on.
self.server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if _IS_WINDOWS:
self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_sock.bind(('localhost', self.comm_port))
logging.debug(f"listening on localhost::{self.comm_port}")
else:
# Create the socket we will listen on.
self.server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# Close the file descriptor if exec() is called (apparently). I'm
# not sure why we need this (or if we do).
flags = fcntl.fcntl(self.server_sock, fcntl.F_GETFD)
fcntl.fcntl(self.server_sock, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

# Close the file descriptor if exec() is called (apparently). I'm
# not sure why we need this (or if we do).
flags = fcntl.fcntl(self.server_sock, fcntl.F_GETFD)
fcntl.fcntl(self.server_sock, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
self.server_sock.bind(self.comm_path)

# Finish setting up the socket, and spawn a thread to listen on that
# socket.
self.server_sock.bind(self.comm_path)
self.server_sock.listen(1)
self.server_event = threading.Event()
self.server_thread = threading.Thread(
Expand All @@ -470,7 +495,8 @@ def restart_listener(path):
def server_cleanup():
if self.server_sock is not None:
self.server_sock.close()
os.unlink(self.comm_path)
if not _IS_WINDOWS:
os.unlink(self.comm_path)

atexit.register(server_cleanup)

Expand Down Expand Up @@ -574,6 +600,14 @@ def _get_serial_port_hash(self):
"""
return hashlib.sha1(self.sp.port.encode("utf-8")).hexdigest()

def _get_serial_port_hashed_to_ip_port(self):
"""
This is a bit of a hack, but it's means to find a reasonably unlikely
to collide port number based on the serial port used to talk to the
board.
"""
return int(self._get_serial_port_hash(), 16) % 40000 + 10000

def _toggle_bootloader_entry_DTR_RTS(self):
"""
Use the DTR and RTS lines on UART to reset the chip and assert the
Expand Down

0 comments on commit e9aba35

Please sign in to comment.