COMP3301 2022 Assignment 3
OpenBSD ccid(4) USB CCID Driver Enhancement
Due: 28th of October 2022, Week 13
$Revision: 429 $
1 OpenBSD ccid(4) USB CCID Driver Enhancement
The task in this assignment is to extend the ccid(4) driver written for Assignment 2, adding support for
concurrent access to smartcard slots and non-blocking event driven I/O.
The purpose of this assignment is to demonstrate your ability to read and comprehend technical specifications,
and apply low level C programming skills to an operating system kernel environment.
You will be provided with the USB CCID specification, a virtual USB device on the COMP3301 virtual machines,
and userland code to test your driver functionality.
This is an individual assignment. You should feel free to discuss aspects of C programming and the assignment
specification with fellow students, and discuss OpenBSD and its APIs in general terms. It is cheating to look at
another student’s code and it is cheating to allow your code to be seen or shared in printed or electronic form.
You should note that all submitted code will be subject to automated checks for plagiarism and collusion. If we
detect plagiarism or collusion (outside of the base code given to everyone), formal misconduct proceedings will be
initiated against you. If you’re having trouble, seek help from a member of the teaching staff. Don’t be tempted to
copy another student’s code. You should read and understand the statements on student misconduct in the course
profile and on the school web-site: https://www.itee.uq.edu.au/itee-student-misconduct-including-plagiarism
2 Overview
This assignment builds on Assignment 2, therefore background information about the USB CCID protocol and
smartcards applies here.
In Assignment 2 you built a basic working CCID driver that can be used by a single process. In this assignment
you will fill out the remaining functionality to make it into a more practical, useful driver for applications.
The core of this assignment is extending the ccid(4) driver featured in A2 to support multiple slots and
concurrent access using a cloneable character device.
Three additional features which build on this are:
Transactions;
Non-blocking I/O, with support for kqueue(2); and
Timeouts on CCID-level commands.
These three tasks do interrelate to some extent – e.g. transactions and non-blocking I/O overlap with a few
transaction-related ioctl() commands having non-blocking semantics. However, part marks will be given for
attempts at each of them on their own.
1
The specification for this assignment generally assumes you will be attempting all parts of the task, as a result
of this interdependence. If you decide to attempt only some parts of the task, there are some notes in the
specification about how behaviour may change, but mostly you will need to consider how an application can
logically use the resulting interface. Full marks for any one part will be difficult to achieve without attempting all
tasks.
2.1 Concurrent access
Assignment 2 specified that the device special file associated with a ccid(4) driver instance must support
exclusive access. The first open (i.e., open("/dev/ccid0", ...)) could succeed, but until the device was
closed all subsequent opens failed with EBUSY. This assignment changes the software specification to allow each
CCID reader device and slot to be opened multiple times, with extra functionality to coordinate access to the slot.
Talking to a smartcard via ccid(4) involves sending commands with write() and then getting results in a
separate syscall with read(). It is important that the read() and write() calls match up (i.e. that you read
in the result of the write command you just sent). Many application-level operations on smartcards are also
composed of multiple write()/read() pairs which only make sense when executed together in order.
With a single minor number and device special file per slot, if we allow multiple processes to open it, there will be
no reliable way to tell which write() or read() calls match with which process or set of operations, and no way
to tell if a process exits or closes their file descriptor without reading in results – all we get in our driver is the
same minor number from every process, and we only get one ccidclose() call after every file descriptor on the
system using that minor has been closed.
To rectify this, we will add both tracking of per-process state and also the ability for processes to notify the
kernel when they begin and send a sequence of operations that must be performed together (which we will refer
to as a “transaction”).
2.2 Transactions
A transaction in ccid(4) starts with a CCIDIOC_XACT_BEGIN ioctl() and ends either at a CCID_IOC_XACT_*_COMMIT
ioctl() or when the relevant file descriptor is closed (due to a close() syscall or process exit).
During a transaction, the smartcard is exclusively under the control of the process which opened the transaction
(no other process can send commands to the smartcard at the same time). Multiple commands may be sent and
their results received using read() and write() by the original process until the transaction ends.
At the end of the transaction, the device is either released for use by another process, or the transacting process
can indicate that the card should be reset or powered down. If the transaction is ended “uncleanly” (by close()
or process exit), the device is always reset.
This enables applications to be sure that even in the case where they crash in the middle of a sequence of
operations, no security state changes they make will be accidentally leaked to another process.
ccid(4) in this assignment will require a program to begin a transaction by making an ioctl() call before being
able to write any commands to the device.
2.3 VFS and vnodes
Device special file access is mediated by the virtual file system (vfs(9)) layer and the vnode(9) abstraction.
Every time a device special is opened, the VFS layer looks for an existing vnode and creates one if it doesn’t
already exist. The device special open handler (eg, ccidopen()) is called to validate the access. If that succeeds,
VFS increments a reference count on the vnode and the program gets access to the device via the file descriptor
returned by open(2). When a program closes the relevant file descriptor (fd) or exits, the vnode reference
count is decremented. Once the reference count on the vnode drops to 0 then the driver close handler (e.g.,
ccidclose()) is called.
2
This means that while your driver code will receive a ccidopen() call every time a given device minor is opened,
it will not receive a ccidclose() call every time one is closed – that call occurs only when the last file descriptor
open for that minor is closed.
Therefore, to enable the same slot to be opened multiple times, while also supporting implicit completion of the
transaction when the device is closed, ccid(4) cannot use a single minor number per driver instance.
Further, the only consistent identifier associated with an open of a device is the dev_t argument to the device
special handlers. It is possible for one program to try to open the one device multiple times and get separate file
descriptors referring to the same device. File descriptors are also inherited by programs when they call fork(2),
meaning multiple programs may have a reference to the same file descriptor. Both these situations mean the
current process ID or pointer cannot be used to safely identify which context is within a transaction. In the first
situation the same process may have multiple file descriptors open against the same device so you can’t tell which
is which, and in the second situation the process id can change without notifying the driver.
Therefore, ccid(4) must map multiple device special file minor numbers to one unit of hardware, rather than
the simple approach used in A2 where each minor mapped to one unit.
2.4 Cloneable devices
Traditionally, to open a device special file with a specific major and minor number, it must be present on a file
system (usually under /dev) and accessible to a program.
Therefore, if we decided that e.g. minor numbers 0-8 each correspond to one “slice” of slot 0, unit 0, then we
would have to create separate device special files for each (e.g. /dev/ccid0.0 => 101, 0, /dev/ccid0.1 =>
101, 1, /dev/ccid1.0 => 101, 8 etc). A program trying to access the hardware device would have to iterate
over all of the “slices” of the unit it wants to open, trying to find one that’s not busy with another process.
This is not unworkable, but it is ugly for application programmers to use. Creating lots of special files in /dev
also costs disk space on metadata, and makes administration more difficult (separate file permissions).
This is hardly a unique problem faced by ccid(4), however, and OpenBSD (like other operating systems) has
developed a “cloneable device” facility to make this scheme nicer to use. With a cloneable device, each open()
of a device special file automatically receives a new minor number, generated by the kernel. To use it, device
drivers set the D_CLONE flag in the glue code in src/sys/sys/conf.h.
When D_CLONE is enabled on a device, the kernel takes over a portion of the bits in the device minor number. A
driver (e.g., ccid(4)) is still able to use the rest of the bits of the minor number for its own purposes (e.g.,
identifying an instance of a hardware driver), but some are taken by the kernel to create separate vnodes and
dev_t values that the driver may use to differentiate between concurrent opens of the same resource.
The bits used by the kernel for dynamic minors are the high bits (most significant) of the minor number. The
number of bits left for drivers to use is identified by the CLONE_SHIFT macro in sys/specdev.h.
Currently in OpenBSD, CLONE_SHIFT is 8, which means that there are 8 bits left for the driver (leading to a
maximum of 256 hardware units for a cloneable device).
In this assignment, you will be converting ccid(4) to be a cloneable character device driver using this scheme.
2.5 Non-Blocking I/O
Traditionally, and by default, when a program performs I/O on a file descriptor the kernel may wait or sleep in
the syscall handler until the related operation makes progress. For example, if a program is fetching data off a
web server using a network socket it will perform a series of read() syscalls against the socket file descriptor. If
the server has not yet sent data, the kernel will de-schedule the calling program (put it to sleep) while it waits for
data from the server. Only when data has been received by the network socket will the kernel wake the waiting
3
program up and finish handling the read syscall. From the perspective of the program, this read syscall appears
to “block” further execution of the program until data has been received from the network.
If the program has other work it could do while waiting for data from the network server, it can configure the
file descriptor for non-blocking operation. In this situation, if the program attempts to read() from the socket
file descriptor when no data is available, the kernel will return immediately with an error, setting errno(2) to
EAGAIN. The program can detect this and move on to perform other work. When data does become available
later, a subsequent read will proceed as normal.
This relies on the kernel being able to receive data on behalf of the file descriptor outside the context of the read
syscall handler. Generally, this means the file descriptor has an associated buffer that can be filled in when the
data becomes available (from the network, or a USB device, etc). When the program then tries to read from the
file descriptor, it uses the buffered data.
Similarly, writes from a program can also block. A write() to a TCP network socket may block until the remote
end of the connection has received and acknowledged the the data. A non-blocking write may fill a buffer and
return immediately, with subsequent writes failing with EAGAIN until space in the buffer becomes available again.
The kernel is then responsible for transmitting data out of the buffer and emptying it when the remote end has
acknowledged the data.
For non-blocking read and write operations, the kernel is performing work associated with the file descriptor on
behalf of the program while the program is doing other work. When this associated work has completed (e.g., a
write buffer has space again, or a read buffer has data in it) the program likely also wants to be notified, so that
it can perform further writes or reads.
The standard UNIX facilities for handling this notification are the select(2) and poll(2) syscalls. In OpenBSD,
the select(2) system call is implemented as a wrapper around poll(2), and drivers such as ccid(4) only have
to implement a handler for poll.
These allow programs to check a large set of file descriptors at once for which (if any) are currently available for
read() or write(). They can also be configured to sleep (or block) until at least one of the set is available, or
a specified amount of time has elapsed.
When read buffer has been or is filled by the kernel, it reports that fact to the poll facility, which will (if necessary)
wake up the program and report which file descriptor has become readable.
This facility is often used by programs to enable event-driven I/O multiplexing across a large number of file
descriptors (e.g. a network server handling many clients at once, without needing a separate thread per client).
However, select(2) and poll(2) struggle to scale beyond a certain number of file descriptors or events due to
their requirement to read in the entire set of FDs every time they are called. Different operating systems provide
alternative (but non-standard) facilities to address this. OpenBSD, as a member of the BSD family of operating
systems, uses the kqueue event notification facility.
In OpenBSD 7.1 and later, the select(2) and poll(2) system calls are internally implemented on top of the
kernel facilities for kqueue(2). This means that drivers only have to implement their side of kqueue(2) and
then support for select(2) and poll(2) will be available “for free”.
Assignment 2 specified that ccid(4) only had to support blocking operation, meaning that when a write or
read operation was performed the kernel was able to wait or sleep in the ccidwrite() or ccidread() handlers
respectively for the underlying USB operation to complete. This assignment requires the implementation
of non-blocking read and write against ccid(4) devices, and support for the kqueue(2) event notification
mechanism.
3 Specifications
You will be modifying the ccid(4) driver written for Assignment 2.
4
You may assume that userland tools which use your driver will directly open the device and use the
read/write/kqfilter/ioctl interfaces specified below. You do not need to implement any additional userland
components to go with it.
3.1 CCID Functionality
The core CCID functionality remains the same as specified in assignment 2, with the following changes:
Multiple slot support: you are now required to implement support for multiple-slot CCID devices, with up
to 8 slots
Timeouts: you are now required to use a fixed timeout for CCID commands (specified in detail below).
You are also required to handle time extension requests.
Abort sequence: you are now required to use the CCID abort sequence (control transfer and bulk transfer,
including sequence number) in certain situations, including command timeouts and unclean terminations of
a transaction with an outstanding command.
The following are still true in A3:
APDU-layer only: you are only required to support readers which advertise APDU-layer operation (meaning
that your driver does not have to implement T=0 or T=1 TPDU layer encapsulation)
Automatic clock, voltage and baud rate only: you are not required to work out what voltage, clock
speed or baud rate is required by a card, and should only support readers that handle this automatically.
PPS support not required: along with being APDU-layer only, and only supporting automatic clock speed
operation, you are not required to implement PPS
No automatic power-on: you must assume the CCID reader requires an explicit power-on CCID message to
power up the ICC (i.e. the Automatic activation of ICC on inserting CCID feature is not available)
You are only required to implement enough of the CCID commands to provide the device interface in this
specification. There are CCID commands (e.g. PC_to_RDR_Mechanical for readers which can physically eject or
lock their card slots) which you do not need to implement any support for, since they have no matching ioctl
or requirement in the device interface.
Part of the objective of this assignment is reading and interpreting the CCID specification, and working out the
exact commands which will be required to implement the required behaviour. You should pay close attention to
the descriptions of each command in the specification, and read the example exchanges towards the end of the
document, to work out which are required.
3.2 Command timeouts
The ccid(4) device driver must use the following time-outs on commands:
Command Timeout
Power on 30 sec
Power off 5 sec
APDU (XfrBlock) 5 sec
APDU time extension +(bError*5) sec
(All others) 5 sec
Each time CCID reader returns a time extension request during an APDU command, wait an additional 5 seconds
multiplied by the value in bError (i.e. assume that BWT or WWT is 5 seconds).
If a command time-out is reached, then ccid(4) must abort that command using the CCID abort sequence.
The errno returned to userland due to a command timeout should be ETIMEDOUT (which does not modify the
result of CCIDIOC_GET_LAST_ERR).
5
Note that timeouts are per-command, not per-transfer.
3.3 Device special file interface
The ccid(4) device special file must continue to use major number 101 on the amd64 architecture for a character
device special file.
The device special configuration glue for ccid(4) should be modified to set D_CLONE. The bits of the device
minor below CLONE_SHIFT identify the ccid(4) hardware device instance (unit) and slot being accessed.
The following structure must be used for the bits of the minor number:
(MSB..LSB) 24..CLONE_SHIFT (CLONE_SHIFT - 1)..3 3..1
(contains) Clone number (16 bits) Unit number (5 bits) Slot number (3 bits)
For example, the minor number 123 corresponds to unit 15, slot 3, clone 0. The 2nd slot on the 3rd CCID
device, clone number 2 would be minor number 538.
The naming scheme for the special files in /dev expected by the ccidctl code provided is /dev/ccidXY, where
X is the unit number as a numeric string, and Y is a for slot 0, b for slot 1, c for slot 2, etc.
Examples:
Device Major Minor (Unit) (Slot)
/dev/ccid0a 101 0 0 0
/dev/ccid0b 101 1 0 1
/dev/ccid0c 101 2 0 2
/dev/ccid1a 101 8 1 0
/dev/ccid1b 101 9 1 1
/dev/ccid2a 101 16 2 0
/dev/ccid2b 101 17 2 1
Note that the on-disk device nodes always use clone number 0 (the kernel will fill in that part of the minor number
automatically).
Your driver may, if you wish, return additional errno(2) values as well as those specified below, in situations
that are not covered by the errno values and descriptions listed. However, it is important to note that the
driver must not return EBADMSG in any situation that is not able to sensibly update the return value of the
CCIDIOC_GET_LAST_ERR ioctl command (see below).
It’s important to note that the errno(2) specifications are very different to A2. For example, the use of
EBADMSG and EIO in A2 is the opposite way around to A3. Be careful not to confuse them.
3.3.1 open() entry point
The open() handler should ensure that the non-clone bits of the minor number have an associated kernel driver
instance with the relevant unit number, and that that device has enough slots for the slot number requested.
Access control is provided by permissions on the filesystem.
The open() handler should return the following errors for the reasons listed. On success it should return 0.
ENXIO no hardware driver is attached with the relevant unit number or the device does not have sufficient slots
6
3.3.2 ioctl()
ccid(4) specific ioctls used by the userland testing tool are provided in src/sys/dev/usb/ccidvar.h. This
header specifies both the ioctl magic numbers (CCIDIOC_* macros) and the structs used as arguments to them.
You must not modify the ioctl definitions provided: they are the interface that is required for testing.
3.3.2.1 CCIDIOC_XACT_BEGIN
This ioctl begins a transaction for the current device clone instance. If another device clone instance already has
a transaction in progress, block until the current device clone instance can begin a transaction.
On beginning the transaction, any existing error state that could be reported by the CCIDIOC_GET_LAST_ERR
ioctl is cleared.
If the smartcard is powered off, or there is a pending power state change (e.g. from the previous transaction, if
the initial attempt to reset or power down failed) then a power-on message is sent to the reader to power it on.
This ioctl() will always block until the power-on command is completed.
EACCES the current device clone instance is open read-only.
EDEADLK the current device clone instance is already within a transaction.
EBADMSG the CCID reader returned an error response to a CCID-level command (e.g. no card/ICC present,
hardware error).
3.3.2.2 CCIDIOC_XACT_TRY_BEGIN
The CCIDIOC_XACT_TRY_BEGIN ioctl attempts to begin a transaction, but returns an error if it is unable to do
so immediately due to another extant transaction on the slot.
If this ioctl successfully begins a transaction, any existing error state that could be reported by the
CCIDIOC_GET_LAST_ERR ioctl is cleared.
If the smartcard is powered off, or there is a pending power state change (e.g. from the previous transaction, if
the initial attempt to reset or power down failed) then a power-on message is sent to the reader to power it on.
This ioctl() does not block waiting for the response to the power-on message – any errors received during
power-on will instead be seen by the first write() call after the transaction begins.
EACCES the current device clone instance is open read-only.
EDEADLK the current device clone instance is already within a transaction.
EAGAIN a different device clone instance (e.g. in another process) is already within a transaction for this device.
3.3.2.3 CCIDIOC_XACT_COMMIT
Signal the end of the transaction. This will then allow another device clone instance to begin a transaction.
To avoid blocking in this ioctl, the program should wait until the file descriptor becomes writable or readable
first. CCIDIOC_XACT_COMMIT after a write() operation (but before the matching read()) starts a CCID abort
sequence that must complete before the transaction ends.
The CCIDIOC_XACT_COMMIT variant of a transaction commit can be used by a program when it did not change
the security disposition of the smartcard.
EPERM the current device clone instance is not currently within a transaction.
3.3.2.4 CCIDIOC_XACT_RESET_COMMIT
Send a power on command to the reader to begin a warm reset of the smart card, and then end the transaction.
This will then allow another device clone instance to begin a transaction after the reset has completed.
7
To avoid blocking in this ioctl, the program should wait until the file descriptor becomes writable or readable
first. CCIDIOC_XACT_RESET_COMMIT after a write() operation (but before the matching read()) starts a CCID
abort sequence that must complete before the transaction ends.
The CCIDIOC_XACT_RESET_COMMIT variant of a transaction commit is used by a program when it changed the
security disposition of the smartcard, eg, if a credential such as a PIN was sent to the card to unlock the use of
certain keys or features.
Errors during the abort sequence (if one is needed) or during the warm reset command or any other CCID
commands made are not returned to this ioctl(). If the relevant commands fail, a message should be written
to the system console and dmesg, and the operations should be retried during the next CCIDIOC_XACT_BEGIN,
with errors reported there (or in the first write() afterwards if non-blocking).
EPERM the device clone instance is not currently within a transaction.
3.3.2.5 CCIDIOC_XACT_POWEROFF_COMMIT
Send a power off command to the reader to begin a cold reset of the smartcard, and then end the transaction.
This will then allow another device clone instance to begin a transaction with a cold-reset device.
To avoid blocking in this ioctl, the program should wait until the file descriptor becomes writable or readable first.
CCIDIOC_XACT_POWEROFF_COMMIT after a write() operation (but before the matching read()) starts a CCID
abort sequence that must complete before the transaction ends.
The CCIDIOC_XACT_POWEROFF_COMMIT variant of a transaction commit is used by a program when it changed
the security disposition of the smart card, eg, if a credential such as a PIN was sent to the card to unlock the
use of certain keys or features.
Errors during the abort sequence (if one is needed) or during the power-off command or any other CCID
commands made are not returned to this ioctl(). If the relevant commands fail, they should be retried during
the next CCIDIOC_XACT_BEGIN, and errors reported there (or in the first write() afterwards if non-blocking).
EPERM the current device clone instance is not currently within a transaction.
3.3.2.6 CCIDIOC_GET_LAST_ERR
The CCIDIOC_GET_LAST_ERR ioctl should provide more detailed information about an error which resulted in
EBADMSG being returned by read() or another ioctl (such as CCIDIOC_XACT_BEGIN).
It contains both a numeric error code and a string description which the driver should fill out based on the error
description tables in the CCID specification. The string should be of the format ERROR NAME: Cause string.
For example, the numeric error code -2 should return a string similar to ICC_MUTE: No ICC present, or CCID
timed out while talking to the ICC.
Our tests will look for the “ERROR NAME” at the start of the string to match the CCID specification, but will
not require any particular cause description following it.
The “last error” state is stored per device clone instance. If no error has occurred since this clone instance was
opened, the ioctl should return ENOMSG instead of a ccid_err_info struct.
This ioctl may fail with errors for the following reasons:
EPERM the current device clone instance is not currently within a transaction.
ENOMSG no error has occurred within the current transaction.
8
3.3.2.7 CCIDIOC_GET_ATR
This ioctl retrieves the last answer-to-reset (ATR) string returned by a card in the CCID reader. If the CCID
device has never been powered up, or if the last message from the CCID device indicated that no card is present,
this should return ENOMSG.
It can be run at any time, regardless of transaction status.
The ATR is returned inside a struct ccid_atr. The len field is set to the length of the ATR, and the actual
bytes of the ATR are written into the buf field.
This ioctl may fail with errors for the following reasons:
ENOMSG the card in this slot has never been powered up, or the last message from the reader indicated that
no card is present.
3.3.2.8 CCIDIOC_GET_DRIVER_INFO
This ioctl must be implemented as specified in Assignment 2, with one variation: the ccid_driver_slot member
must be filled out with the slot index which is open. The A3 base patch contains a working implementation
(which sets the slot index to 0 always).
3.3.2.9 CCIDIOC_GET_DESCRIPTOR
This ioctl must be implemented as specified in Assignment 2. The A3 base patch contains a complete
implementation.
3.3.2.10 USB_DEVICEINFO
This ioctl must be implemented as specified in Assignment 2. The A3 base patch contains a complete
implementation.
3.3.3 write() entry point
The write handler allows userland to send an APDU to a smartcard that is inserted into the reader.
An entire APDU must be provided to write() in a single call – one APDU cannot be spread across multiple
write() calls. A call to writev() is treated as a single write (even if it uses multiple iovecs).
The write() handler encapsulates the APDU bytes from userland in a CCID message and submits it to the
bulk-out message USB endpoint.
A write() must be performed within a ccid(4) transaction begun by the current device clone instance.
A blocking write must wait until at least the command submission to the CCID reader has been completed.
A non-blocking write must begin the command submission to the CCID reader and return.
Note: if transactions are not implemented, then write() on a device must implicitly power up the card, if it is
not presently powered up.
EAGAIN non-blocking I/O was requested, but the operation would block.
EPERM the current device clone instance has not begun a transaction.
EBUSY a request is already in progress – either waiting for a response from the reader or a response has been
received but read() has not yet consumed it.
EACCES the device is open read-only.
EBADMSG a preceding non-blocking read or poweron command had a CCID error, or a CCID error occurred
during blocking operation which prevented sending the command.
9
EIO a preceding non-blocking read had a USB error, or a USB level error occurred during blocking operation
that prevented sending the command.
3.3.4 read() entry point
read() gets a response APDU from the smartcard inserted into the relevant slot of the CCID reader via the
bulk-in response USB endpoint.
The response matches the previous write() made to this device clone instance.
A blocking read() will wait for a response CCID message from the reader related to the most recent write()
call that submitted a request. It should block until the response is available.
The response APDU (or the portion of it that will fit) will be transferred to the buffer supplied by userland. No
CCID-level headers or structures are included (just the response APDU itself).
EAGAIN non-blocking I/O was requested, but the operation would block.
EPERM the current device clone instance has not begun a transaction.
EACCES the current device clone instance is open read-only.
EBADMSG the CCID reader returned an error response to the CCID-level command (e.g. no card/ICC present,
hardware error). Note that this does not include errors returned as an APDU-level error by the card (e.g. a
SW other than 0x9000), which are processed as normal response APDUs.
ENOMSG no write request has been made.
EBADMSG a preceding non-blocking write had a CCID-level error, or a CCID-level error occurred during blocking
operation which prevented receiving the response.
EIO a preceding non-blocking write had a USB error, or a USB level error occurred during blocking operation
that prevented receiving the response.
3.3.5 close()
If close() is called while the current device clone instance is in a transaction, any command in progress must be
aborted using the CCID abort sequence, and close() will then implicitly reset the smartcard (a warm reset, by
sending an extra power-on command) and end the transaction.
If no transaction is open, close() does not perform any CCID-level operations.
close() must not return an error. If any operations during close() fail, or the device returns an error, then a
message should be written to the system console and dmesg, and the relevant operations should be retried during
the next CCIDIOC_XACT_BEGIN, with errors reported there (or in the first write() afterwards if non-blocking).
Note: if transactions are not implemented, then close() should behave as if it always occurred during a
transaction.
3.3.6 kqfilter
The ccidkqfilter() function entry point is used by the kqueue subsystem to request information about the
usability of the current device clone instance, notionally for read and write operations. In the ccid(4) driver, the
meaning of “usability for read and write” also depends on whether a transaction is in progress on the current
hardware device slot.
The following table summarises the possible events which can be observed using kqueue(2) on a ccid(4) device:
State Event Reason
No transaction W ready to start transaction (CCIDIOC_XACT_TRY_BEGIN)
10
State Event Reason
Transaction open W ready to start a command (write())
W ready to end transaction (CCIDIOC_XACT_*COMMIT)
State Event Reason
Command in progress R ready to receive result (read())
Outside of a ccid(4) transaction, a program can request to be informed when it can try to begin a transaction
by waiting for the file descriptor associated with the current device clone instance to become writable.
Once a transaction has successfully begun, the program can again wait for the file descriptor to become writable
before calling write() to submit a command.
After a command has been submitted, it can then wait for the file descriptor to become readable before calling
the associated read(). Once the file descriptor is writable again, it can end the transaction without blocking.
Note: if transactions are not implemented, the device should simply poll writable when ready for a command,
and poll readable when the results are available.
Note 2: in an earlier version of this assignment, poll() was also required to be implemented. In OpenBSD 7.1,
the poll(2) system call was changed to be implemented on top of kqueue(2). Drivers are only required to
implement kqfilter(), and their driverpoll() callback will never be called.
3.3.7 poll
The ccidpoll() entry point exists for historical reasons and will never be called. In the ccid(4) driver, the
errno ENOTSUP should be returned unconditionally by ccidpoll().
When userland calls poll(2), your driver will be called under the ccidkqfilter() entry point – the poll(2)
and select(2) system calls are implemented on top of kqueue(2) internally.
3.4 Testing
3.4.1 Userland tool
In the provided code (see section below for instructions, similar to A2) we have given you a userland tool named
ccidctl, which you may use to test your driver.
You can build ccidctl by running make in the directory usr.sbin/ccidctl as per usual for a userland tool.
The commandline interface for ccidctl is summarised below:
usage: ccidctl ls [-v]
ccidctl mknod [-v]
ccidctl show [-v] ccid0a
ccidctl atr ccid0a
ccidctl ykversion [-n] ccid0a [ccidXY..]
ccidctl longcmd [-n] [-t sec] ccid0a [ccidXY..]
3.4.1.1 ccidctl ls
ccidctl ls iterates over /dev/ccid0a, /dev/ccid0b, /dev/ccid1a, etc. and shows information about each
device. The amount of information can be increased with the -v flag.
Example output (showing 2 CCID devices on the system, with 2 slots each):
11
$ ccidctl ls
ccid0a: device: "ccid0" driver: "ccid" unit: 0 slot: 0
ccid0b: device: "ccid0" driver: "ccid" unit: 0 slot: 1
ccid1a: device: "ccid1" driver: "ccid" unit: 1 slot: 0
ccid1b: device: "ccid1" driver: "ccid" unit: 1 slot: 1
$
Note that your simulator will vary from this output (the number of slots is always at least 2 per device, but is
different for each user).
3.4.1.2 ccidctl mknod
ccidctl mknod creates device nodes in /dev for ccid0a, ccid0b etc, for up to 4 devices with 8 slots each.
3.4.1.3 ccidctl show
ccidctl show opens the specified device special file and shows information about the CCID device. The amount
of information can be increased with the -v flag
Example output (for the simulator device in your VM):
$ ccidctl show -v ccid0a
ccid0a: device: "ccid0" driver: "ccid" unit: 0 slot: 0
usb:
id: 3301:6497
vendor: "COMP3301"
product: "CCID Simulator"
release: "0.00"
serial: "01"
smartcard:
CCID rev: 1.10
max slot index: 1
supported voltages: 1.8V 3.0V 5.0V
protocols: T=0 T=1
default clock: 3580KHz (3.580MHz)
maximum clock: 3580KHz (3.580MHz)
num clock supported: default
data rate: 9600 bps
max data rate: 115200 bps
num data rates supported: default
max IFSD: 255
mechanical: none
features: auto-atr auto-icc-voltage auto-icc-clock auto-baud auto-pps auto-ifsd extended-apdu-exch
max message length: 32778
lcd: none
max ccid busy slots: 2
$
3.4.1.4 ccidctl atr
ccidctl atr retrieves the answer-to-reset (ATR) string from the card in the given CCID device. If the device
has already been powered up, it returns the last cached ATR. Otherwise it will open a transaction to force the
device to power-up and retrieve the ATR afterwards.
12
3.4.1.5 ccidctl ykversion
ccidctl ykversion opens a transaction with each given CCID device, selects an applet, and retrieves a firmware
version string from it.
If multiple devices are given, it will attempt the commands in parallel across all of them using non-blocking I/O.
You can also give the -n option to force non-blocking I/O on a single device.
This command will print more information than the ykselect command in A2, including detailed information
from the card applet’s response-to-select.
3.4.1.6 ccidctl longcmd
ccidctl longcmd opens a transaction with each given CCID device, selects an applet and runs a command that
takes a long time to complete.
The length of time taken to complete the command can be controlled using the -t secs option.
If multiple devices are given, it will attempt the commands in parallel across all of them using non-blocking I/O.
You can also give the -n option to force non-blocking I/O on a single device.
3.4.2 Other testing
You may wish to alter ccidctl during your testing, and we strongly recommend that you do. Make sure to test
invalid sizes and arguments to ioctl commands and read and write, performing operations out of order, and
other similar error conditions.
We will not be using your ccidctl code in your repository for marking, so you are free to modify it as you wish
and keep those modifications in your a3 branch.
Note that the ioctl interface of the device must be kept exactly as it is given to you (i.e. the ioctl magic
numbers and their arguments and structs defined in dev/usb/ccidvar.h).
We will be testing for error conditions (not just success) when marking your submission for this assignment.
3.5 Code Style
Your code is to be written according to OpenBSD’s style guide, as per the style(9) man page.
An automatic tool for checking for style violations is available at https://stluc.manta.uqcloud.net/comp3301/p
ublic/2022/cstyle.pl. This tool will be used for calculating your style marks for this assignment.
3.6 Compilation
Your code for this assignment is to be built on an amd64 OpenBSD 7.1 system identical to your course-provided
VM. It must compile as a result of make obj; make config; make in sys/arch/amd64/compile/GENERIC.MP.
relative to your repository root.
3.7 Provided code
A patch will be provided that includes source for the ccidctl userland program, and the sys/dev/usb/ccidvar.h
header file containing the ioctls that ccidctl expects the ccid(4) driver to implement.
It also contains the file sys/dev/usb/ccidreg.h. This file contains struct definitions for generating and
parsing CCID messages, including all messages necessary for this assignment.
Finally, it contains a skeleton for your actual driver code, and autoconf(9) declarations. This code implements
roughly the common subset of assignments 2 and 3.
13
The provided code can be downloaded as a single patch file at:
https://stluc.manta.uqcloud.net/comp3301/public/2022/a3-base.patch
You should create a new a3 branch in your repository based on the openbsd-7.1 tag using git checkout, and
then apply this base patch using the git am command:
$ git checkout -b a3 openbsd-7.1
$ ftp https://stluc.manta.uqcloud.net/comp3301/public/2022/a3-base.patch
$ git am < a3-base.patch
$ rm a3-base.patch
$ git push origin a3
3.8 Recommendations
You should refer to the course practical exercises (especially pracs 4 and 5) for the basics of creating a USB
device driver and attaching to a device.
You may freely re-use your own code from Assignment 2 if you wish, to fill out the basics of your driver for
this assignment. You may also re-use code from any of the course practicals and from elsewhere in the OpenBSD
source tree.
The following are examples of some APIs you will likely need to use in this assignment:
usbd_open_pipe(9)
usbd_transfer(9)
uiomove(9)
tsleep(9)
You may also wish to refer to the code of some existing device drivers in the kernel, in particular:
uvideo(4)
cdce(4)
4 Submission
Submission must be made electronically by committing to your Git repository on source.eait.uq.edu.au. In
order to mark your assignment the markers will check out the a3 branch from your repository. Code checked in
to any other branch in your repository will not be marked.
As per the source.eait.uq.edu.au usage guidelines, you should only commit source code and Makefiles.
Your a3 branch should consist of:
The openbsd-7.1 base commit
The a3 base patch commit
Commit(s) implementing your ccid(4) driver
We recommend making regular commits and pushing them as you progress through your implementation. As this
assignment involves a kernel driver, it is quite likely you will cause a kernel panic at some point, which may lead
to filesystem corruption. Best practise is to commit and push before every reboot of your VM, just in case.
4.1 Marking
Your submission will be marked by course tutors and staff, who will annotate feedback on your code using an
online tool (Gerrit).
14
You will receive an e-mail containing a link to your detailed feedback when it is available, as well as a final grade
on Blackboard.
4.2 Late submission
If you wish to make a late submission and understand the penalty that will be applied (see the course profile for
details), then please e-mail the Git SHA you wish to have marked to Matt to submit.
The Git SHA can be found in the output of the command git log --format=reference HEAD~1..HEAD. If
you’re confused, e-mail the whole output of that command.
We will not automatically mark additional commits you add to your a3 branch after the due date as if they were
a late submission. The timestamp used to calculate your late penalty is the timestamp of the e-mail, not your
commit.
版权所有:留学生编程辅导网 2020 All Rights Reserved 联系方式:QQ:99515681 微信:codinghelp 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。