UDI Channels: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
m Bot: Replace deprecated source tag with syntaxhighlight
 
(6 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[Category:Uniform Driver Interface]]
== What is a UDI Channel? ==


A UDI "channel" is the nomenclature used in the UDI specification for an IPC ([https://en.wikipedia.org/wiki/Inter-process_communication Inter Process Communication]) link between two driver end-points. In other words, it is an abstraction of IPC on the host kernel which implements a compliant UDI Environment. The specific underlying mechanics of a particular kernel's implementation of UDI channel IPC, and how messages are sent across these channels are left up to the implementor.
The UDI Specification details entities called "Channels". Before we begin to explore in more detail what a channel is, you should look at [[UDI Operation Vectors]].


=What is a UDI Channel?=
Essentially then, a channel is a single flow of signaling between two drivers regions, a driver and a metalanguage, or a driver and the Management Agent, which is the OS pretty much. Calls into drivers are sent in via channels. A channel defines operation vectors, or entry points for each end of the channel.
UDI channels are generally two-way communication links, with the exception of the channel formed between the kernel and the driver for the UDI Management Metalanguage Channel. Every other channel is a two-way communication link. The Management Metalanguage channel is two-way as well technically, but there is a constraint applied by the specification which states that drivers cannot '''initiate''' communications with the host kernel unless first asked to do so. The kernel can "post" requests to the driver on this channel ''if it is interested in communicating with the driver'', and the driver can "hold on" to them until it has an appropriate response. An example would be hotplug device enumeration, where ''if the kernel is interested'', it can "post" a request for the driver to "hold on to", and the driver will not respond until a hotplug event has occured. If the kernel did not "post" the request to indicate that it was interested in hotplug events, the driver cannot initiate a hotplug notification on its own.


The management metalanguage channel is the only channel with such a restriction.
For example, let us take the example of an imaginary device that accepts character data and displays it on some screen; better yet, let's call it a terminal device. And Let's say there's a UDI metalanguage which defines communications between an OS and a terminal device, and the API expects ASCII characters (which would be very naive, but we're still using it for the example anyway).


=Underlying implementation details=
For the OS to call the device, the driver would have to export a set of entry points, much like any other driver. Therefore a set of operations vectors would be defined in the device's metalanguage. Let us do an initial mark-up.
The specific underlying behaviour of IPC messaging "channels" is implementation defined. For a Separate Address Space Driver kernel, "channels" may be implemented as cross-process IPC using shared memory or message passing. For a purely monolithic kernel, or a Single Address Space kernel, "channels" can be simplified to nothing more than function calls. UDI does not impose any particular kernel design or driver environment design on kernels -- kernels are free to flexibly implement the interfaces in the UDI specification in a manner that suits them best, and it fastest and best optimized for their own situation, workload and requirements.


=Interfaces and structures=
<source lang="C">
==Key structures==
<syntaxhighlight lang="c">
/* The opaque type used to refer to a channel. Channels are refered to via opaque handles,
* and the underlying kernel implementation is hidden from drivers.
*/
typedef <HANDLE> udi_channel_t;


/* The core IPC message type used by UDI. Every IPC message contains
typedef struct
* this base type, and new message types can be created by extending
{
* this type. Every UDI IPC message (aka "control block") is based
void (*udi_term_send_req)(char *stream, udi_ubit_32_t nchars);
* on this type, the udi_cb_t structure.
void (*udi_term_set_colour)(udi_ubit_8_t r, udi_ubit_8_t g, udi_ubit_8_t b);
*/
void (*udi_term_set_position)(udi_ubit_16_t x, udi_ubit_16_t x);
typedef struct {
} udi_term_ops_t;
udi_channel_t channel;
void *context;
void *scratch;
void *initiator_context;
udi_origin_t origin;
} udi_cb_t;
</syntaxhighlight>


==Key service calls==
</source>
<syntaxhighlight lang="c">
/* Used to spawn new channels. */
void udi_channel_spawn(
udi_channel_spawn_call_t *callback,
udi_cb_t *gcb,
udi_channel_t channel,
udi_index_t spawn_idx,
udi_index_t ops_idx,
void *channel_context);


