联系方式

  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-23:00
  • 微信:codinghelp

您当前位置:首页 >> C/C++编程C/C++编程

日期:2022-10-26 07:26

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
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。 站长地图

python代写
微信客服:codinghelp