new post: Xen - split driver, initial communication
This commit is contained in:
parent
ef9b45c981
commit
8d12e80d81
211
_posts/2016-12-20-xen-split-driver-initial-communication.md
Normal file
211
_posts/2016-12-20-xen-split-driver-initial-communication.md
Normal file
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
layout: post
|
||||
title: "Xen - split driver, initial communication"
|
||||
date: 20.12.2016 16:00
|
||||
---
|
||||
|
||||
In the [previous post](/2016/12/02/xen-a-backend-frontend-driver-example/) I explained how to initially setup a split driver
|
||||
for Xen with the backend in *dom0* and the frontend in a *domU*.
|
||||
|
||||
This time we are taking a look at the internal states each side goes through.
|
||||
Most of this code is a trimmed down version of the [Xen network driver](https://github.com/torvalds/linux/blob/bc3913a5378cd0ddefd1dfec6917cc12eb23a946/drivers/net/xen-netfront.c#L2024-L2060).
|
||||
|
||||
The full code can be found in [chapter 2](https://github.com/badboy/xen-split-driver-example/tree/master/chapter02) of the example repository.
|
||||
|
||||
## Background
|
||||
|
||||
The frontend part of the driver sits in an uprivileged *domU* and gets its input from the kernel in this virtual machine.
|
||||
Depending on its usecase it then passed on commands what to do and the data over to the backend part,
|
||||
sitting in an privileged domain such as *dom0*.
|
||||
For example in case of the network driver, the *domU* kernel generates network packets
|
||||
which are passed over to the backend,
|
||||
which is then responsible to transfering this data to the actual network card.
|
||||
|
||||
Before all of this can happen both parts need to be able to communicate with each other.
|
||||
Each part must probably set up a few things before it can do its job.
|
||||
Some of these things must be advanced in lock-step, so each part advances to its next status
|
||||
and then waits for counterpart to advance as well.
|
||||
|
||||
## The state machine
|
||||
|
||||
Internally this is all done through a state machine.
|
||||
Both sides start in the `XenbusStateInitialising` state.
|
||||
The goal is to reach `XenbusStateConnected` once fully setup.
|
||||
|
||||
If no setup is required at all, it is as easy as saying so:
|
||||
|
||||
~~~c
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
~~~
|
||||
|
||||
Of course a driver rarely has to do nothing at all.
|
||||
Instead in each intermediate state some work can be done.
|
||||
This results in a fairly large state machine on both ends, but most of it is just boilerplate.
|
||||
|
||||
This results in about 30 lines extra in the frontend and 100 lines in the backend.
|
||||
In this blog post I will focus only on a few relevant lines.
|
||||
|
||||
Both sides gain another callback function, to be notified when the other side changes its state.
|
||||
This way they can advance in lock-step.
|
||||
|
||||
~~~c
|
||||
static void mydevicefront_otherend_changed(struct xenbus_device *dev,
|
||||
enum xenbus_state backend_state)
|
||||
…
|
||||
}
|
||||
|
||||
static struct xenbus_driver mydevicefront_driver = {
|
||||
.ids = mydevicefront_ids,
|
||||
.probe = mydevicefront_probe,
|
||||
.otherend_changed = mydevicefront_otherend_changed,
|
||||
};
|
||||
~~~
|
||||
|
||||
(The backend as a similar one)
|
||||
|
||||
The passed state will tell us the new state of the other side.
|
||||
In the frontend we can simply wait for different state switches and switch over the frontend as well,
|
||||
eventually reaching `XenbusStateConnected`
|
||||
|
||||
~~~c
|
||||
switch (backend_state)
|
||||
{
|
||||
…
|
||||
case XenbusStateInitWait:
|
||||
if (dev->state != XenbusStateInitialising)
|
||||
break;
|
||||
if (frontend_connect(dev) != 0)
|
||||
break;
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
|
||||
break;
|
||||
…
|
||||
}
|
||||
~~~
|
||||
|
||||
The `frontend_connect` function should then set up everything necessary.
|
||||
|
||||
The backend has a similar function:
|
||||
|
||||
~~~c
|
||||
static void mydeviceback_otherend_changed(struct xenbus_device *dev, enum xenbus_state frontend_state)
|
||||
{
|
||||
switch (frontend_state) {
|
||||
…
|
||||
case XenbusStateConnected:
|
||||
set_backend_state(dev, XenbusStateConnected);
|
||||
break;
|
||||
…
|
||||
}
|
||||
~~~
|
||||
|
||||
This defers to yet another function, actually just boilerplate to ensure the right order of state changes:
|
||||
|
||||
|
||||
~~~c
|
||||
static void set_backend_state(struct xenbus_device *dev,
|
||||
enum xenbus_state state)
|
||||
{
|
||||
while (dev->state != state) {
|
||||
switch (dev->state) {
|
||||
…
|
||||
case XenbusStateInitWait:
|
||||
switch (state) {
|
||||
case XenbusStateConnected:
|
||||
backend_connect(dev);
|
||||
xenbus_switch_state(dev, XenbusStateConnected);
|
||||
break;
|
||||
case XenbusStateClosing:
|
||||
case XenbusStateClosed:
|
||||
xenbus_switch_state(dev, XenbusStateClosing);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
break;
|
||||
|
||||
…
|
||||
}
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
*Note: The whole state machine switching was taken from the network driver and may be reduced for other cases.*
|
||||
|
||||
Again, this calls into another function `backend_connect` where we can handle the setup.
|
||||
|
||||
For every invalid state switch it will trigger the `BUG()` macro, which crashes the module and in turn the kernel,
|
||||
but at least you know where to start.
|
||||
|
||||
Last but not least let's set the initial state in the backend:
|
||||
|
||||
~~~c
|
||||
static int mydeviceback_probe(struct xenbus_device *dev,
|
||||
const struct xenbus_device_id *id)
|
||||
{
|
||||
xenbus_switch_state(dev, XenbusStateInitialising);
|
||||
return 0;
|
||||
}
|
||||
|
||||
~~~
|
||||
|
||||
With the module code done, we need one last change: The guest domain must be able to write its state back to the XenStore.
|
||||
Thus we set the correct permissions on paths in the XenStore using:
|
||||
|
||||
~~~
|
||||
xenstore-chmod $DOM0_KEY r0 b$DOMU_ID
|
||||
xenstore-chmod $DOMU_KEY r$DOMU_ID b0
|
||||
~~~
|
||||
|
||||
The XenStore permissions are a bit unusual.
|
||||
There are 4 different modes per file: **r**ead, **w**rite or **b**oth, **n**o access.
|
||||
However, the owner of a file always has full access (**b**oth).
|
||||
|
||||
The very first permission always sets the owner and the permissions for any remaining user.
|
||||
Every additional permission overwrites this first specified permission for the given user.
|
||||
|
||||
So the above `r0 b$DOMU_ID` means:
|
||||
|
||||
* Owner is domain 0, with full rights
|
||||
* Every not-further specified domain can read the key
|
||||
* The guest domain has read and write access (**b**oth)
|
||||
|
||||
For the second line it is the other way around.
|
||||
|
||||
## Run it
|
||||
|
||||
With everything in the code, we can compile the modules and load them into the kernel.
|
||||
|
||||
~~~
|
||||
dom0# insmod mydeviceback.ko
|
||||
domU# insmod mydevicefront.ko
|
||||
d0m0# ./activate.sh 1
|
||||
~~~
|
||||
|
||||
In the kernel log output of `dom0` you should see something along the lines:
|
||||
|
||||
~~~
|
||||
Hello World!
|
||||
Probe called. We are good to go.
|
||||
Connecting the backend now.
|
||||
~~~
|
||||
|
||||
And in the log output of `domU` you should see:
|
||||
|
||||
~~~
|
||||
Hello World!
|
||||
Probe called. We are good to go.
|
||||
Connecting the frontend now.
|
||||
Other side says it is connected as well.
|
||||
~~~
|
||||
|
||||
At this point both sides are in `XenbusStateConnected` mode and can communicate.
|
||||
|
||||
As always, the full code can be found in
|
||||
[chapter 2](https://github.com/badboy/xen-split-driver-example/tree/master/chapter02) of the example repository.
|
||||
|
||||
## Up next
|
||||
|
||||
Now that we know each state to go through,
|
||||
we can set up communication through event channels.
|
||||
We take a closer look at this in the next post.
|
Loading…
Reference in a new issue