1
0
Fork 0

refactor: refactor UMS mediums and links, add internals docs

This commit is contained in:
Thomas Touhey 2024-04-06 13:20:02 +02:00
parent 9a49056522
commit 6b211a8f40
10 changed files with 1097 additions and 396 deletions

View File

@ -184,10 +184,9 @@ Link management related function declarations
.. warning::
This cannot be used if :c:macro:`CAHUTE_SERIAL_RECEIVER` is not
set and :c:macro:`CAHUTE_SERIAL_NOCHECK` is set, as the
sender / active side tweaks the checking flow to determine the
protocol of the other side.
This cannot be used if :c:macro:`CAHUTE_SERIAL_NOCHECK` is set,
as we tweak the checking flow to determine the protocol of the
other side.
.. c:macro:: CAHUTE_SERIAL_PROTOCOL_CASIOLINK
@ -339,9 +338,8 @@ Link management related function declarations
.. warning::
This cannot be used if :c:macro:`CAHUTE_SERIAL_RECEIVER` is not
set and :c:macro:`CAHUTE_SERIAL_PROTOCOL_AUTO` is used, as the
sender / active side tweaks the checking flow to determine the
This cannot be used if :c:macro:`CAHUTE_SERIAL_PROTOCOL_AUTO`
is used, as we tweak the checking flow to determine the
protocol of the other side.
.. c:macro:: CAHUTE_SERIAL_NODISC

View File

@ -28,6 +28,7 @@ This documentation is organized using `Diátaxis`_' structure.
cli-guides
developer-guides
topics
internals
project
cli
headers

16
docs/internals.rst Normal file
View File

@ -0,0 +1,16 @@
Cahute internals
================
In this section, we will explore discussion topics regarding the project's
design and inner workings. These topics provide keys to understanding what is
happening in this project, and why it is happening.
.. note::
The following sections are hands-on, it is recommended to open the code
at the same time.
.. toctree::
:maxdepth: 2
internals/links

681
docs/internals/links.rst Normal file
View File