/* Used to destroy currently active channels. */
In your average driver interface, this would be sufficient. However, we must recognize that there are two signal flows running into the driver from the OS here: a control flow and an output flow. Let's begin by isolating this into channels, like the UDI model recommends:
void udi_channel_close(udi_channel_t channel);

</syntaxhighlight>
<source lang="C">

// Ops vectors expected to be implemented by the Driver.
typedef struct
{
void (*udi_term_set_colour)(udi_ubit_8_t r, udi_ubit_8_t g, udi_ubit_8_t b);
void (*udi_term_set_position)(udi_ubit_16_t x, udi_ubit_16_t x);
} udi_term_ctl_ops_t;

typedef struct
{
void (*udi_term_send_req)(char *stream, udi_ubit_32_t nchars);
} udi_term_send_ops_t;

</source>

Great. But this is not yet sufficient. What if the device ever needs to contact the OS, and not just the other way around? The *OS* now needs to provide some form of entry point into itself for the driver to call on so that information can be passed to the OS. Let's add the idea of our terminal device being able to take touchscreen-like co-ordinate data, just as an example. So one extra flow of signals is added. But wait!

This operation needs to be an entry into the OS, and not into the driver, not so? This means that either the OS provides a direct syscall for terminal devices to call in, or it provides a...library of sorts which it will place into every terminal driver's address space. This sounds a lot like a...[[UDI Metalanguage Library]]! So the [[UDI Metalanguage]] for terminal devices would specify the entry points into libuditerm.o which a driver will be able to call. This Metalanguage library is loaded into the address space of every driver which has a 'requires udi_term' in its udiprops. So now we have a set of entry points into the driver, and we're about to add entry points into the library so that the driver can call on the OS.

<source lang="C">
typedef struct
{
void (*udi_term_coord_ind_t)(udi_ubit_16_t x, udi_ubit_16_t y, udi_ubit_16_t pressure);
} udi_term_touch_ops_t;
</source>

Nice. Now our metalanguage library, which our driver will call, has an entry point for telling our OS the co-ordinates that have been received on the device. However, this is yet another channel of signal flow within our driver, not so? Or more specifically, this signal flow goes into our OS from the driver by way of the Metalanguage library, which will call the embedding OS on the driver's behalf.

Let's re-do all of this yet again:

<source lang="C">
typedef struct
{
// Notice the '_t's here now. In the final iteration these will be nothing more than typedefs, etc as seen in the spec.
void (*udi_term_coord_ind_t)(udi_ubit_16_t x, udi_ubit_16_t y, udi_ubit_16_t pressure);
} udi_term_touch_ops_t;

typedef struct
{
void (*udi_term_set_colour_t)(udi_ubit_8_t r, udi_ubit_8_t g, udi_ubit_8_t b);
void (*udi_term_set_position_t)(udi_ubit_16_t x, udi_ubit_16_t x);
} udi_term_ctl_ops_t;

typedef struct
{
void (*udi_term_send_req_t)(char *stream, udi_ubit_32_t nchars);
} udi_term_send_ops_t;
</source>

So there are three channels that we can visibly see of communication between the OS and the driver. Stop. Now we understand the relationship between Metalanguages and Operations Vectors a bit better: A metalanguage enumerates all of the data input and output flows between the driver and the environment and specifies an operations vector, or set of entry points *per signalling channel*. In our terminal driver model, there are, so far, three conceivable channels of signal flow between the driver and the environment. Two of them are flows where the environment calls into the driver, and the third is where the driver must call into the OS to indicate co-ordinate information from a user touching its screen.

But what of UDI's asynchronous model? Every operation in UDI has a corresponding reaction from the child. So when the OS calls into our terminal driver, the call ends there, and then when the driver has completed the request, it call call us and tell us so. So actually, communications along a channel aren't strictly one-way: the call-in direction is generally one way, but the call-back direction will obviously opposite.

So, image the OS calls the terminal driver and asks it to display a string. The OS would select the correct operation function pointer, and call that. Based on how complex the OS's choice of driver execution is, it may be as simple as a normal function call; If the driver is in a separate address space, this would require the call to be queued. It would make sense if the call is queued on the *channel to which it belongs*. This way queued calls are easily organized and channel operations can be cancelled more easily on channel teardown, etc. Now the OS makes sure that the terminal driver is awake. Pre-emption occurs on some random CPU, and the terminal driver's send-request-channel-handling-thread is chosen to run. The send-request channel processing thread checks its queue, decrements the semaphore and pulls an item. Assume the next item in the queue was our send request. The send-request thread enters whatever region handles writing to I/O for the driver, and sends the buffer of characters to the device.

