 Hi, welcome to my talk about new USB device and clear CPI. My name is Johan Fischer. I'm an ND engineer for Nordic Semiconductor and the USP Maintainer for Safe Fire project. We are working on new USB support for a while. And one part of this is new USB device support. And the new stack was merged last year with CDCS-M implementation. And this year, we also moved or ported a few classes to the new device support. So the current state is that we have three, the current state for the new device support is that we have three UDC drivers. It's for NF USB D controller for the Kinects controller used on key 64 or K22 devices. We have three class implementations for Bluetooth host control, controller interface, USB transport layer, CDCS-M, CDCS-M, and USB mass storage class. The mass storage class was completely written by Thomas. And it's actually in very good state now if we compare that with previous implementation. And this is something to do. We have a few classes that needs to be ported. So Thomas is working on an audio class. I started HID porting. And then what remains is USB review. And in this, we probably scraped because we have three classes for networking. In the current implementation, it's CDC, E-C-M, E-M, and NDS. And I think it would be a good solution to replace NDS with CDCS-M class. And our mass storage is from here. Yeah, that's a typo in the slide. So what is it about this talk? Let's look at this function example. It's a device that implements CDCS-M class and provides this serial functionality to the host. It's on the server side, it's what applications see as a virtual UI controller or virtual UI interface provided by our CDC implementation. And the logical connection to the host is the serial connection. If you look deeper, it's on the USB framework. The lower connection is control pipe and interrupt in pipe. And that is one line is missing, two, four, and parking and out pipes. And actual connection is USB. And USB class API is about this interface in the red circle. And it's a generic part that each class implementation has to use. So the class implementation provides some functionality to the host and the communication flow through control, interrupts, and barc end pipes, at least in our example here. But this CDCS-M implementation uses resources provided by control driver and by device support, like endpoints. And at the interface, each of these, the functionality of how the host know how the pipes are provided or what endpoints are used, it's described in interface descriptor and endpoint descriptors. So that's an example. It's one fancy interface descriptor. It's not CDCS-M, but it looks like, if you look at code, it looks like CDCS-M interface descriptor. So then, so all the information is inside this interface descriptor. You can see there's a number of endpoints. There's a first interface. It doesn't have any endpoints. It's set to zero. And then there's one alternative alternate for the first interface, where the number of endpoints is set to two. And then two endpoint descriptors follows. At the end, there is a nildescriptor. That is like it says terminator or nildescriptor. So if the device tag passes, this part of the descriptor is no, it marks the end of this specific interface descriptor. And so every implementation for the class requires at least one interface descriptor. Endpoints are optional. For example, USBDVU class doesn't need any endpoints. They're just interfaces that are described, actually, at this firmware layout. So all the information that the host class and the device tag needs to know about specific class implementation is provided by the interface descriptors. And interface descriptors are always a part of a configuration descriptor. And so interfaces are part of a configuration. And only one configuration can be active at a time. With the new stack, we support multiple configurations. It's an improvement, actually. And as it was not enough, we also support multiple device instances. The reason for that is that we decided to support multiple device controllers. So there are not that many SOCs or SOCs, actually, that have multiple device controllers. But we have a few of them in the tree, from inexperience, this, for example. And with new stack, it is possible to use multiple device controllers. And each of these of the controller requires our own device instance. And from that, each class instance needs to be assigned to a configuration. And a device instance can have multiple configurations that the host can select. And everything above the class, but its class communicator, from the class point of view, is application. Even if it's a virtual UART for the class virtual UART, or everything that connects to this virtual UART interface, it's an application the same for the emulated network device. And as a result of that, the new and new device support is that application is responsible for the configuration. In the current device support, there are a lot of car config options to set vendor ID, product ID, it's a remote wake-up capabilities. They are actually obtained from the driver, but they are self-powered, or not self-powered, and so on. But in the new support, the application is responsible for the setting and configurations. So an application must institute one or more USB device instances that always placed in the application context. And application must institute at least one configuration per device instance, like the diagram here. And application must add and the desired class instance to a specific configuration. If a CDC-SAM implementation has to be a part of this configuration that it's up to application to register this class. And application must insert specific USB device instance. There are two steps. One is to call USB D in it, and the second one USB D enable. The reason for that is that it's like two steps. The stack passes all the pieces or all the instances on fixed descriptors updates the interface numbers and endpoints. And after that, an instance can be enabled. It can be disabled any time and enabled again, so it's. And the other reason is that if the device controller supports Vibas and detection, and not every controller is scalable, but it can send an event to the host. And the application will be notified that after the initialization, the device is connected to the host or to a charger port, for example, and configure some power delivery device. Yeah, that's an example of what the application needs to do before it can enable USB device support. They have a helper markers to institute a device context and helper markers to define a configuration. They're same for the string descriptors, but they are optional. Yeah, no one device has to use a string descriptor. And there are a few steps in the code self, like a descriptor configuration register the class. Each class instance has a must have a name, like humans. It's always. And it's post fixed with a number, depending on the number of instances. If there are two CDCSMs, there will be two CDCSM or CDCSM1. And yeah, after initialization, USB can be enabled or disabled multiple times. It's just an example. And finally, shut down. After shut down, anything is initialized. And yeah, other classes will be removed from the context of USB device context. There's a bit underlying to have this part for testing, for example, or for development. So there's an alternative for that. We have a shell support for USB device. This is a sample that enables us to have these settings. And yeah, then for testing, you can just load some defaults. The commands there need some polish or some more improvements. But you can load some defaults. It's a pre-configured device. String descriptor's add-on configuration. For example, here, you can see it's a class with name loopback 0 that will be added to configuration 1. And change PID, remote wake-up capability, finally it's a size and device and enable. And after a device is enabled, the host will enumerate. This device will be recognizable by the host. And if application doesn't need USB support anymore, it can disable or shut down the device. After shut down, it can be reconfigured and initialized and enabled again. And for each of these steps, there's some actions required for the classes or for class instances. And that's actually my goal is to show these requirements for the class API. So what we need for the API itself, we need an installation handler. Instance is a part of our configuration that's initialized, shut down handler. A handler for associated configuration is selected. Yeah, if the host selects, for example, there's just two configurations. And instance is part of configuration 2. And host selects this configuration. Instance will notify that this configuration part is now active. And if the endpoints are active and there could be some requests from the host. The same for disable. And there are two handlers for USB control requests. They're separated in the new stack. The one is for two hosts and the other one two device. That's the direction of the data stage of the data transfer. And this interface configuration update handler and endpoint, there's one handler for all the finished or completed transfer requests. We also have suspend and resume handlers and feature hold, but it's not that important. Class instantiation. Yeah, that's actually a problematic part. And I have the same issues we have with devices or how to instantiate a device. So the approach here is the FI USB define macro that needs to be used for each approaches. But the first one is to use device tree. If a device model is involved. Like for the CDCSAM that emulate UART interface and UART interface is described in device tree. Here we can use entity instance for each status macro. If a device model is not involved, then the only way is to define an instantiation macro for the user or for the application. And the last approach is the combination of the instantiation macro and a listify macro. That is what the new mass storage controller class implements. And there are very bad examples in the current USB device support. I will talk more about that. This is, for example, a USB JD class implementation that opposes a USB device model and not a USB device model from CFI. Here's an example. We talk this from CDCSAM. You can see there is one API for the class. It's a USB D class API structure. And the other one is Ethernet controller driver API. Because ECM emulates Ethernet controllers. So we have two. So this implementation uses two APIs. Yeah, and instantiation apps happens in this huge macro. And therefore, each instance, so a descriptor is defined each instance on descriptor. It's an instantiate string descriptor. And finally, use the macro. Then you can see there are a few in USB D class API as all these, there are these handlers. Most of them are optional. It's depending on the class implementation to use them or to implement them or not. And for example, we will start with Inetangular. So it's called as result of USB unit. USB D init. Every time USB D init is called from the application, later this handler will also be called from the stack, in the stack contents. The stack updates are the interface number and endpoint addresses. And as only the instance or class implementation there is no details about the interface descriptor. It can be very specific. It's the class can update all these missing parts that the stack doesn't know during this handler call. And also this handler can be used to institute interface string descriptors in the class. So in the new support, it's possible to define or institute a string descriptor and assign it to a specific interface. Like for CDCSM, it is used for the Mac, for the remote Mac address. And here's the example from CDCSM of the init and the implementation. Then you can see that the new descriptor will be inserted to device context. And the number of the index of the new string descriptor will be updated in the interface descriptor. And the shutdown is opposite of init handler. It's called as a result of USB D shutdown. And implementation can use it to remove on string descriptors. Or not, it will be removed anyway later. It's kind of optional, but it would be good behavior. Finally, the class instance is removed from the configuration by the stack. After USB shutdown, class needs to be re-registered to the context. Then we have class as a disabled handler and update handler. There is a difference that can be used kind of in same way, but it depends on the class. So the enabled handler is called when associated configuration is selected. It's notifies the instance that it's now part of active USB configuration. But update handler is called when an interface alternate is selected. If you look at the descriptor at the beginning, there are two other one ethernet for the interface. And if the host updates alternate, this handler will be called. Both handlers can be used to trigger specific actions in the implementation. Like CDCSM uses update handler to enable a network stack and start first out transfer, initiate transfers. But CDCSM uses because CDCM doesn't have alternates. CDCSM uses enable handler to enable your ex-IQ handling. And here's an example from CDCSM and the important part is here that if there is alternate interface, the implementation needs to check this alternate value and handle accordingly. CDCSM, if the alternate is selected to zero, and there is no endpoints in this configuration, that means it's from the host driver, it's like carrier off that will be handled by the driver. And if the alternate is one, then the interface will be activated or enabled. There are two handlers who control requests. So class manipulation doesn't need to check transfer direction on its own. It's two handlers, one of them, but with different signatures. The host to device is just a constant pointer to constant structure. And for device to host, it's just a constant pointer. So that's a difference in the function signatures. The protocol are indicated in the control handling by ANO variable from the thread. This way, because for the device framework, we need two error variables or two ways to signalize the error. The one is not as protocol error, like not supported control request. Then the implementation would say ANO variable is not supported. And the return value as function return value is used for non-protocol errors, like something very bad happens. And in the current implementation or the current state of new device tech, the thread will panic and stop it. It will be removed later, for sure. Yeah, the other thing is the USB vendor request. That is, I can show it later. So the macro is used to register specific vendor requests that are not directed to an interface or endpoint. So the stack cannot find what instance should reply to this request. And then instance can register a specific vendor request. And the stack will look for that in a table and call. Appreciate instance here. So the hand itself should not allocate or free any buffers. This is done by the lower layer and by device framework. So it just control request handling, not anymore. And so finally, the class must not implement this handler if control bar is not used. Here's an example from the must storage class support, how to register this vendor specific request. Then we have transfer request competition handler. The handler is called as consequence of a completed interrupt bulk or isochronous transfer request. There's only one for all of these transfer types, but not for the control. It's called regardless of the result. If it's time out or canceled. No, time out is not support story. But if it's a transfer is canceled, it's called if another error happens on the controller level, this handler will be called. And so here class must not implement this handler if only control bar is used. Like USBD, the view won't implement this handler. And the transfer cells are submitted by USBD and Q function. So that is also from CDCSM implementation. And you can see here the buffer is for interrupt transfer is allocated. That's for notification CDCSM notification. And finally, NQ'd by USBD AP NQ. And when the transfer is finished, the request handler will be called. And then the instance needs two parts. So the information what endpoint was used here for the transfer or what transfer is it can be obtained from the buffer info structure from the NetBuff. And finally, you need some helpers in the implementation because each implementation know what interface descriptor belongs to that one. And to compare what endpoint was used on then you know what kind of transfer it is. If it's bulk transfer, interrupt, or bulk out, bulk in, so the same for isocornos. We don't have specific support for synchronous transfers because it can easily be implemented with a semaphore like here in this example. And you can just give the semaphore in this request handler. So the semaphore will be taken just after the transfer. After the transfer is incurred, so after here at the bottom. And given in the last time competition handler. So we actually don't need specific handling or some specific implementation in synchronous transfer. Each instance can be implemented on its own. And a few more notes. Circularly just one. All these handlers are executed in the trade context of USB device stack. You actually shouldn't block in these handlers. And because if it's a control in, out handler, then you will block the processing in device framework. So not about future work, but it's a new device support is still experimental. So next steps will be that we need some improvements for transfer handling, more abstraction. And there is a big issue on the GitHub for USB notification that we will implement for. That will be used for the new device support and also for the class instances. Like, for example, for the CSM, if the host changed about the rate, we don't have an interface to notify application about that. Because your API is used. And for this purpose, we will have USB notifications. Troubleshooting, if you have any, then, yeah, the one way is to use Pi USB. In Pi USB, you can just remove the host driver and start control or a bulk or whatever transfer from Pi USB and look at our new device, a new instance behavior. And I think we have USB device support for shell, as I mentioned before. And there is also virtual controller implementation for USB host on USB device as well with the shell support. And that's another way to debug your device or your instance. Yeah, I understand. That's a shell demo, a shell sample in USB support. And so it's built for Kimu Cortex M3 with USB host support and USB device enabled. And we don't have that much commands now. It's not that user-friendly, but we have this helper command for USB defaults to enable. It just instates some string descriptors and edits to the context. And we have two, in this shell support, there are two configurations available, two instances of configurations. So we need at least one to register the classes. And finally, you can see that two instances, one from CDCS-M and one is loopback implementation that we use for testing for the current and new device stack sometimes. So that's the CDCS-M to configuration one. And now we can initialize USB device support. And so now if that would be a real controller and you would attach your device to the host, if the controller supports detection of Vibas, you will see a notification here from the log in the future that will be notified using USB notifications. We add the same for the host in it, enable. So then we can, for example, obtain, read device descriptor from our unconfigured device, which is always zero. The same for the configuration descriptor. No? Yeah, it's weird, always. It's not zero, it's one. Sorry, there's no configuration descriptors. Yeah, no. So that's the configuration. Yeah, there's a limited set of commands that we can use for debugging. You don't even need a real host for that. And we will use also the same virtual host and controller device support for testing in the future. Yeah, that is from my side, actually. Questions? No questions, questions, yeah. Devices, if you are doing anything about composite devices, are you doing anything particular to handle the fact that on Windows the different drivers, if you ramp up and you add a new class over the same BAD and PAD, you're going to get the driver hell and you have to uninstall everything? Or are you just taking this as the user will take care? I understood it correctly. So vendor ID and product ID, so the samples we have, what we support on classes in Zifier do not require any specific vendor ID or the product ID to be recognized by the driver. Let me ask it again. So if you're implementing a composite device, so with two classes, you will end up on Windows. In particular, Linux makes a much better job. But on Windows you end up having driver mismatch if one is implemented earlier or added earlier and then the other later. And Windows will get confused by this. You're not tackling these kind of problems in the right of the USB-D stack. Actually, I'm not aware of any. So what is missing in the new device support is support for OS descriptors and binary object storage descriptors, yeah. That is missing. I'm working on it. But yeah, we have that in the current device support, but it's kind of, I don't know, it's not, it's there, yeah. It works, but yeah. It's, last time, recently we changed how, for example, string descriptors are handled a bit, so on the next step will be support for binary object storage and OS descriptors are Microsoft-specific. It's not, Linux doesn't care. The other thing is that I forgot to mention if the devices with multiple interfaces, yeah, or class instance with multiple interfaces requires an interface association descriptor. And we will look at that, yeah. For the, so actually, CDC, SEM and EM should have, yeah, their pedifold. We don't even have a config option for that if those requirements will be, if there's a multiple interfaces for an instance, yeah, then it should contain interface association descriptor, yeah. There's a difference to why CDC SEM is a bit confusing because there's a CDC SEM as whole, yeah, because there are these union descriptors, yeah. And that is probably for the first time in, before this ECM, at the name, AIT ECM was introduced, yeah. Yeah, yeah. OK.