@ -0,0 +1,681 @@
Links and mediums
=================
All communication implementations are centered around resources called links.
Internally, links are mostly constituted of:
* A **medium**, which is an interface with a set of resources to communicate
with the underlying medium opened with the system or hardware.
* A **protocol**, which is a set of resources and functions that do **not**
constitute an interface, since protocols may obey different logics.
A link only requires one memory allocation (except for system resources that
are allocated / opened using different functions), and the medium
and the protocol are initialized together using link opening functions.
Mediums
-------
Mediums define a common set of interfaces that can be used by protocols to
communicate with the device or host.
Medium interface
~~~~~~~~~~~~~~~~
Most mediums support a stream-like interface with the following functions:
.. c:function:: int cahute_read_from_link(cahute_link *link, cahute_u8 *buf, \
size_t size, unsigned long first_timeout, unsigned long next_timeout)
Read exactly ``size`` bytes of data into the buffer.
This function uses the medium read buffer to store any incoming excess
data, for it to be processed first next time before using the underlying
buffer to read more data.
.. warning::
This function does not provide the number of bytes that have been
read in case of error (with the exception of
:c:macro:`CAHUTE_ERROR_TIMEOUT_START`, which implies that no bytes
have been read).
This is to simplify as much as possible protocol
implementations, but it also means that the medium should be
considered irrecoverable in such cases.
Errors to be expected from this function are the following:
:c:macro:`CAHUTE_ERROR_TIMEOUT_START`
The first byte was not received in a timely matter.
This can only occur if ``first_timeout`` was not set to 0.
:c:macro:`CAHUTE_ERROR_TIMEOUT`
A byte past the first one was not received in a timely matter.
This can only occur if ``next_timeout`` was not set to 0.
:c:macro:`CAHUTE_ERROR_GONE`
The device is no longer present, usually either because the USB
cable has been unplugged on one end or the other, or the serial
adapter has been unplugged from the host.
:c:macro:`CAHUTE_ERROR_UNKNOWN`
The medium-specific operations have yielded an error code that
Cahute did not interpret. Some details can usually be found in
the logs.
:param link: Link to the device.
:param buf: Buffer in which to write the received data.
:param size: Size of the data to write to the buffer.
:param first_timeout: Maximum delay to wait before the first byte of the
data, in milliseconds. If this is set to 0, the first byte will be
awaited indefinitely.
:param next_timeout: Maximum delay to wait between two bytes of the data,
or before the last byte, in milliseconds. If this is set to 0, next
bytes will be awaited indefinitely.
:return: Error, or :c:macro:`CAHUTE_OK`.
.. c:function:: int cahute_skip_from_link(cahute_link *link, size_t size, \
unsigned long first_timeout, unsigned long next_timeout)
Skip exactly ``size`` bytes of data.
This function is a convenience function for protocol implementations.
It uses :c:func:`cahute_read_from_link` to read into a trashable
buffer, and thus, comes with the same risks and errors.
:param link: Link to the device.
:param size: Size of the data to receive and skip.
:param first_timeout: Maximum delay to wait before the first byte of the
data, in milliseconds. If this is set to 0, the first byte will be
awaited indefinitely.
:param next_timeout: Maximum delay to wait between two bytes of the data,
or before the last byte, in milliseconds. If this is set to 0, next
bytes will be awaited indefinitely.
:return: Error, or :c:macro:`CAHUTE_OK`.
.. c:function:: int cahute_write_to_link(cahute_link *link, \
cahute_u8 const *buf, size_t size)
Write exactly ``size`` bytes of data to the link.
Errors to be expected from this function are the following:
:c:macro:`CAHUTE_ERROR_GONE`
The device is no longer present, usually either because the USB
cable has been unplugged on one end or the other, or the serial
adapter has been unplugged from the host.
:c:macro:`CAHUTE_ERROR_UNKNOWN`
The medium-specific operations have yielded an error code that
Cahute did not interpret. Some details can usually be found in
the logs.
:param link: Link to the device.
:param buf: Buffer from which to read the data to send.
:param size: Size of the data to read and send.
:return: Error, or :c:macro:`CAHUTE_OK`.
Serial mediums such as :c:macro:`CAHUTE_LINK_MEDIUM_POSIX_SERIAL` or
:c:macro:`CAHUTE_LINK_MEDIUM_WIN32_SERIAL` support changing the parameters
of the serial link using the following function:
.. c:function:: int cahute_set_serial_params_to_link(cahute_link *link, \
unsigned long flags, unsigned long speed)
Set the serial parameters to the medium.
Accepted flags are a subset of the flags for :c:func:`cahute_open_serial`:
* ``CAHUTE_SERIAL_STOP_*`` (stop bits);
* ``CAHUTE_SERIAL_PARITY_*`` (parity);
* ``CAHUTE_SERIAL_XONXOFF_*`` (XON/XOFF software control);
* ``CAHUTE_SERIAL_DTR_*`` (DTR hardware control);
* ``CAHUTE_SERIAL_RTS_*`` (RTS hardware control).
:param link: Link to the device.
:param flags: Flags to set to the medium.
:param speed: Speed to set to the medium.
:return: Error, or :c:macro:`CAHUTE_OK`.
USB Mass Storage mediums support an interface capable of making SCSI requests,
with the following functions:
.. c:function:: int cahute_scsi_request_to_link(cahute_link *link, \
cahute_u8 const *command, size_t command_size, cahute_u8 const *data, \
size_t data_size, int *statusp)
Emit an SCSI request to the medium, with or without data.
:param link: Link to the device.
:param command: Command to send.
:param command_size: Size of the command to send.
:param data: Optional data to send along with the command.
This can be set to ``NULL`` if ``data_size`` is set to 0.
:param data_size: Size of the data to send along with the command.
:param statusp: Pointer to the status code to set to the one returned by
the device.
:return: Error, or :c:macro:`CAHUTE_OK`.
.. c:function:: int cahute_scsi_request_from_link(cahute_link *link, \
cahute_u8 const *command, size_t command_size, cahute_u8 *buf, \
size_t buf_size, int *statusp)
Emit an SCSI request to the medium, while requesting data.
:param link: Link to the device.
:param command: Command to send.
:param command_size: Size of the command to send.
:param buf: Buffer to fill with the requested data.
:param buf_size: Size of the data to request.
:param statusp: Pointer to the status code to set to the one returned by
the device.
:return: Error, or :c:macro:`CAHUTE_OK`.
Available mediums
~~~~~~~~~~~~~~~~~
Mediums are represented as ``CAHUTE_LINK_MEDIUM_*`` constants internally.
.. warning::
The medium constants are only represented **if they are available on the
current configuration**. This is a simple way for medium-specific
implementations to be defined or not, with ``#ifdef``.
Available mediums are the following:
.. c:macro:: CAHUTE_LINK_MEDIUM_POSIX_SERIAL
Serial medium using the POSIX STREAMS API, with a file descriptor (*fd*):
* Closing using `close(2) <https://linux.die.net/man/2/close>`_;
* Receiving uses `select(2) <https://linux.die.net/man/2/select>`_ and
`read(2) <https://linux.die.net/man/2/read>`_;
* Sending uses `write(2) <https://linux.die.net/man/2/write>`_;
* Serial params setting uses
`termios(3) <https://linux.die.net/man/3/termios>`_, including
``tcdrain()``, and
`tty_ioctl(4) <https://linux.die.net/man/4/tty_ioctl>`_, especially
``TIOCMGET`` and ``TIOCMSET``.
Only available on platforms considered POSIX, including Apple's OS X
explicitely (since they do not define the ``__unix__`` constant like
Linux does).
Available protocols on this medium are the following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_AUTO`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP`.
.. c:macro:: CAHUTE_LINK_MEDIUM_WIN32_SERIAL
Serial medium using the Windows API, with a |HANDLE|_ and
`Overlapped I/O`_:
* Closing uses |CloseHandle|_;
* Receiving uses |ReadFile|_ and |WaitForSingleObject|_, and depending
on whether the second function succeeded or not, either
|GetOverlappedResult|_ or |CancelIo|_, to ensure we don't have any
buffer writes post-freeing the link;
* Sending uses |WriteFile|_ and |WaitForSingleObject|_, and depending
on whether the second function succeeded or not, either
|GetOverlappedResult|_ or |CancelIo|_, to ensure we don't have any
buffer reads post-freeing the link;
* Serial params setting uses |SetCommState|_.
For more information, see `Serial Communications in Win32`_.
Available protocols on this medium are the following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_AUTO`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP`.
.. c:macro:: CAHUTE_LINK_MEDIUM_WIN32_CESG
USB device used as a host through CASIO's CESG502 driver using the
Windows API.
As described in :ref:`usb-detection-windows`, we must detect if the
device driver is CESG502 or a libusb-compatible driver by using
SetupAPI_ or CfgMgr32_, and use this medium in the first case.
It is used with a |HANDLE|_ and `Overlapped I/O`_:
* Closing uses |CloseHandle|_;
* Receiving uses |ReadFile|_ and |WaitForSingleObject|_, and depending
on whether the second function succeeded or not, either
|GetOverlappedResult|_ or |CancelIo|_, to ensure we don't have any
buffer writes post-freeing the link;
* Sending uses |WriteFile|_ and |WaitForSingleObject|_, and depending
on whether the second function succeeded or not, either
|GetOverlappedResult|_ or |CancelIo|_, to ensure we don't have any
buffer reads post-freeing the link.
Note that CESG502 waits for calculator input by default, and always
requires a buffer bigger than the actual input it receives (4 KiB is
usually enough). It also abstracts away whether it using bulk transfers
directly, or USB Mass Storage, into a stream interface; this however
does not allow you to make SCSI requests directly.
Available protocols on this medium are the following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP`.
.. c:macro:: CAHUTE_LINK_MEDIUM_LIBUSB
USB device used as a host through libusb, with bulk transport.
It is used with a |libusb_device_handle|_, opened using a
|libusb_context|_:
* Closing uses |libusb_close|_ on the device handle, and |libusb_exit|_
on the libusb context;
* Receiving and sending uses |libusb_bulk_transfer|_.
Available protocols on this medium are the following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP`.
.. c:macro:: CAHUTE_LINK_MEDIUM_LIBUSB_UMS
USB device used as a host through libusb, implementing USB Mass Storage
(UMS) with Bulk-only transport.
As for :c:macro:`CAHUTE_LINK_MEDIUM_LIBUSB`, it is used with a
|libusb_device_handle|_, opened using a |libusb_context|_:
* Closing uses |libusb_close|_ on the device handle, and |libusb_exit|_
on the libusb context;
* Requesting using SCSI uses |libusb_bulk_transfer|_ with manual reading
and writing of the Command Block Wrapper (CBW) and
Command Status Wrapper (CSW).
See `USB Mass Storage Class, Bulk-Only Transport`_ for more information
on CBW and CSW format and protocol in general.
Available protocols on this medium are the following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP`.
Protocols
---------
Protocols define what operations and logics are available, and how to
implement these operations and logics.
All protocols may use the **protocol buffer**, which is in the link directly,
and for which the use varies depending on the selected protocol:
* CASIOLINK uses the protocol buffer when receiving data, for storing both
the headers and raw data.
* Protocol 7.00 uses the protocol buffer to store unpadded data from
data packets.
* Protocol 7.00 Screenstreaming uses the protocol buffer to store raw
picture data.
.. todo::
The role of the protocol buffer for Protocol 7.00 should actually
change to store the received data.
Available protocols are:
.. c:macro:: CAHUTE_LINK_PROTOCOL_SERIAL_AUTO
Automatic protocol detection on a serial medium.
Note that this doesn't outlive link protocol initialization, and gets
replaced by the actual protocol afterwards; see
:ref:`internals-link-protocol-initialization` for more details.
.. c:macro:: CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK
CASIOLINK protocol over a serial medium.
See :ref:`protocol-casiolink` for more information.
Note that in this case, the CASIOLINK variant is set in the
``protocol_state.casiolink.variant`` property of the link.
.. c:macro:: CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN
Protocol 7.00 over a serial medium.
See :ref:`protocol-seven` for more information.
This differs from :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN` by the
availability of command :ref:`seven-command-02`.
.. c:macro:: CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP
Protocol 7.00 Screenstreaming over a serial medium.
See :ref:`protocol-seven-ohp` for more information.
.. c:macro:: CAHUTE_LINK_PROTOCOL_USB_SEVEN
Protocol 7.00 over USB bulk transport or USB Mass Storage or
USB Mass Storage commands.
See :ref:`protocol-seven` and :ref:`protocol-ums` for more information.
.. c:macro:: CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP
Protocol 7.00 Screenstreaming over USB bulk transport or USB Mass Storage
extended commands.
See :ref:`protocol-seven-ohp` and :ref:`protocol-ums` for more information.
.. c:macro:: CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE
USB Mass Storage without extensions.
Opening behaviours
------------------
In this section, we will describe the behaviour of link opening functions.
:c:func:`cahute_open_serial_link`
This function first validates all params to ensure compatibility, e.g.
throws an error in case of unsupported flag, speed, or combination.
.. note::
The protocol is selected, depending on the flags, to one of the
following:
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_AUTO`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN`;
* :c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP`.
Then, depending on the platform:
* On POSIX and compatible, it will attempt to open the serial device
using `open(2) <https://linux.die.net/man/2/open>`_.
If this succeeds, the medium of the created link will be set to
:c:macro:`CAHUTE_LINK_MEDIUM_POSIX_SERIAL`;
* On Windows, it will attempt to open the serial device using
|CreateFile|_, then, if it succeeds, call |SetCommTimeouts|_
with ``ReadTimeoutInterval`` set to ``MAXDWORD`` in order to only read
what is directly available, and create the event for the overlapped
object using |CreateEvent|_. If this succeeds, the medium of the
created link will be set to :c:macro:`CAHUTE_LINK_MEDIUM_WIN32_SERIAL`;
* Otherwise, it will return :c:macro:`CAHUTE_ERROR_IMPL`.
If the underlying medium has successfully been opened, it will allocate
the link and call :c:func:`cahute_set_serial_params_to_link` to set
the initial serial parameters to it.
It will then initialize the protocol using the common protocol
initialization procedure; see
:ref:`internals-link-protocol-initialization`.
:c:func:`cahute_open_usb_link`
This function first validates all params to ensure compatibility, e.g.
throws an error in case of unsupported flag or combination.
If Cahute runs on Windows, this function makes use of cfgmgr32_ to
look for USB device interfaces, and get the associated USB devices.
If it finds one matching the provided bus and address numbers, it
checks if the device driver is CESG502, and if this is the case, it
opens the device interface using |CreateFile|_, and creates the link
with the :c:macro:`CAHUTE_LINK_MEDIUM_WIN32_CESG` medium, and:
* Either the :c:macro:`CAHUTE_LINK_PROTOCOL_SEVEN_OHP` protocol if the
:c:macro:`CAHUTE_USB_OHP` flag has been passed;
* Or the :c:macro:`CAHUTE_LINK_PROTOCOL_SEVEN` protocol otherwise.
.. note::
In order to get bus and address numbers for USB devices that are
equivalent to what is obtained through libusb,
|DEVPKEY_Device_LocationInfo|_ is used.
This property returns a string of the form ``Port_#0002.Hub_#000D``,
which must be parsed to obtain the address number (here, 2) and
the bus number (here, 13).
Otherwise, and on other platforms, if libusb support is enabled, this
function creates a context using |libusb_init|_, gets the device list
using |libusb_get_device_list|_, and finds one matching the provided bus
and address numbers using |libusb_get_bus_number|_ and
|libusb_get_device_address|_ on every entry.
If a matching device is found, the configuration is obtained using
|libusb_get_device_descriptor|_ and |libusb_get_active_config_descriptor|_,
in order to:
* Get the vendor (VID) and product (PID) identifiers, to ensure they match
one of the known combinations for CASIO calculators.
* Get the interface class (``bInterfaceClass``) to determine the protocol
and medium type.
* In both cases, ensure that the bulk IN and OUT endpoints exist, and
get their endpoint identifiers.
.. note::
While historical implementations of CASIO's protocols using libusb
hardcode 0x82 as Bulk IN and 0x01 as Bulk OUT, this has proven to
change on other platforms such as OS X; see `#3 (comment 1823215641)
<https://gitlab.com/cahuteproject/cahute/-/issues/3#note_1823215641>`_
for more context.
The interface class and :c:macro:`CAHUTE_USB_OHP` flag presence to
protocol and medium type mapping is the following:
.. list-table::
:header-rows: 1
:width: 100%
* - (in) Intf. class
- (in) ``OHP`` flag
- (out) Medium
- (out) Protocol
* - 8
- absent
- :c:macro:`CAHUTE_LINK_MEDIUM_LIBUSB_UMS`
- :c:macro:`CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE`
* - 8
- present
- :c:macro:`CAHUTE_LINK_MEDIUM_LIBUSB_UMS`
- :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP`
* - 255
- absent
- :c:macro:`CAHUTE_LINK_MEDIUM_LIBUSB`
- :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN`
* - 255
- present
- :c:macro:`CAHUTE_LINK_MEDIUM_LIBUSB`
- :c:macro:`CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP`
See :ref:`usb-detection` for more information.
Once all metadata has been gathered, the function opens the device using
|libusb_open|_, and attempt to claim its interface using
|libusb_claim_interface|_ and |libusb_detach_kernel_driver|_.
.. note::
Access errors, i.e. any of these two functions returning
``LIBUSB_ERROR_ACCESS``, are ignored, since libusb is still
able to communicate with the device on some platforms afterwards.
See `#3 <https://gitlab.com/cahuteproject/cahute/-/issues/3>`_
for more context.
Once this is done, the link is created with the previously selected
medium and protocol.
Otherwise, if libusb support has been disabled, the function returns
:c:macro:`CAHUTE_ERROR_IMPL`.
If medium initialization has been successful, the function will then
initialize the protocol using the common protocol initialization
procedure; see :ref:`internals-link-protocol-initialization`.
:c:func:`cahute_open_simple_usb_link`
This function is a convenience function, using mostly public functions
to work:
* It detects available USB devices using :c:func:`cahute_detect_usb`.
If it finds none, it sleeps and retries until it has no attempts left.
If it finds multiple, it fails with error
:c:macro:`CAHUTE_ERROR_TOO_MANY`.
* It opens the found USB device using :c:func:`cahute_open_usb_link`.
It used to be to the program or library to define by itself, and was in
the guides, but this behaviour is found in most simple scripts that
use the Cahute library, so it was decided to include it within the library.
.. _internals-link-protocol-initialization:
Protocol initialization
~~~~~~~~~~~~~~~~~~~~~~~
The common protocol initialization procedure is defined by a function named
``init_link`` in ``link/open.c``.
First of all, if the selected protocol is
:c:macro:`CAHUTE_LINK_PROTOCOL_SERIAL_AUTO`, the communication initialization
is used to determine the protocol in which both devices should communicate.
.. note::
Since the initialization step is necessary for automatic protocol
discovery to take place, the :c:macro:`CAHUTE_SERIAL_NOCHECK` flag
is forbidden with :c:macro:`CAHUTE_SERIAL_PROTOCOL_AUTO`.
This is described in :c:func:`cahute_open_serial_link`'s flags description.
Then, the initialization sequence is run depending on the protocol and role
(sender or receiver, depending on the presence of the
:c:macro:`CAHUTE_SERIAL_RECEIVER` :c:macro:`CAHUTE_USB_RECEIVER` in the flags
of the original function).
.. |HANDLE| replace:: ``HANDLE``
.. |CreateFile| replace:: ``CreateFile``
.. |SetCommTimeouts| replace:: ``SetCommTimeouts``
.. |CreateEvent| replace:: ``CreateEvent``
.. |ReadFile| replace:: ``ReadFile``
.. |WriteFile| replace:: ``WriteFile``
.. |WaitForSingleObject| replace:: ``WaitForSingleObject``
.. |GetOverlappedResult| replace:: ``GetOverlappedResult``
.. |CancelIo| replace:: ``CancelIo``
.. |CloseHandle| replace:: ``CloseHandle``
.. |SetCommState| replace:: ``SetCommState``
.. |DEVPKEY_Device_LocationInfo| replace:: ``DEVPKEY_Device_LocationInfo``
.. |libusb_context| replace:: ``libusb_context``
.. |libusb_init| replace:: ``libusb_init``
.. |libusb_exit| replace:: ``libusb_exit``
.. |libusb_device_handle| replace:: ``libusb_device_handle``
.. |libusb_get_device_list| replace:: ``libusb_get_device_list``
.. |libusb_get_bus_number| replace:: ``libusb_get_bus_number``
.. |libusb_get_device_address| replace:: ``libusb_get_device_address``
.. |libusb_get_device_descriptor| replace:: ``libusb_get_device_descriptor``
.. |libusb_get_active_config_descriptor|
replace:: ``libusb_get_active_config_descriptor``
.. |libusb_detach_kernel_driver| replace:: ``_libusb_detach_kernel_driver``
.. |libusb_claim_interface| replace:: ``libusb_claim_interface``
.. |libusb_open| replace:: ``libusb_open``
.. |libusb_close| replace:: ``libusb_close``
.. |libusb_bulk_transfer| replace:: ``libusb_bulk_transfer``
.. _HANDLE:
https://learn.microsoft.com/en-us/windows/win32/sysinfo/handles-and-objects
.. _Overlapped I/O:
https://learn.microsoft.com/en-us/windows/win32/sync/
synchronization-and-overlapped-input-and-output
.. _CreateFile:
https://learn.microsoft.com/en-us/windows/win32/api/
fileapi/nf-fileapi-createfilea
.. _SetCommTimeouts:
https://learn.microsoft.com/en-us/windows/win32/api/
winbase/nf-winbase-setcommtimeouts
.. _CreateEvent:
https://learn.microsoft.com/en-us/windows/win32/api/
synchapi/nf-synchapi-createeventa
.. _ReadFile:
https://learn.microsoft.com/en-us/windows/win32/api/
fileapi/nf-fileapi-readfile
.. _WriteFile:
https://learn.microsoft.com/en-us/windows/win32/api/
fileapi/nf-fileapi-writefile
.. _WaitForSingleObject:
https://learn.microsoft.com/en-us/windows/win32/api/
synchapi/nf-synchapi-waitforsingleobject
.. _GetOverlappedResult:
https://learn.microsoft.com/en-us/windows/win32/api/
ioapiset/nf-ioapiset-getoverlappedresult
.. _CancelIo:
https://learn.microsoft.com/en-us/windows/win32/fileio/cancelio
.. _CloseHandle:
https://learn.microsoft.com/en-us/windows/win32/api/
handleapi/nf-handleapi-closehandle
.. _SetCommState:
https://learn.microsoft.com/en-us/windows/win32/api/
winbase/nf-winbase-setcommstate
.. _DEVPKEY_Device_LocationInfo:
https://learn.microsoft.com/en-us/windows-hardware/
drivers/install/devpkey-device-locationinfo
.. _Serial Communications in Win32:
https://learn.microsoft.com/en-us/previous-versions/ms810467(v=msdn.10)
.. _SetupAPI:
https://learn.microsoft.com/en-us/windows-hardware/drivers/install/setupapi
.. _cfgmgr32:
https://learn.microsoft.com/en-us/windows/win32/api/cfgmgr32/
.. _libusb_context:
https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html
#ga4ec088aa7b79c4a9599e39bf36a72833
.. _libusb_init:
https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html
#ga7deaef521cfb1a5b3f8d6c01be11a795
.. _libusb_exit:
https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html
#gadc174de608932caeb2fc15d94fa0844d
.. _libusb_device_handle:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#ga7df95821d20d27b5597f1d783749d6a4
.. _libusb_get_device_list:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#gac0fe4b65914c5ed036e6cbec61cb0b97
.. _libusb_get_bus_number:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#gaf2718609d50c8ded2704e4051b3d2925
.. _libusb_get_device_address:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#gab6d4e39ac483ebaeb108f2954715305d
.. _libusb_get_device_descriptor:
https://libusb.sourceforge.io/api-1.0/group__libusb__desc.html
#ga5e9ab08d490a7704cf3a9b0439f16f00
.. _libusb_get_active_config_descriptor:
https://libusb.sourceforge.io/api-1.0/group__libusb__desc.html
#ga425885149172b53b3975a07629c8dab3
.. _libusb_detach_kernel_driver:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#ga5e0cc1d666097e915748593effdc634a
.. _libusb_claim_interface:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#gaee5076addf5de77c7962138397fd5b1a
.. _libusb_open:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#ga3f184a8be4488a767b2e0ae07e76d1b0
.. _libusb_close:
https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html
#ga779bc4f1316bdb0ac383bddbd538620e
.. _libusb_bulk_transfer:
https://libusb.sourceforge.io/api-1.0/group__libusb__syncio.html
#ga2f90957ccc1285475ae96ad2ceb1f58c
.. _USB Mass Storage Class, Bulk-Only Transport:
https://www.usb.org/sites/default/files/usbmassbulk_10.pdf

