Subject: Proposal for generic kernel event framework
To: None <tech-kern@netbsd.org>
From: Jason Thorpe <thorpej@nas.nasa.gov>
List: tech-kern
Date: 01/25/1999 19:40:31
This is a description of my framework for generic event notification
within the NetBSD kernel, called `kevent'.

BASIC MODEL
-----------

The kevent framework has the notion of `event servers' and `event clients'.
Things which wait for events to happen are clients.  Things which notify
the clients that the event has occured are servers.

For each event type, there is one event server.  Event clients are bound
to specific servers, and thus can receive only one event type.  If the
owner of a client wishes to receive notification of more than one event
type, it must use multiple clients bound to the corresponding event servers.

A message may accompany the notification of an event.  For example, a
network driver may be waiting for "power-status" events.  A "power-status"
event message may include one of the following message payloads:

	"suspend"
	"resume"
	"line"
	"battery"
	"level" <int:72>

The format of any message payloads are defined by the owner of the server.
The message payload is opaque to the kevent framework.

DATA STRUCTURES
---------------

kevent_message:

	This structure defines an event message which is sent to
	clients.  A kevent_message is always send to a client,
	even though there may be no payload.

	The one public member of a kevent_message is "kmsg_len",
	which is the length of the payload portion of the message.
	If there is no payload, "kmsg_len" is 0.

	Payload in the kevent_message is aligned to the system's
	alignment requirements, as if the ALIGN() macro were used.

	The KEVENT_MESSAGE_INITIALIZER constant exists to initialize
	a kevent_message to 0s by assignment, e.g.:

		void foo(void)
		{
			struct kevent_message kmsg =
			    KEVENT_MESSAGE_INITIALIZER;
			
			...
		}

	The KEVENT_DATA() macro returns a void * to the payload
	portion of the message.

	The KEVENT_SIZE() macro computes the total size of a
	kevent_message structure, given the payload length, suitable
	for allocating storage for the message either as an automatic
	variable (i.e. char kmsg_stor[KEVENT_SIZE(100)];) or via some
	dynamic allocation mechanism.


kevent_server:

	This structure describes an event server.  The structure has
	no public members, and users of the kevent framework should
	not access the internals of this structure.


kevent_client:

	This structure describes an event client.  The structure has
	no public members, and users of the kevent framework should
	not access the internals of this structure.


SOFTWARE INTERFACE
------------------

void	kevent_init(void);

	This function exists to initialize the kevent framework.  It
	is called by main() early in the bootstrapping process, immediately
	after the memory allocation subsystems are initialized.


int	kevent_server_create(const char *name, struct kevent_server **ksrvrpp);

	This function creates an event server.  The event type is defined by
	"name".  The handle to the server is returned in "ksrvrpp".

	If the "name" is longer than KEVENT_MAXNAMELEN, including the
	terminating NUL, this function returns EINVAL.

	If a server of type "name" already exists, this function returns
	EEXIST.

	Otherwise, the value 0 is returned, and *ksrvrpp points to a valid
	kevent_server structure.  This value is used as a handle by which
	event notifications are sent to clients.

	If a previously orphaned server of type "name" is found, that server
	will be reconnected, owned by the new owner, with all remaining
	clients.  No other special action is taken in this case.  If a
	server owner wishes to notify clients that it has reconnected the
	server, it may do so.  See kevent_server_destroy() below.


void	kevent_server_destroy(struct kevent_server *ksrvr);

	This function destroys an event server.  If the server still has
	clients bound to it, the server becomes `orphaned'.  This allows
	a detaching subsystem to notify all clients that it has gone away,
	but allows the clients to detach from the server at their leisure.
	Once the last client reference is gone, the kevent_server structure
	will be freed back to the system.


int	kevent_client_create(const char *name,
	    void (*func)(void *arg, const struct kevent_message *kmsg),
	    void *arg, struct kevent_client **kclntpp);

	This function creates an event client.  The server the client
	is to be bound to is defined by "name".  The callback function
	which will receive event notifications is defined by "func".  An
	opaque argument to pass to this function is defined by "arg".  The
	handle to the client is returned in "kclntpp".

	If the specified server does not exist, this function returns ESRCH.

	Otherwise, the value 0 is returned, and *kcnltpp points to a valid
	kevent_client structure.  This may later be used to destroy the
	client.


void	kevent_client_destroy(struct kevent_client *kclnt);

	This function destroys an event client.


void	kevent_send(struct kevent_server *ksrvr,
	    const struct kevent_message *kmsg);

	This function sends notifications of kernel events.  A
	kevent_message must be included, although its payload may
	be 0.  If a client wishes to keep a copy of the message, it
	is responsible for copying it to client-local storage.  This
	allows messages to be allocated as automatic variables by
	the sender.

	Clients which receive event notification may not block, as
	notification may occur in interrupt context.  Senders are
	responsible for blocking interrupts, as appropriate.

MISC NOTES
----------

	As a proof of concept, I have replaced "shutdownhooks" in my
	source tree with kevents.  main() creates a kevent_server called
	"shutdown", and cpu_reboot() sends a zero-payload message on this
	event server where doshutdownhooks() would have normally been
	called.  Drivers which previously registered shutdownhooks now
	create clients of the "shutdown" event server.

	-----

	It is intended that userland programs may be clients of the
	framework, as well.

	My initial inclination is to create a new protocol family,
	PF_KEVENT, which allows programs to receive atomic datagrams
	containing kevent_messages.

	My idea here is to have the following:

	struct sockaddr_kevent {
		u_char	skev_len;		/* total length */
		u_char	skev_family;		/* address family */
		char	skev_name[KEVENT_MAXNAMELEN];
						/* event type */
	};

		struct sockaddr_kevent skev;
		int s;

		...

		s = socket(PF_KEVENT, SOCK_DGRAM, 0);

		...

		skev.skev_len = sizeof(skev);
		skev.skev_len = AF_KEVENT;
		strcpy(skev.skev_name, "nfslock");

		bind(s, (struct sockaddr *)&skev, sizeof(skev));

	...and that program can now listen for "nfslock" event
	messages from the kernel.  (Note, this is the message path
	required to implement NFS locking, which is why I used it
	as an example :-)

	The act of binding a socket to an event server would cause
	that socket's kevent_pcb to create a client for that server.

	In order to hang messages off of socket buffers, we'd have to
	define a NETISR_KEVENT, because all of that stuff has to happen
	at splsoftnet.  But that is an implementation detail.

	-----

	Also, some sort of "feedback" mechanism may be desired for the
	userland -> kernel path, although I haven't thought too much about
	this yet.

        -- Jason R. Thorpe <thorpej@nas.nasa.gov>