 Hi, welcome to my talk about USB support in the fire doors. My name is Johan Fischer. I'm an engineer for Nordic Semiconductor, and contributing to the fire since 2016. And I'm the maintainer of USB subsystem. I will give a short introduction about current USB support and slide to differences to new USB support and give you a short update about the current state of USB development in Sapphire. I found that on the Sapphire documentation page at the beginning somewhere at the sentence that Sapphire offers a large and ever-growing number of features. Like Bluetooth network stack, thread, mod pass, USB and so on. And some of this feature depends on other features. Like open thread depends on network stack and some parts of Bluetooth, for example, if you use host controller interface, a sample depends if you connect us over UR, it depends on driver subsystem if you use that with USB, it obviously depends on USB. And the thing is that USB support in Sapphire was not written from scratch for Sapphire subsystem. The core was imported and it was over time shaped in parts by the project needs. And some of these things as a part of USB depends on other features like CDC-SM, the user interface for CDC-SM is UR driver API. So that depends on UR. And about two years ago we decided to start with new design. With new USB device stack because we are not that happy with that we have in the tree with many issues. And yeah, it grows over time and currently we started, actually we started USB device stack reward but now I call it rework of USB support. Yeah, because that also will include host support and a few other things. But let's take a look how it is nowadays how to enable USB device support in Sapphire. That's very simple, you just have to call USB enable and then it works out of the box, probably. Yeah, you can pass a parameter. Simply it's null but you can pass a callback and that will notify you about state changes in USB stack like if device get configured you will notify about that. There's some drawbacks about that because depending on the driver, it really depends on the driver, it will be executed in ISI context, you need to be careful about that and that will stay with us I think the next few months. But that's only one part how to enable USB in Sapphire. There's another part how to configure USB device support in Sapphire. That's actually, you have to do that to build time. You need to enable in cloud configurations to enable device stack to configure your vendor ID and product ID. You shouldn't use the ZFIS default vendor ID in new products. You have to configure manufacturer string, device product string, serial number. We usually generate it using hardware ID in ZFIS at runtime so it's just a placeholder and you also have to configure if the device is self-powered or not maximum power and finally to enable a class of function you would like to use like in this example that's Lubeck that just offers few endpoints for testing, it's nothing special. But you can see a few issues with that. You can change this options at runtime. We could add an IPI for that but we decided to start and that I think you would have just one instance of USB device. Usually it's enough. It's not that often that you would have multiple devices on your board, USB devices. But there you use a case for that too. Take as a look under the hood what happens. Here you enable USB device Lubeck. There are a lot of, not a lot but there are a few macros in the class code that will place kind of redundant structures in the RAM that will be used by device tech like USB configuration data and inside this structure there is also endpoint configuration data. Part of that is redundant because this data is already available in function descriptor. We have this information twice. At the runtime if something changes in the stack like alternative is like that we have to update anything on to look at different structures. That's a mess. So in the new stack that will be different. Let's do the same what we did with current support with the new stack. That is what you see here is not really settled. It can change but it will be like that because in GitHub you can find these requirements or issues as feedback from users but they would like to see how the stack works and that's the product of that. So you can see there's a macro to define. So the new stack supports multiple configuration. The current one it's fixed to support just one configuration. So here you can define multiple configurations and using the cell pro macro you can also just select the flex like self-powered or if that configuration supports remote wake up into maximum power. Then it's mandatory. Let's do define one configuration. The descriptors are like language descriptors are optional but that is nice to have. So they help markers to define language descriptor and finally string descriptors for manufacturer products and certain number. Here again the certain number if the platform supports hardware information the certain number will be overwritten at runtime. So we use the one from the board. And finally we have to define device context. That's the USB device define macro. This will create a structure that the stack will use as context. You need at least one. But you can have multiple device instances. But if that assigned to a controller like here for default as if I UDC controller zero you can enable only one instance. You can shut down that and enable another one. That is possible. So you can have two instances with device context and descriptor that have different product or vendor IDs and different strings. But you can also update the strings at runtime. You don't have to do that but everything is possible. You have to do the choice. And then the configuration side tastes not that much to configure in config options. You just need to enable device stack next in that case and to enable USB again. But before that as we do that to run time we need to register descriptors and configuration and finally enable USB. The first step is to add a descriptor to USB context. Manufacturer, product and setting number and finally to add a configuration. It's like device descriptor is built. First you have device descriptor and configuration descriptor and so on. After that you can initialize and enable USB support and attach the device to a host and our device will probably work. But the host, if you use that on Linux kernel, for example, will complain and taste no interfaces because we didn't add any interfaces to our context. One thing as important here that we have this... It looks unnecessary to call USB need and then USB enable but the reason for that is like after USB, call USB need the controller is initialized but it's not recognizable by the host. For example, you have battery charging controller to give the controller time to initialize or to detect if it's connected to a host or a charging device. It means after ENU you can get notification from the controller that if VBus is present or not and finally if you call USB enable the host will see your device and start to enumerate. I recommended out USB D register class In the previous example for the current stack I used to loop it but here we can define or write our own class of function in few slides. Finally, after this presentation you can write our own USB class of function for a new USB device support. What we need at first place is the interface descriptor and that's just a few lines of this. I don't have enough place so you can see just the definitions for interface zero and can see that it's alternative interface as it contains two end points and interface zero, the end points would be zero. This configuration is if you design a USB device this kind of configuration is very useful because it clearly describes in which configuration the device or your class is ready to transmit or receive the data and here if the host selects the alternative settings one that can be used in the class to start or to prepare for the transfer. The class API looks like if you're familiar with the fire and with device API or device driver that looks very familiar but it's much simpler and I couldn't use the device API here because it was too heavy and so device API is missing not for this list implementation so I wouldn't need another structure anyway. But USB device class API is very simple. There are only two, three interfaces mandatory like request one, any request to the endpoint will ends in this function. There is update with notifies if new interface is selected at the end of if the host calls set interface finally this function will be called notify the class that another interface was selected and it's an intelligent function. And finally you have to use USB device defined class macro to place your structure in the variable section. So finally you have access to your class from everywhere in ZFIA at run time and there is example in the shell how to access that using in that case it would be a string foo. And short example how to submit a transfer as I explained before you can select new interface and it will be notified over the interface update and here we can start for the completeness one actually has to check what alternative is selected and start transfer only if the alternative one is selected. But here it's just as example you can start and transfer after the notification and on the right you can see Python code for this class how to read from there or to write to endpoint one in that case and in the console you will finally see the hex dump of the data. You can go step by step actually and you should able to write open function or class for new USB device stack. And finally over you what APIs we what new APIs we would have in USB device stack it's again starting from the top it's device stack API that if you are user of the USB device stack or of USB device stack that's the API we will probably use or if you would like to change vendor ID or product ID at runtime that is the API to use and that will be defined in includes a fire USB, USBD header. If you are designer of USB class or function then you have to look at USBD class API that's also defined in the same header but use another namespace. And below if you are driver designer or you would like to port a driver from current USB device support or driver API to new one that is the UDC driver API it's also the namespace for that is UDC and you can find that in includes a fire driver USB UDC header. You can see that the driver between the driver and the USB device stack there's a thin common layer and that also takes care what is common in all the drivers. You can also see in current stack things like to check if the endpoint is enabled or not in some use cases or the device, the driver self controller is enabled or initialized and so on. As a locking is done in this common layer you don't have to repeat all this code in your own driver. We have we'll go over to the UDC drivers we have support for multiple drivers like we have support for multiple device instance there's support for multiple drivers on driver instance if that's supported by your platform like it would be not the case for example on Nordic NRF USBD because there's always one instance of the driver but you can't attach for example if you would have support for external device you get a touch of USB would have two drivers or devices. There's a single asynchronous platform to include transfers in the driver if you compare that with the current driver there's no direct right right access to endpoint buffer for each endpoint there's a queue that will take the request and after the request is finished there's a single API to put it to an upper layer so we use net buffers for endpoint transfers there are few drawbacks we'll discuss that later and hopefully fix that I already described the common layer and we have different implementations for NRF USBD controller on Nordic devices Kinetius USB, FS, OTG and devices like Freedinboard 64, Key Freedinboard Key 25 and 22 and so on and the new one is virtual controller and if you are interested to port a new driver to a new API or a new to port an existing or a new UDC driver then please use one of the existing drivers as reference but after you finish that please rewrite every single line just to make sure that you don't repeat the errors the errors in the code I for example made or someone else use the shell sample as application on device there are samples such as USB shell sample if you flash that to device it will not immediately enumerate the device you have to enable, you need to do the steps in the sample one by one like you have to enable the device and after that was enumerated there is a command to start vendor specific control request to verify that control transfer works without issues and to verify bulk usually if you get two things working then you are fine also one thing is to verify endpoint HALT either for controller point is using features or no for the either using on the host site using features they feature HALT and endpoint or from the shell the new as a working on is USB host controller API it's very similar to device controller API but it uses not uses net buffs directly but in containers it is always depending on the art of transfers there are at least one or more net buffs in this container and for example for control transfers it contains three of them like one each one for two or three depending on the control transfer like for setup control and status states stage of of the transfer of the control transfer there is also a common layer between driver and host as helper country we have in the branch implementation for the max 3421 controller you can connect over SPI to any devices actually and for virtual controller and as host support is in at the beginning it's very simple initial but initially driven by needs for testing for test device support and is that is very similar in structure to a new USB device support the stake development I hope it will accelerate the next releases but we are very busy with device part and finally it should ideally map the device support like if we have CDC-SAM code on device side ideally would have CDC-SAM code on the host side to build as a for the testing and new and hot especially for the testing is the virtual bus and virtual controllers that was actually motivated by by the top level testing on the hardware so internally at Nordic we have to setup with max device that we can attach to our boards and do some testing related to usually chapter 9 like device framework to verify the stake but as other function like suspend on resume the device test remote wake up and so on but testing on the hardware is slow and expensive because slow is always expensive it takes time and the other motivation was to implement better USB IP server support we actually have one in the tree it's called USB DC native POSIX but it's not correct name because it doesn't really depends on POSIX it uses USB IP and expose the function I started to rework that few times but finally lost the poor request and decided to dispatch because with that code we can export the real USB host controller over Ethernet for example but also virtual part and use that on POSIX for development for example both of that as a native virtual bus no controller emulates a real bus or hardware it's not an emulator it's much simpler it connects the host as a USB host in device below the common layer of UDC and UDC driver API as shown in previous slides and there are overlays in the shell sample and that actually describes how that works on higher level this part was not approved by device ministry so it's that may change because before it gets in but you can see here the devices are child nodes of the controller and that allows me to obtain the name of the host node using device tree and that is very simple I don't need anything special for that just device tree and virtual bus support bus support is actually this virtual bus support is closer to real bus as USB bus is because in this virtual bus from the host controller you can advertise the events to all devices connected to this controller so it works like a bus like D bus for example but the responses are directed to the host only and that has some issues because on USB you can you don't have really a bus there to connect to devices for example like here after the reset both of these devices are in default state with address 0 you don't have that situation on the real USB because there is a hub in between and here we just need to care that only one device is initialized and after that it's enumerated we can initialize another one and start with testing like that so it's kind of walk around but yeah yeah it works yeah and usage examples for virtual bus and virtual controller is like as a device framework test that's I think the big one to test all the requests and like example here that interface request it's it's not specified in default state and anything but it's not specified we will trade that as a error that's actually the state in device state that's anything is not specified it's an error and if device is under state then the device stack must respond with error and if that is in configured state the device may that test request may be valid or not depending on the interfaces for example if we look back at the code where we have two interfaces one is alternative then the request to I don't know request five is not valid on these cases we can test that with virtual support very quickly and daily or on any put request whatever I have and the other user cases is class implementation testing on both sites so if we for example cdcsm on device site cdcsm on host site we can test it as a daily and very quickly and finally developments set up for native project platform yeah that is from my side carless just to clarify the virtual controller it's a single instance of Zephyr where you where the host support and the device support to each other via RAM directly yes so it doesn't work with say if you were mentioning native projects one example then you would build one Zephyr.exe that Zephyr.exe would contain both host support and device support but it's not like the USB IP where you build the device support and then it talks to the little USB stack via IP that's another story right? so in the case of when you use that set up you have to enable host and device support always yes test example test in the sample is overlay for cdcsm3 and if you want build that for chemo cdcsm3 if you build the sample for cdcsm3 to enable device support and host support and use the same overlay and finally in first time I can it doesn't, yeah it's broken, it's always that's weird yeah that's I hoped I can we have time so another question so you initially mentioned something that having multiple configurations and you can shut down one of them and switch to the other one? yes so you know that's not doable in the existing interface right? in the existing existing the USB device stack it takes only one it's always hard coded to one configuration and you can change that it's statically built and you could actually add ROM times from a working USB device into EFU mode yeah that's special use case but yeah actually we can shut down, we can have multiple there are too many configurations possibilities you can you can have multiple instances of a class like cdcsm0 cdcsm1 and assign cdcsm0 to a configuration to first configuration cdcsm to a second configuration and also Ethernet support as a two second configuration have these two configurations assigned to a device instance and yeah you can shut down and change that again you can unregister the class from a configuration and unregister it to another one like that yeah it uses ROM it's like current stack anything is placed in the ROM so we invest a lot of ROM not that much but here the case with new USB device support but I think we can improve how we handle the strings because it's always fixed at runtime but we can also put the strings we try it encoding in the flash and but the descriptors and the interface and so on that's all still in ROM like in the first stack yeah and we for example in device context is the device descriptor and as a pointer to the device itself so you can actually you can change the device and use the context and so I think it started with the issue that we can we can run time or to build time to list or to obtain what compatible devices we have or we support so user has selected but I think that's better approach to have this context in the user area on the user ROM it can be placed as a bare and just needed both so if you have for example you have an interface with two endpoints one for bulk bulk endpoints one for in one for out it would be in hex 81 and 01 like for in and out yeah and you would have that in multiple classes and at the start during initialization or class assignments to the stack it will fix that for you like in the current approach it goes over all the interfaces and points and renumerates assigned new addresses based on other uncontrollable capabilities so if you have multiple instances of you interface then the first one we start with 81 01 the second one 8202 and so on but there are requests to have an option to disable that so I will add that later there are use cases for simple devices with just one interface that doesn't have this composite configuration that on the whole side people use drivers with hard coded endpoint addresses so they don't use descriptors of the device to discover what endpoints are there what use hard coded endpoints and in some situations with some controllers because we reassigned the addresses it wouldn't work anymore with driver you could switch back to the sample application with the news stack in the slides sorry when you use those macros to describe this one yeah exactly so these macros are typically put in a header file or in the main c file where what's the structure of using these macros in the code that's yeah but where exactly in the code like in the new samples where have you placed them in the main yeah and do they have to be in order like if you start for the first configuration then comes no no no so how do you refer to a particular configuration when you are how do you know that I'm adding to full config what line so if I define let's say two USBD configuration define full config that's my first configuration assume I have two configurations how do I tell that a particular interface the first one you add using USBD it configuration will be first index and the second and so on I had a version where you a user could assign the number but that doesn't work good because you if you start with not with zero example yeah then it would not work the host will not emirate at least linux will say no the first one has to be and that cannot be for example two or three no way so that was not in my opinion not that good so I changed I have to actually yeah to the first configuration yeah you have to care to take care what to what configuration you add then I guess I partially macros and partially functions why not do that part that you have in macros why not do it in functions as well if you're going to do it anyway so why have the API split between macros and functions why not have just like an API call string define and call it you know and then in the macro test yeah but there's not only the string descriptor structure if you use the USBD descriptor string define it's not just device not only the device string descriptor structure it's also there's a management structure with CIS CIS node it's instances more not just one structure but few of them on their use there's like we can have additional descriptors on depending on the index you have to provide the index and in the request processing that will look for this index and if it wouldn't find that it will reply with error but every single thing every single structure that's declared by those macros stays in RAM yes okay so but that's handled by the stack you will not see that that's in behind this that is but after there is it the device controller would notify the upper layer there is it the upper layer would try to enqueue all endpoint buffers and bring the stack in kind of initial position what is what is missing here you can see there is no callback for notifications so that's intentional because we have two approaches in ZFIA two different projects to add kind of T-Bus they are kind of like T-Bus support in ZFIA one is the C-Bus pull request and the other one is event manager from Nordic and one of these approaches 294 the user there will be a channel to subscribe to usp notifications to general notification from the stack from specific context to get notified if the host raises it like V-Bus is connected or V-Bus is present for you charging device device is configured if you need to at operation mode of your device and we can also have class specific notifications like we have this it's actually work around for CDCM on some Arduino devices to put it in bootloader mode if the bow trade is changed I don't like that in the end it's very heky because it's called from class context and having this kind of event manager or Z-Bus we would able to submit notification from the classes like for the CDCM some parameter changed you can subscribe to this channel and will be notified yeah I forget to mention that but that's one of them then thank you for your attention