 So welcome to Low Level Center Programming and Security and Enforcement with Mara. I'm Brendan and I work for Intel in Germany, focusing on developer tools, specifically aimed at new Linux user space. And sometimes a few odd tag alongs like Zephyr. I think they call it like IoT or Fog or something. But I think we want to know what that really means. So the agenda for today is we'll go through what libmaramrider.io is. Then what we're doing with it at the moment, including our Google peripheral manager backend that we've done for Brillo. And then some of the work I've been doing on the AGL AFB bus, the Automotive Grade Linux AFB bus. I can't even remember what that stands for. So the first thing we'll do is go for a little bit about what Mara is in case. Actually, I'm hopefully none of you know. And really the acronym doesn't stand for anything, but I think the M is for monkey. And I'll go through that a little bit more. A little terminology, we talk about libmaramrider.io. Mara.io is meant to be the API standard. And libmarar refers to the Linux implementation of the Mara.io specification, although it already got written down properly. But the aim is that we have multiple Mara.io implementations for things like Zephyr. We've kind of got a Windows one. We've got a few other weird and wonderful ones. But really here we're talking about libmaram for Linux. I started it Mara a little while ago. And really the specific need was code reuse for user space IO based devices. And we did a small hack-a-thon with our congress with like the Intel Galer board. And we thought it was really cool. We gave a little C environment in the clips and it kind of was relatively easy to program on. But it turned out that you still need like 100 lines of code plus to just make an LED flash on and off. And it turns out most developers thought that was pretty acceptable. And it probably was. And we had one little library. All it did was make an i2clcd kind of work relatively easily with a few lines of code. And that was super, super like popular. So we thought well we probably need an abstraction layer to make reuse of that code so we can make more useful stuff. And that's kind of how it started. So to give you an impression that it was kind of made rapid prototyping and for making IO work as expected rather than the way it works when you get a standalone Linux dev board. And then we kind of went on a little bit further. So the aim was to have an abstraction layer with a level of flexibility which is very, very high so we can support a load of different boards. We obviously wanted to be open source licensed so we chose the MIT license because it was just simple and easy. And a lot of it has been written by Intel employees but we've accepted PRs from just about everyone on GitHub and we've collaborated with loads of SFC vendors now. So I'll talk about a little bit. And what Morale essentially does in Linux, it's doing IO that's typically reserved for the kernel and moving it into user space. And bringing essentially the R2D inner header that you see so commonly on Linux single board computers so you can use it really easily without any special magic just like you can on the microcontroller. The aim is to make really that experience as easy as it is on the micro. Obviously it's not quite perfect but we're trying to get there. We support all the common low speed IO protocols, GPIO, I2C, SPAR, UART, things like that. We're pretty agnostic to kernel versions and we support a load of different access methods. So for example for GPIO we do everything from CISFS, we support DevUIO, DevMEM for horrific boards and even GPIO by the new CARDEV interface in the kernel from 4.acranba which is way nicer and based on that we actually let you do full GPIO line access now which is much, much more efficient but we still have a failover mode to do on CISFS if you have an older kernel or an older board. We also have our own IO user space interface. Mostly it was initially done for ADCs because we typically don't suggest writing user space ADC drivers. That's typically a bit inefficient and we also do UART and emulated one wire on UART. Just last week we merged some patches from the NARO to add an ADN kernel subsystem so we can do that directly from Libra. And target audience really is monkeys so it's dumb, it's easy and it just works. That's really the main concept. We try to really emulate the easiness from the light control of the world and there's just a few things because of that we can't do. That's fine, that's part of the limitations. Like I said it's for quick prototyping and we're finding in 2017 no one really throws away a prototype anymore so we've kind of tried to adapt that into products and try to make a few things to make it actually easier to develop on. And generally our users are used to handling software at a much higher level than Linux Kernel so they may not have access or the ability to rebuild their own kernel they might be on a specific vendor kernel. They may not have the skills to rebuild a Linux kernel to start with. And we're a real world so we often have vendor specific kernels, things that aren't super clean and it's not always possible to just upgrade to the latest kernel to get the Cardev subsystem for example. And we have APIs and C++, Node.js, Java and Python and C++ is header only, Python is with some type of face work and Node.js is kind of a halfway house. We're struggling a bit with Node.js 7 support but we have 6 support and we're looking at moving from SWIG to end bind for that. And we support loads and loads of boards and the aim is to abstract the platform quirks so really support the actual board as much as possible so that it reflects the hardware data sheet from the SOC or the board vendor and it's sometimes harder than you'd believe. So straight on here's the quick and ugly CAPI demo. So you can see it's context based. Once you initialize the context, which we handle, it's a hidden pointer struct. You can pass it around and you can close it with the aptly named close function. And next to it is a little table that I got from a data sheet from the Intel Edison as you can see this is one of the kernels which is slightly more weird and it gives you an idea of kind of what we actually do in the background when you actually do this call. So when you actually go and ask for a GPIO, we basically go look at the table for you, we'll do the pinmuxing for you if it's not done nicely. We'll essentially kind of fix it up so it does the right thing. And on some platform it's actually even hard to get the data sheet so it's kind of nice and easy and we get a lot of people actually that use it simply because of that, you know. And we find a lot of manufacturers do really good SOC level IO support but then you get an ODM that turns up, they make a random board, they put a bunch of GPIOs on it that are controlled by normal means or normal ways and no mainline kernel support, of course. And it's especially common with add-on and daughter boards and SOM designs, SOC on module. I won't name anyone. And then we have RC++ API, which is our heteronian API. It's not really fancy but what we do is we change our context to be objects and all our SWIG APIs are based on this. So previously we had SWIG generate objects using type maps and this was a massive, massive mess. So to reduce the size of our wrappers we started using this and it seems to be a bit easier to maintain. We do actually have unsupported bindings for Haskell, Argo, PHP and Lua. Lua one's probably the only one that might get merged at some point but we're interested in one of those. We could have chat. And then here's our Python API. So kind of the same. We even have some Python docs that are generated so it can make weird reading. We do Python 2 and Python 3 separate binaries but they are built in the same sources. And then some more Python. And you can see that most of our calls are actually synchronous including for JavaScript, which means that we don't have that many fans. And it's because we're really time sensitive. So when we benchmarked it we made some asynchronous calls like some really basic stuff and we found that actually it was really, really slow to make a thread and destroy it just for one simple IO operation. And at the end of the day most IO operations require you to really get the result back and you kind of want it as soon as possible to get on the next thing. So we found that it didn't really make that much sense. But we're still curious to see why it was really so bad. So we did a few tests with some say non-trigger stuff and it was still pretty slow. So we kind of recommend now making the sensor read completely async but not the IO calls themselves. Obviously some people in the JS community disagree with us. And finding one of the things that we would get asked is why Unified API was the point and I have a cool demo that hopefully will convince you. So we have this cool little JavaScript-based web-based actually Mara implementation which shows you the JS implementation of Mara and if I can type correctly which is a bit of an ask we can basically make a GPIO and we can write to it. It turns on. So that's a fun way of showing why Unified API is kind of handy. We can basically make it run on a web browser. We can make it run on any hardware and we have, let's say, multiple implementations of it. But really the real big reason for doing this is, excuse my looking at the screen, is that on top of this we can basically put a bunch of sensors. So on top of our API that's kind of unified we managed to basically load a different sense libraries on top and what we did is we created UPM which is useful plugins from Mara and we made around 400 sensors that would basically be loaded on top of Mara. And we're constantly adding more and we take poor requests again. This is again fully open source and you can basically go and look up any of the sensors that you like and try and add it to your project. So for example my favorite temperature sensor is the PMP280 and everyone should have a favorite temperature sensor. I think it's like a basic. And basically for this you can get, sometimes you get a nice picture, we have a description, we have a link to the data sheet, we have all our API nodes, some key specs. And then down at the bottom we have basically examples of making it run in any language. If you go look in there, then always perfect. I mean they are all to generate, it's a little bit nice. But the idea is you can get the basic idea of how you would run that sensor. And again we try and make this as easy as possible. So when you add a sensor and you give us a poor request from the documentation that you give us, this is essentially all to generate it. It's not always super, super nice. And then we give you a few little examples on some platforms that might be something a little bit weird about that sensor. So you need to make sure that something is set up right. Let's go back to that was the EPM slide. So hopefully at this point you're thinking, well this is cool, how do I add my platform? And what do you support? So here it is, a bunch of boards that we support, random pictures, no real order. And what that means by the boards supported is that we have the pins mapped and it all works out of the box. You get some docs, we've tested it a little bit. And hopefully you'll notice it's much bigger than Intel. We support boards from all around the place. We've got 96 boards from the Naro, well the 96 board spec, mostly the Dragon 410C I think is the one they're using. MediaTek just added two new boards just recently. Google did the BeagleBone Green which I think is on there. Fitek have their BeagleBone clones. I think they're slightly different. There's a few little patches for them. And we explore all the Raspberry Pi versions, I can't remember how many versions, loads of them. And like for example the whole Miniboard series and stuff supported. And we have a bunch of different ways to add platforms to Libra. So the first method is what we call the raw platform. And essentially it's not writing a platform. It's similar to how you use GPIO libs. We don't actually do any cleverness of mapping or try to work out what you have. You just initialize pin numbers as they are in Cephes. Or in your kernel that's booted. And it's pretty poor because one of the problems we get is for example you refer to Device 2C1, but really when it boots it can be randomly assigned. You can't actually guarantee that Device 2C1 is going to be your device unless you answer special stuff. And based on firmware this can change. So internally we use this API, but typically we don't advise it for anything than just development and playing around. The main reason people use this is for the C platform config files. And this is the main way we add boards. And really we think it's relatively easy. We've gotten a bunch of PRs with absolutely no help from middle of nowhere to do this. So it doesn't seem too difficult. We sort them by arch type. The Mara library tries to be portable across Devboard. So for example you can run on a middleboard, copy over the binary and run it on a cherry trail base up. We do actually separate the architectures because there's no point in putting the ARM support boards with the x86 ones. You're not going to be able to copy the binary anyway. And you can compile it for a specific board to reduce binary size. We've got loads of different compile options that let you do that. The cool thing about this is you're able to override some of the Mara specific functionality so we actually have our testing board, which is the mock board which overrides essentially everything the board does and triggers a bunch of tests rather than actually real IO operations. We do this for supporting boards that are for Mara based. We support the D2XX, FTTI driver, things like that. And then our last way of doing things is for our JSON platform API. And this lets you add a relatively simple board. This assumes that the kernel mostly works on your board. And essentially you're able to define your board in a simple JSON file and load it at runtime or set an environment variable to reload it up. We've done this actually for Intel AeroBoards, which is those little drones that Intel has. And I think they don't use it for the nav. I hope not. But for example, things like the mid-board turbo will load like this really easily and you don't need to actually do any actual coding inside Mara. So that's quite cool. And then we come to the weird stuff. So, excuse me as I change gears a little bit. So the reason for this talk is really how do we apply Mara to instead of doing direct kernel access, which obviously requires us to have permissions that are a little bit dangerous. We, you know, go in. And some boards will end up hitting DevMen, which is obviously a big no-no. But DevUIO even surface can be a little bit tricky. And our first attempt to basically solve this problem was to use a demon called Imra, which would basically load on boot time and try and set the permissions for the various IOs with the permission that the users that you would set that would use these IOs later. And this kind of worked relatively well for simple platforms, but it didn't really scale. We had a lot of problems on more complex platforms and boxes where it wouldn't really work properly. Or it required real intricate knowledge of how it worked. So then we worked with Google on Android Things. And they had an interesting problem because they created this thing called Peripheral Manager, or PIO, which is really basically a demon and you request IO operations to be done with it through the Android binder. And it checks that you're allowed to basically do this access, that you have permission to this bus or this specific device. But they didn't really have any sensors on it. And like a lot of the new APIs, you know, it was different. It was new and they basically didn't really have much. I think they had two sensors, examples on this. And we did a dev kit with them, and we thought, well, you know, instead of porting all of our sensors, which is what they asked us to do initially, we thought that's a lot of work. So we'll do the lazy, you know, halfway route. We'll port our base API and just make a backend that actually calls the Peripheral Manager instead of actually calling the direct kernel functionality. And then we'll let PIO actually do that work. And actually the PIO API, luckily, was very, very similar to ours. I dare say it's a copy. And we made what we call P-MRA. We like to prefix things in front of MRA. We have, like, Z-MRA, W-MRA, D-MRA. Yeah, it's bad jokes. But essentially, what we did is that instead of calling dev I2CN, suddenly you were actually asking for the Peripheral Manager for I squared C plus N and then doing operations based on it through that. And we use our Java bindings, and that's actually when we made our Java bindings way better than they were before. And we added a few different little things, like we added a MRA in it, IO call, that takes a string argument and returns you a hidden context so you can do some slightly more dynamic stuff. And then because we increased and made the MRA's backend much better, we were actually able to merge all that work back into the mainline MRA. So you can now basically compile a Android Things MRA. And one thing to notice, when we talk about backends, we have some backends that are unique. So like MRA, the Peripheral Manager one, or even the AFB one. And we have others that are treated as sub-platforms. So you can actually have two backends running in MRA at the same time. So when we run a Firmata board, typically we consider that an add-on board. So you're able to do IO directly on your SPC and then do IO on the Firmata board by basically adding an offset to your IO numbers. So when you ask for bus 512, you'll actually get the USB-controlled Firmata board or Bluetooth-enabled board instead of your native board. But for Peripheral Manager, obviously you only have one board, so that's really the native board. And we start the numbering just like we do in Peripheral Manager. And so you can see that's kind of how the stack ends up looking. You get your application in Java, which calls MRA Java. And then it calls LibMRA with the Peripheral MRA back-end, which calls and calls the Android Binder, requests, basically access to things. And the Peripheral Manager essentially does the operation on the kernel directly. So we don't actually ever touch the kernel in this case. And so now we're going to move on to the AFB part of the presentation, which is the whole reason, really. I did this. And so the base of it, it's probably a whole presentation. I hope some of you guys watch the AGL presentation next door. But it's a way to connect applications to services promoted by the Automotive Grade Linux group. And essentially the way it works is you have a security context, which is insured by SMAC, and you run an application connected to a local binder. And the binder runs in the same security context as your application. And the binder lets you basically load dynamic libraries with a few magic hooks, which essentially load a service that you can call from your application. And you can actually do this either on the same security context, but actually you can do it in a different security context, and you still call your local binder, but it's actually going to call a service that's running in a different application, in a different context. And you can do this over the network, you can do this completely locally, and it's completely transparent to the calling application. So the application always talks to its local binder, which exposes the bindings it's got access to. And essentially the binders can be connected via debars, websockets, or other connection types. But it doesn't actually matter who is part of the calling application. And what's really cool about AFB is it doesn't actually require AGL, but it is a component of it. So you can install it on basically anything that's running Linux, which has a non-ancient json-c. There's some packages. You can do it in common distros out of the box, but it's basically a few minutes to compile. It's really, really easy. It's just down in Cmate, nothing special. And with the binder comes a few like utils and demo tools that are quite nice, but really what we're interested in is the libafbwsc, and I'll have to explain that naming. But it's what Mara uses to contact its local binder. So it's really a pretty easy API. And essentially you basically pass a json-c, a json message, which goes through json-c, and essentially you're off to calling a function in your binding. You can do this in C, which is what I did, but you can apparently do it from the lower. It's really easy. I haven't looked. So if you have a little look at how it works. So that's the simple picture of how we basically plug in Mara to AFB. And the kind of cool thing is you want two Maras, which makes me particularly happy. So the way it works, we have one Mara with the AFB back end and one Mara that runs normally. And here we put them all in the same security context to make it a little bit easier, but I'll show you the more complex one afterwards. But the idea is that the application calls the Mara with the back end, with the AFB back end. The AFB back end talks to the actual binder with the binding loaded. And actually the binding is linked to Lib Mara, the normal one, and by normal I mean the one with the actual hardware platform support. Your AFB Mara is compiled just for the AFB platform. So you do have to do some magic to have two libraries. And the way it works is the permissions are entirely handled by AFB, by the binder. So you can basically say, well, this application only has access to run this high-squared C on this address. So it's really quite perfect. Well, two addresses, seven bit. But really you can only send and receive messages that were intended for you, and you can't access the bus directly anymore because it's hidden by essentially AFB. We're not able to access the local IO, and that's what's really cool. So let me show you the more complex picture which actually does some of this. And this is, I try to make this kind of funky. So hopefully that shows a little bit more on talking about, but in this diagram, which actually the way I developed it, we have a thermometer board. So we're not actually hitting the list kind of directly. We're actually hitting a thermometer board that's connected via UART. And what we're doing is talking from our application to an I-squared-C UPM sensor, like a BMP280. And then that's talking to our Libra with our AFB back end, which is making a binder call for the AFMRI binding. Once we do that, we enter that binder security context so suddenly we're able to actually make a UART call before we don't have access to the UART of our kernel. And suddenly from our thermometer enabled Libra, we're able to hit the UART and go and call our thermometer board. So actually in this, it's relatively complex because we're actually going for an I2C MRAR call from UPM into the binder, which is then transforming it into a JSON message, passing it into two verbs that have been called on the AFMRI binding, which then converts it to a MIDI message to be passed via UART and Firmata is a MIDI kind of base protocol which will actually then do the IO operation. So you can see it's relatively non-trivial, but it all works actually relatively easily because we already have that part of the stack and all we need to do is basically transform the MRAR calls into JSON verb, well, functions, AFB calls verbs essentially function calls with the right arguments to the binding which actually calls the MRAR calls directly. And so our AFB bug end basically works by extending the MRAR API while overriding some of the functionality. So every LibRA call actually has the possibility for overrides on its normal behavior. So we usually did this because of odd platforms like the X1000 quark platform or non-sander kernels like some of the RPY stuff, or because we weren't talking to Linux directly, like Firmata base platforms, Z2XX, peripheral manager, and the AGL platform is just an example of that. So we compile it with the build art AFB and this triggers all the calls to go to the AFB bus rather than directly to the kernel. And once we do that, we override all the platform functionality so we no longer have basically a standard platform, we have an AGL style platform. And I use AGL and AFB in, yeah, you should probably fix that. But basically when we ask for DeFi to CNN, we really go off and we make a call via AFBWSJ1 call S, which is asynchronous and that's a bit useless for us because we basically need to know the response right away, so we basically go and wait a little bit, it's a bit inefficient, we point it to fix that. But for example, there are things that for the platform name, for example, we actually don't need permission for that one, so we can go grab that directly. So there are some things that are done directly because we can do this directly from the binder and there are some things we actually go and query the platform for. But the cool thing as you can see now is that we can interconnect different applications through the binder so that we can start caching results for AIO calls, we can start caching those results if we're asking for too much, things like that. And so the AFMRA binding is typically linked against the static libMRA, so then we can basically keep that easily available just for that binding and then all the other applications are linked against the dynamic libMRA, which is available on the system. And it's obviously that version will be linked to the hardware specifically, so it's really basically to treat the hardware rather than the AFB bus. All the AFB bus actually functionality is in the binding itself rather than in the libMRA. So it can get a little bit confusing because you essentially have two versions of the library, which means this log is a little bit of a mess, but that's part of what we'll fix in the future. And now we go on to the future and what we're doing. So there's a bunch of stuff left to do. I didn't do quite as much as I wanted to before this presentation, so currently the AGL platform in MRA has no real knowledge of what's supported on the platform. We have no basic introspection, which we normally do on a platform, so we're able to query what we have and what we can do. Currently it's kind of, let's say, an ask and forget mode. So you'll get refused if you're not able to, of course, but we basically don't have the ability to tell you what you can do. So it's pretty nice. And our binding is pretty simplistic. It's based on the version 2 of the AFB binder, but it's pretty simplistic in that we only have two verbs. We have dev init and we have a command, which is basically a MRA command against an initialized context. And I think in the future we'll try and clean that up so you can actually use the binding directly and actually use it without another live MRA if you wanted to prove that. And currently you can, but it's just a little bit ugly and we haven't made any effort to make that nice. And one of the other things that currently are that the course of AFB are all asynchronous, and we basically have to lock on that and that's a little bit nasty, so we've got a bit of a hack to do that nice and efficiently, but we'll probably just write a new AFB call that lets us do that directly. It'll just be easier. The first thing is that we essentially have the I2C function now if you're working, but we don't have any of the other MRA IO protocols, and this is more because I wanted to make one work nicely, get some feedback, see where it was going, and as soon as we have that working nicely we'll move on to the other ones. And some of the other protocols, I think the I2C one is probably one of the most widely used that we have, and it's probably one of the more complex ones anyway, so we'll see where that goes from there. Hopefully the ones that fought long and effectively easily won me when we have that. And then I've got some further reading in docs if you want to look at more things. So it's all on our GitHub page. We have MRA, we have the MRA binding which lives in a slightly different place. We also have the UPM website, and UPM is on the same GitHub, you just put slash UPM and you end up on there. We're fairly active on GitHub issues if you have any problems or you want to play with it. There's a docs folder in MRA that explains a little bit of the compilation and how to do that. And we obviously have a free node I'll see channel which I'm usually on. A mailing list. GitHub issues seems to be where most people go. But yeah, that's it. Thanks a lot for listening and any questions? I don't have a microphone. I apologize. Yeah, I can hear. We've already tried to be real-time because we're, let's say, way too far up the stack to really even pretend. We're more hitting stuff that's not real-time critical, so there's plenty of sensor readings, for example, which aren't necessarily real-time, you need an answer. Let's say relatively quickly, but exactly when is not necessarily required. Yeah, if you want real-time stuff, you're going to need to be way deeper down. I don't think this is going to solve your need. We are not deterministic. I mean, that's part of the aim, right? It's meant more at prototyping and doing fast dev, not rebuilding the kernel, doing things quickly, and then if you need to move further down, then you do need to be deterministic, then you need to move down the kernel. That's partly why we have the IIO stuff. So we're able to do a user space binding on top of an IIO kernel sensor, and I guess that could be relatively deterministic, depending on how you compile your kernel. We have done that for certain platforms that want that, but even then, when you're calling that from user space, it's deterministic. So it depends on which platform you look at. There are definitely some when we're very hard-coded. There are some when we go and look at the PCIIDs for stuff. We'd have to have a chat about which ones you looked at and what you thought was bad, but there's some magic that we do, for example, supporting different kernel versions, like, for example, on the middle-board max when the kernel changed the max NRGPIO value. So basically, if we wanted to support two different kernels, we have to basically do a bit of magic. We can't actually get that value from the kernel, so we have to do a bit of magic. There are a few little things that we've not managed to get. But, yeah, some of the platform support is not always perfect, especially I think the Raspberry Pi one comes to mind is the one that's fairly nasty. But you're more than welcome to fire the bug if you see something that you think is nasty. So we actually have support for both. Although it's still in the branch, so you have to go look for it. But we have SysFS, we have CharDev, and then some platforms will use DevUIO if that's something that they specified. But, yeah, we are looking to merge the CharDev stuff really soon. But, yeah, that's only going to work on newer kernels, obviously. And that's still difficult on a lot of SBCs to run brand new kernels. Okay, well, thank you very much.