View File

@ -1,9 +1,9 @@
Discussion topics
=================
In this section, we will explore discussion topics regarding the project's
environment and design. These topics provide keys to understanding what is
happening in this project, and why it is happening.
In this section, we will explore topics regarding the project's environment,
i.e. the protocols and formats implemented or to be implemented,
rationales behind those, and some of Cahute's external-facing logics.
.. toctree::
:maxdepth: 2
@ -13,6 +13,5 @@ happening in this project, and why it is happening.
topics/text-encodings
topics/file-formats
topics/protocols
topics/data
topics/usb-detection
topics/logging

View File

@ -48,6 +48,8 @@ when the file system and main memory are presented using SCSI.
Note however that these should be used through the system's serial
bus interface rather than directly.
.. _usb-detection-windows:
Driver detection on Microsoft Windows
-------------------------------------

View File

@ -152,10 +152,9 @@ CAHUTE_INLINE(void) log_windows_error(char const *func_name, DWORD code) {
#define CAHUTE_LINK_MEDIUM_READ_BUFFER_SIZE 32768
/* Flags that can be present on a link at runtime. */
#define CAHUTE_LINK_FLAG_CLOSE_MEDIUM 0x00000001
#define CAHUTE_LINK_FLAG_CLOSE_PROTOCOL 0x00000002
#define CAHUTE_LINK_FLAG_TERMINATE 0x00000004 /* Should terminate. */
#define CAHUTE_LINK_FLAG_RECEIVER 0x00000008 /* Act as a receiver. */
#define CAHUTE_LINK_FLAG_CLOSE_MEDIUM 0x00000001
#define CAHUTE_LINK_FLAG_TERMINATE 0x00000002 /* Should terminate. */
#define CAHUTE_LINK_FLAG_RECEIVER 0x00000004 /* Act as a receiver. */
#define CAHUTE_LINK_FLAG_GONE 0x00000100 /* Underlying medium gone. */
#define CAHUTE_LINK_FLAG_TERMINATED 0x00000200 /* Was terminated! */
@ -170,7 +169,8 @@ CAHUTE_INLINE(void) log_windows_error(char const *func_name, DWORD code) {
# define CAHUTE_LINK_MEDIUM_WIN32_CESG 3
#endif
#if LIBUSB_ENABLED
# define CAHUTE_LINK_MEDIUM_LIBUSB 4
# define CAHUTE_LINK_MEDIUM_LIBUSB 4
# define CAHUTE_LINK_MEDIUM_LIBUSB_UMS 5
#endif
/* Protocol selection for 'initialize_link_protocol()'. */
@ -180,8 +180,7 @@ CAHUTE_INLINE(void) log_windows_error(char const *func_name, DWORD code) {
#define CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP 3
#define CAHUTE_LINK_PROTOCOL_USB_SEVEN 4
#define CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP 5
#define CAHUTE_LINK_PROTOCOL_UMS 6
#define CAHUTE_LINK_PROTOCOL_UMS_OHP 7
#define CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE 6
/* CASIOLINK variant selection for the same function. */
#define CAHUTE_CASIOLINK_VARIANT_AUTO 0

View File

@ -85,7 +85,6 @@ cahute_receive_screen(
case CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP:
case CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP:
case CAHUTE_LINK_PROTOCOL_UMS_OHP:
return cahute_seven_ohp_receive_screen(link, frame, timeout);
}

View File

@ -77,10 +77,41 @@ CAHUTE_INLINE(char const *) get_protocol_name(int protocol) {
return "Protocol 7.00 (USB)";
case CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP:
return "Protocol 7.00 Screenstreaming (USB)";
case CAHUTE_LINK_PROTOCOL_UMS:
case CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE:
return "USB Mass Storage";
case CAHUTE_LINK_PROTOCOL_UMS_OHP:
return "USB Mass Storage (Screenstreaming)";
default:
return "(unknown)";
}
}
/**
* Get the name of a link medium.
*
* @param medium Medium identifier, as a constant.
* @return Textual name of the medium.
*/
CAHUTE_LOCAL(char const *) get_medium_name(int medium) {
switch (medium) {
#ifdef CAHUTE_LINK_MEDIUM_POSIX_SERIAL
case CAHUTE_LINK_MEDIUM_POSIX_SERIAL:
return "Serial (POSIX)";
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_SERIAL
case CAHUTE_LINK_MEDIUM_WIN32_SERIAL:
return "Serial (Win32)";
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_CESG
case CAHUTE_LINK_MEDIUM_WIN32_CESG:
return "CESG502 (Win32)";
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB:
return "USB Bulk (libusb)";
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB_UMS
case CAHUTE_LINK_MEDIUM_LIBUSB_UMS:
return "USB Mass Storage (libusb)";
#endif
default:
return "(unknown)";
}
@ -247,11 +278,7 @@ fail:
* @return Cahute error, or CAHUTE_OK if no error has occurred.
*/
CAHUTE_LOCAL(int)
initialize_link_protocol(
cahute_link *link,
unsigned long flags,
int casiolink_variant
) {
init_link(cahute_link *link, unsigned long flags, int casiolink_variant) {
struct cahute_casiolink_state *casiolink_state;
struct cahute_seven_state *seven_state;
struct cahute_seven_ohp_state *seven_ohp_state;
@ -277,7 +304,10 @@ initialize_link_protocol(
flags |= PROTOCOL_FLAG_NOCHECK;
}
msg(ll_info, "Using %s.", get_protocol_name(protocol));
msg(ll_info,
"Using %s over %s.",
get_protocol_name(protocol),
get_medium_name(link->medium));
msg(ll_info,
"Playing the role of %s.",
flags & PROTOCOL_FLAG_RECEIVER ? "receiver / passive side"
@ -341,7 +371,6 @@ initialize_link_protocol(
case CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN_OHP:
case CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP:
case CAHUTE_LINK_PROTOCOL_UMS_OHP:
seven_ohp_state = &link->protocol_state.seven_ohp;
/* No need to guarantee a minimum protocol buffer size here;
@ -360,35 +389,6 @@ initialize_link_protocol(
return CAHUTE_OK;
}
/**
* De-initialize a link's protocol state.
*
* @param link Link to deinitialize.
* @return Cahute error, or CAHUTE_OK if no error has occurred.
*/
CAHUTE_LOCAL(int) deinitialize_link_protocol(cahute_link *link) {
if (!(link->flags
& (CAHUTE_LINK_FLAG_IRRECOVERABLE | CAHUTE_LINK_FLAG_TERMINATED
| CAHUTE_LINK_FLAG_GONE | CAHUTE_LINK_FLAG_RECEIVER))) {
switch (link->protocol) {
case CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK:
if (link->flags & CAHUTE_LINK_FLAG_TERMINATE)
cahute_casiolink_terminate(link);
break;
case CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN:
case CAHUTE_LINK_PROTOCOL_USB_SEVEN:
if (link->flags & CAHUTE_LINK_FLAG_TERMINATE)
cahute_seven_terminate(link);
break;
}
}
return CAHUTE_OK;
}
#if WIN32_ENABLED
# include <initguid.h>
# include <cfgmgr32.h>
@ -466,11 +466,10 @@ cahute_open_serial_link(
switch (flags & CAHUTE_SERIAL_PROTOCOL_MASK) {
case CAHUTE_SERIAL_PROTOCOL_AUTO:
/* If we are to be the sender or active side, and are not allowed
* to initiate the connection, we cannot test different things,
* therefore this cannot be used with ``CAHUTE_SERIAL_NOCHECK``. */
if ((~flags & CAHUTE_SERIAL_RECEIVER)
&& (flags & CAHUTE_SERIAL_NOCHECK)) {
/* If we are not allowed to initiate the connection, we cannot test
* different things, therefore this cannot be used with
* ``CAHUTE_SERIAL_NOCHECK``. */
if (flags & CAHUTE_SERIAL_NOCHECK) {
msg(ll_error, "We need the check flow to determine the protocol.");
return CAHUTE_ERROR_UNKNOWN;
}
@ -773,14 +772,12 @@ cahute_open_serial_link(
if (flags & CAHUTE_SERIAL_RECEIVER)
protocol_flags |= PROTOCOL_FLAG_RECEIVER;
err = initialize_link_protocol(link, protocol_flags, casiolink_variant);
err = init_link(link, protocol_flags, casiolink_variant);
if (err) {
cahute_close_link(link);
return err;
}
link->flags |= CAHUTE_LINK_FLAG_CLOSE_PROTOCOL;
*linkp = link;
return CAHUTE_OK;
}
@ -803,7 +800,7 @@ cahute_open_usb_link(
int address
) {
cahute_link *link = NULL;
int protocol;
int protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN;
unsigned long protocol_flags = 0;
int i, err = CAHUTE_ERROR_UNKNOWN;
@ -822,6 +819,7 @@ cahute_open_usb_link(
struct libusb_config_descriptor *config_descriptor = NULL;
libusb_device_handle *device_handle = NULL;
int device_count, libusberr, bulk_in = -1, bulk_out = -1;
int medium = CAHUTE_LINK_MEDIUM_LIBUSB;
#endif
if (flags & CAHUTE_USB_OHP) {
@ -1017,8 +1015,6 @@ cahute_open_usb_link(
/* The device has been found and is running the CESG502 driver! */
if (flags & CAHUTE_USB_OHP)
protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP;
else
protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN;
msg(ll_info, "Opening the following CESG502 interface:");
msg(ll_info, "%ls", device_interface);
@ -1118,18 +1114,19 @@ cahute_open_usb_link(
interface_descriptor = config_descriptor->interface[0].altsetting;
interface_class = interface_descriptor->bInterfaceClass;
if (interface_class == 8) {
if (flags & CAHUTE_USB_OHP)
protocol = CAHUTE_LINK_PROTOCOL_UMS_OHP;
else
protocol = CAHUTE_LINK_PROTOCOL_UMS;
} else if (interface_class == 255) {
if (flags & CAHUTE_USB_OHP)
protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP;
else
protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN;
} else
if (interface_class == 8)
medium = CAHUTE_LINK_MEDIUM_LIBUSB_UMS;
else if (interface_class == 255)
medium = CAHUTE_LINK_MEDIUM_LIBUSB;
else {
msg(ll_error, "Unsupported interface class %d", interface_class);
goto fail;
}
if (flags & CAHUTE_USB_OHP)
protocol = CAHUTE_LINK_PROTOCOL_USB_SEVEN_OHP;
else if (medium == CAHUTE_LINK_MEDIUM_LIBUSB_UMS)
protocol = CAHUTE_LINK_PROTOCOL_USB_MASS_STORAGE;
/* Find bulk in and out endpoints.
* This search is in case they vary between host platforms. */
@ -1287,7 +1284,7 @@ ready:
#if defined(CAHUTE_LINK_MEDIUM_LIBUSB)
link->flags = CAHUTE_LINK_FLAG_CLOSE_MEDIUM;
link->medium = CAHUTE_LINK_MEDIUM_LIBUSB;
link->medium = medium;
link->medium_state.libusb.context = context;
link->medium_state.libusb.handle = device_handle;
link->medium_state.libusb.bulk_in = bulk_in;
@ -1324,12 +1321,10 @@ prepared:
if (flags & CAHUTE_USB_NOTERM)
protocol_flags |= PROTOCOL_FLAG_NOTERM;
if ((err = initialize_link_protocol(link, protocol_flags, 0)))
if ((err = init_link(link, protocol_flags, 0)))
goto fail;
link->flags |= CAHUTE_LINK_FLAG_CLOSE_PROTOCOL;
*linkp = link;
return CAHUTE_OK;
fail:
@ -1479,8 +1474,23 @@ CAHUTE_EXTERN(void) cahute_close_link(cahute_link *link) {
if (link->cached_device_info)
free(link->cached_device_info);
if (link->flags & CAHUTE_LINK_FLAG_CLOSE_PROTOCOL)
deinitialize_link_protocol(link);
if ((link->flags & CAHUTE_LINK_FLAG_TERMINATE)
&& !(
link->flags
& (CAHUTE_LINK_FLAG_IRRECOVERABLE | CAHUTE_LINK_FLAG_TERMINATED
| CAHUTE_LINK_FLAG_GONE | CAHUTE_LINK_FLAG_RECEIVER)
)) {
switch (link->protocol) {
case CAHUTE_LINK_PROTOCOL_SERIAL_CASIOLINK:
cahute_casiolink_terminate(link);
break;
case CAHUTE_LINK_PROTOCOL_SERIAL_SEVEN:
case CAHUTE_LINK_PROTOCOL_USB_SEVEN:
cahute_seven_terminate(link);
break;
}
}
if (link->flags & CAHUTE_LINK_FLAG_CLOSE_MEDIUM) {
switch (link->medium) {

View File

@ -209,8 +209,184 @@ cahute_read_from_link(
* ``target_size`` (while the caller only requires ``size``).
* It must set ``bytes_read`` to the actual number of bytes read
* this pass. */
if (link->protocol == CAHUTE_LINK_PROTOCOL_UMS
|| link->protocol == CAHUTE_LINK_PROTOCOL_UMS_OHP) {
switch (link->medium) {
#ifdef CAHUTE_LINK_MEDIUM_POSIX_SERIAL
case CAHUTE_LINK_MEDIUM_POSIX_SERIAL: {
ssize_t ret;
if (timeout > 0) {
/* Use select() to wait for input to be present. */
fd_set read_fds, write_fds, except_fds;
struct timeval timeout_tv;
int select_ret;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&except_fds);
FD_SET(link->medium_state.posix.fd, &read_fds);
timeout_tv.tv_sec = timeout / 1000;
timeout_tv.tv_usec = (timeout % 1000) * 1000;
select_ret = select(
link->medium_state.posix.fd + 1,
&read_fds,
&write_fds,
&except_fds,
&timeout_tv
);
switch (select_ret) {
case 1:
/* Input is ready for us to read! */
break;
case 0:
goto time_out;
default:
msg(ll_error,
"An error occurred while calling select() %s (%d)",
strerror(errno),
errno);
return CAHUTE_ERROR_UNKNOWN;
}
}
ret = read(link->medium_state.posix.fd, dest, target_size);
if (ret < 0)
switch (errno) {
case 0:
continue;
case ENODEV:
case EIO:
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_error,
"An error occurred while calling read() %s (%d)",
strerror(errno),
errno);
return CAHUTE_ERROR_UNKNOWN;
}
bytes_read = (size_t)ret;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_SERIAL
case CAHUTE_LINK_MEDIUM_WIN32_SERIAL:
case CAHUTE_LINK_MEDIUM_WIN32_CESG: {
DWORD received = 0;
BOOL ret;
ret = ReadFile(
link->medium_state.windows.handle,
dest,
target_size,
&received,
&link->medium_state.windows.overlapped
);
if (!ret) {
DWORD werr = GetLastError();
if (werr == ERROR_IO_PENDING) {
ret = WaitForSingleObject(
link->medium_state.windows.overlapped.hEvent,
timeout ? timeout : INFINITE
);
switch (ret) {
case WAIT_OBJECT_0:
ret = GetOverlappedResult(
link->medium_state.windows.handle,
&link->medium_state.windows.overlapped,
&received,
FALSE
);
if (!ret) {
werr = GetLastError();
if (werr == ERROR_GEN_FAILURE)
return CAHUTE_ERROR_GONE;
log_windows_error("GetOverlappedResult", werr);
return CAHUTE_ERROR_UNKNOWN;
}
break;
case WAIT_TIMEOUT:
CancelIo(link->medium_state.windows.handle);
goto time_out;
default:
log_windows_error(
"WaitForSingleObject",
GetLastError()
);
CancelIo(link->medium_state.windows.handle);
return CAHUTE_ERROR_UNKNOWN;
}
} else {
log_windows_error("ReadFile", GetLastError());
return CAHUTE_ERROR_UNKNOWN;
}
}
bytes_read = (size_t)received;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB: {
int libusberr;
int received;
libusberr = libusb_bulk_transfer(
link->medium_state.libusb.handle,
link->medium_state.libusb.bulk_in,
dest,
target_size,
&received,
timeout
);
switch (libusberr) {
case 0:
break;
case LIBUSB_ERROR_PIPE:
case LIBUSB_ERROR_NO_DEVICE:
case LIBUSB_ERROR_IO:
msg(ll_error, "USB device is no longer available.");
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
case LIBUSB_ERROR_TIMEOUT:
goto time_out;
default:
msg(ll_error,
"libusb_bulk_transfer returned %d: %s",
libusberr,
libusb_error_name(libusberr));
if (libusberr == LIBUSB_ERROR_OVERFLOW)
msg(ll_error, "Required buffer size was %d.", received);
return CAHUTE_ERROR_UNKNOWN;
}
bytes_read = (size_t)received;
} break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB_UMS
case CAHUTE_LINK_MEDIUM_LIBUSB_UMS: {
cahute_u8 status_buf[16];
cahute_u8 payload[16];
size_t avail;
@ -274,189 +450,11 @@ cahute_read_from_link(
return err;
bytes_read = avail;
} else {
switch (link->medium) {
#ifdef CAHUTE_LINK_MEDIUM_POSIX_SERIAL
case CAHUTE_LINK_MEDIUM_POSIX_SERIAL: {
ssize_t ret;
if (timeout > 0) {
/* Use select() to wait for input to be present. */
fd_set read_fds, write_fds, except_fds;
struct timeval timeout_tv;
int select_ret;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&except_fds);
FD_SET(link->medium_state.posix.fd, &read_fds);
timeout_tv.tv_sec = timeout / 1000;
timeout_tv.tv_usec = (timeout % 1000) * 1000;
select_ret = select(
link->medium_state.posix.fd + 1,
&read_fds,
&write_fds,
&except_fds,
&timeout_tv
);
switch (select_ret) {
case 1:
/* Input is ready for us to read! */
break;
case 0:
goto time_out;
default:
msg(ll_error,
"An error occurred while calling select() %s (%d)",
strerror(errno),
errno);
return CAHUTE_ERROR_UNKNOWN;
}
}
ret = read(link->medium_state.posix.fd, dest, target_size);
if (ret < 0)
switch (errno) {
case 0:
continue;
case ENODEV:
case EIO:
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_error,
"An error occurred while calling read() %s (%d)",
strerror(errno),
errno);
return CAHUTE_ERROR_UNKNOWN;
}
bytes_read = (size_t)ret;
}
break;
} break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_SERIAL
case CAHUTE_LINK_MEDIUM_WIN32_SERIAL:
case CAHUTE_LINK_MEDIUM_WIN32_CESG: {
DWORD received = 0;
BOOL ret;
ret = ReadFile(
link->medium_state.windows.handle,
dest,
target_size,
&received,
&link->medium_state.windows.overlapped
);
if (!ret) {
DWORD werr = GetLastError();
if (werr == ERROR_IO_PENDING) {
ret = WaitForSingleObject(
link->medium_state.windows.overlapped.hEvent,
timeout ? timeout : INFINITE
);
switch (ret) {
case WAIT_OBJECT_0:
ret = GetOverlappedResult(
link->medium_state.windows.handle,
&link->medium_state.windows.overlapped,
&received,
FALSE
);
if (!ret) {
werr = GetLastError();
if (werr == ERROR_GEN_FAILURE)
return CAHUTE_ERROR_GONE;
log_windows_error("GetOverlappedResult", werr);
return CAHUTE_ERROR_UNKNOWN;
}
break;
case WAIT_TIMEOUT:
CancelIo(link->medium_state.windows.handle);
goto time_out;
default:
log_windows_error(
"WaitForSingleObject",
GetLastError()
);
CancelIo(link->medium_state.windows.handle);
return CAHUTE_ERROR_UNKNOWN;
}
} else {
log_windows_error("ReadFile", GetLastError());
return CAHUTE_ERROR_UNKNOWN;
}
}
bytes_read = (size_t)received;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB: {
int libusberr;
int received;
libusberr = libusb_bulk_transfer(
link->medium_state.libusb.handle,
link->medium_state.libusb.bulk_in,
dest,
target_size,
&received,
timeout
);
switch (libusberr) {
case 0:
break;
case LIBUSB_ERROR_PIPE:
case LIBUSB_ERROR_NO_DEVICE:
case LIBUSB_ERROR_IO:
msg(ll_error, "USB device is no longer available.");
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
case LIBUSB_ERROR_TIMEOUT:
goto time_out;
default:
msg(ll_error,
"libusb_bulk_transfer returned %d: %s",
libusberr,
libusb_error_name(libusberr));
if (libusberr == LIBUSB_ERROR_OVERFLOW)
msg(ll_error, "Required buffer size was %d.", received
);
return CAHUTE_ERROR_UNKNOWN;
}
bytes_read = (size_t)received;
}
break;
#endif
default:
CAHUTE_RETURN_IMPL("No method available for reading.");
}
default:
CAHUTE_RETURN_IMPL("No method available for reading.");
}
if (!bytes_read)
@ -552,8 +550,126 @@ cahute_write_to_link(cahute_link *link, cahute_u8 const *buf, size_t size) {
*
* This way, if only a partial write was achieved, the
* implementation-specific write function can be called again. */
if (link->protocol == CAHUTE_LINK_PROTOCOL_UMS
|| link->protocol == CAHUTE_LINK_PROTOCOL_UMS_OHP) {
switch (link->medium) {
#ifdef CAHUTE_LINK_MEDIUM_POSIX_SERIAL
case CAHUTE_LINK_MEDIUM_POSIX_SERIAL: {
ssize_t ret;
ret = write(link->medium_state.posix.fd, buf, size);
if (ret < 0)
switch (errno) {
case ENODEV:
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_fatal, "errno was %d: %s", errno, strerror(errno));
return CAHUTE_ERROR_UNKNOWN;
}
bytes_written = (size_t)ret;
} break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_SERIAL
case CAHUTE_LINK_MEDIUM_WIN32_SERIAL:
case CAHUTE_LINK_MEDIUM_WIN32_CESG: {
DWORD sent;
BOOL ret;
ret = WriteFile(
link->medium_state.windows.handle,
buf,
size,
&sent,
&link->medium_state.windows.overlapped
);
if (!ret) {
DWORD werr = GetLastError();
if (werr == ERROR_IO_PENDING) {
ret = WaitForSingleObject(
link->medium_state.windows.overlapped.hEvent,
INFINITE
);
switch (ret) {
case WAIT_OBJECT_0:
ret = GetOverlappedResult(
link->medium_state.windows.handle,
&link->medium_state.windows.overlapped,
&sent,
FALSE
);
if (!ret) {
werr = GetLastError();
if (werr == ERROR_GEN_FAILURE)
return CAHUTE_ERROR_GONE;
log_windows_error("GetOverlappedResult", werr);
return CAHUTE_ERROR_UNKNOWN;
}
break;
default:
log_windows_error(
"WaitForSingleObject",
GetLastError()
);
return CAHUTE_ERROR_UNKNOWN;
}
} else {
log_windows_error("WriteFile", werr);
return CAHUTE_ERROR_UNKNOWN;
}
}
bytes_written = (size_t)sent;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB: {
int libusberr;
int sent;
libusberr = libusb_bulk_transfer(
link->medium_state.libusb.handle,
link->medium_state.libusb.bulk_out,
(cahute_u8 *)buf,
size,
&sent,
0 /* Unlimited timeout. */
);
switch (libusberr) {
case 0:
break;
case LIBUSB_ERROR_PIPE:
case LIBUSB_ERROR_NO_DEVICE:
case LIBUSB_ERROR_IO:
msg(ll_error, "USB device is no longer available.");
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_error,
"libusb_bulk_transfer returned %d: %s",
libusberr,
libusb_error_name(libusberr));
return CAHUTE_ERROR_UNKNOWN;
}
bytes_written = (size_t)sent;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB_UMS
case CAHUTE_LINK_MEDIUM_LIBUSB_UMS: {
size_t to_send = size > 0xFFFF ? 0xFFFF : size;
cahute_u8 payload[16], status_buf[16];
int err;
@ -593,131 +709,11 @@ cahute_write_to_link(cahute_link *link, cahute_u8 const *buf, size_t size) {
return err;
bytes_written = to_send;
} else {
switch (link->medium) {
#ifdef CAHUTE_LINK_MEDIUM_POSIX_SERIAL
case CAHUTE_LINK_MEDIUM_POSIX_SERIAL: {
ssize_t ret;
ret = write(link->medium_state.posix.fd, buf, size);
if (ret < 0)
switch (errno) {
case ENODEV:
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_fatal,
"errno was %d: %s",
errno,
strerror(errno));
return CAHUTE_ERROR_UNKNOWN;
}
bytes_written = (size_t)ret;
} break;
} break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_WIN32_SERIAL
case CAHUTE_LINK_MEDIUM_WIN32_SERIAL:
case CAHUTE_LINK_MEDIUM_WIN32_CESG: {
DWORD sent;
BOOL ret;
ret = WriteFile(
link->medium_state.windows.handle,
buf,
size,
&sent,
&link->medium_state.windows.overlapped
);
if (!ret) {
DWORD werr = GetLastError();
if (werr == ERROR_IO_PENDING) {
ret = WaitForSingleObject(
link->medium_state.windows.overlapped.hEvent,
INFINITE
);
switch (ret) {
case WAIT_OBJECT_0:
ret = GetOverlappedResult(
link->medium_state.windows.handle,
&link->medium_state.windows.overlapped,
&sent,
FALSE
);
if (!ret) {
werr = GetLastError();
if (werr == ERROR_GEN_FAILURE)
return CAHUTE_ERROR_GONE;
log_windows_error("GetOverlappedResult", werr);
return CAHUTE_ERROR_UNKNOWN;
}
break;
default:
log_windows_error(
"WaitForSingleObject",
GetLastError()
);
return CAHUTE_ERROR_UNKNOWN;
}
} else {
log_windows_error("WriteFile", werr);
return CAHUTE_ERROR_UNKNOWN;
}
}
bytes_written = (size_t)sent;
}
break;
#endif
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB: {
int libusberr;
int sent;
libusberr = libusb_bulk_transfer(
link->medium_state.libusb.handle,
link->medium_state.libusb.bulk_out,
(cahute_u8 *)buf,
size,
&sent,
0 /* Unlimited timeout. */
);
switch (libusberr) {
case 0:
break;
case LIBUSB_ERROR_PIPE:
case LIBUSB_ERROR_NO_DEVICE:
case LIBUSB_ERROR_IO:
msg(ll_error, "USB device is no longer available.");
link->flags |= CAHUTE_LINK_FLAG_GONE;
return CAHUTE_ERROR_GONE;
default:
msg(ll_error,
"libusb_bulk_transfer returned %d: %s",
libusberr,
libusb_error_name(libusberr));
return CAHUTE_ERROR_UNKNOWN;
}
bytes_written = (size_t)sent;
}
break;
#endif
default:
CAHUTE_RETURN_IMPL("No method available for writing.");
}
default:
CAHUTE_RETURN_IMPL("No method available for writing.");
}
if (bytes_written >= size)
@ -1057,8 +1053,8 @@ cahute_scsi_request(
/* The medium-specific implementation must store the status in the
* ``status`` variable (NOT ``*statusp``). */
switch (link->medium) {
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB
case CAHUTE_LINK_MEDIUM_LIBUSB: {
#ifdef CAHUTE_LINK_MEDIUM_LIBUSB_UMS
case CAHUTE_LINK_MEDIUM_LIBUSB_UMS: {
cahute_u8 cbw_buf[32];
int libusberr, sent = 0;