Note that all this time, the calling thread has been put to sleep while its request was queued. Let's say this calling thread was...bash. Now the call has been completed from the OS-side. Now the driver must generate the call into the OS to confirm the transaction. Oh! Our API above does not have a corresponding response entry point into the Metalanguage library for the call-in! We'll deal with that shortly. For now, assume that the necessary vector has been implemented in the Metalanguage library, and this lib is in the driver's address space. Now the driver generates a call into the Metalanguage lib using the function pointer that it would have receieved when it was bound to its metalanguage via the UDI metalanguage bind sequence, which exports metalanguage entry points (that is, UDI library exported routines) to the driver, which will be explained in a separate article. So the driver calls the metalanguage library's call-back entry point, which would call into the OS to say that "The operation is confirmed to be completed". This call takes place, and the metalanguage lib calls into the OS with the control block used to queue the request, as defined by the specification.

Within the control block, the OS has its own opaque set of bytes which it attaches to every request: the Id of the process that made to call; This ID helps the OS know which process to wake when the confirmation call from the driver comes in. The OS reads the control block, and wakes up the calling thread, bash. Bash is now scheduled on the run queue, and when a CPU pulls it, it returns from the syscall, and life goes on for Bash.

Now let us finally, and once and for all define UDI-acceptable API from our highly inadequate example API above.

[ARTICLE INCOMPLETE]

Latest revision as of 05:35, 9 June 2024


A UDI "channel" is the nomenclature used in the UDI specification for an IPC (Inter Process Communication) link between two driver end-points. In other words, it is an abstraction of IPC on the host kernel which implements a compliant UDI Environment. The specific underlying mechanics of a particular kernel's implementation of UDI channel IPC, and how messages are sent across these channels are left up to the implementor.

What is a UDI Channel?

UDI channels are generally two-way communication links, with the exception of the channel formed between the kernel and the driver for the UDI Management Metalanguage Channel. Every other channel is a two-way communication link. The Management Metalanguage channel is two-way as well technically, but there is a constraint applied by the specification which states that drivers cannot initiate communications with the host kernel unless first asked to do so. The kernel can "post" requests to the driver on this channel if it is interested in communicating with the driver, and the driver can "hold on" to them until it has an appropriate response. An example would be hotplug device enumeration, where if the kernel is interested, it can "post" a request for the driver to "hold on to", and the driver will not respond until a hotplug event has occured. If the kernel did not "post" the request to indicate that it was interested in hotplug events, the driver cannot initiate a hotplug notification on its own.

The management metalanguage channel is the only channel with such a restriction.

Underlying implementation details

The specific underlying behaviour of IPC messaging "channels" is implementation defined. For a Separate Address Space Driver kernel, "channels" may be implemented as cross-process IPC using shared memory or message passing. For a purely monolithic kernel, or a Single Address Space kernel, "channels" can be simplified to nothing more than function calls. UDI does not impose any particular kernel design or driver environment design on kernels -- kernels are free to flexibly implement the interfaces in the UDI specification in a manner that suits them best, and it fastest and best optimized for their own situation, workload and requirements.

Interfaces and structures

Key structures

/* The opaque type used to refer to a channel. Channels are refered to via opaque handles,
 * and the underlying kernel implementation is hidden from drivers.
 */
typedef <HANDLE> udi_channel_t;

/* The core IPC message type used by UDI. Every IPC message contains
 * this base type, and new message types can be created by extending
 * this type. Every UDI IPC message (aka "control block") is based
 * on this type, the udi_cb_t structure.
 */
typedef struct {
	udi_channel_t channel;
	void *context;
	void *scratch;
	void *initiator_context;
	udi_origin_t origin;
} udi_cb_t;

Key service calls

/* Used to spawn new channels. */
void udi_channel_spawn(
	udi_channel_spawn_call_t *callback,
	udi_cb_t *gcb,
	udi_channel_t channel, 
	udi_index_t spawn_idx,
	udi_index_t ops_idx,
	void *channel_context);

/* Used to destroy currently active channels. */
void udi_channel_close(udi_channel_t channel);