labgrid-project / labgrid

Embedded systems control library for development, testing and installation
https://labgrid.readthedocs.io/
Other
327 stars 164 forks source link

How to implement a multi-function device? #1382

Open sjg20 opened 4 months ago

sjg20 commented 4 months ago

I have a 'servo' device which contains UARTs and reset control. I have manged to create a Servo resource and a ServoDriver for it:

class Servo(Resource):
    """"This resource describes a servo board with servod server

    This provides serial consoles along with reset control

    Args:
        serial (str): serial number of the servo device
        port (int): port index to use for servod process
    """
    serial = attr.ib(validator=attr.validators.instance_of(str))
    port = attr.ib(validator=attr.validators.instance_of(int),
                    converter=int)

@target_factory.reg_driver
@attr.s(eq=False)
class ServoDriver(ConsoleExpectMixin, Driver, ConsoleProtocol, ResetProtocol):
    """Provides access to servo features including console and reset

    Args:
        bindings (dict): driver to use with
    """
    bindings = {
        "servo": {"Servo", "NetworkServo"},
    }

but I am not sure how to connect up the console. I get this error when using the labgrid-client 'console' command:

labgrid.exceptions.NoResourceFoundError: no NetworkSerialPort resource found in Target(name='try', env=Environment(config_file='/vid/software/devel/ubtest/lab/env_rpi_try.cfg'), var_dict=None)

This seems obvious enough, but how can I create the serial port? I don't see how multi-function devices can be implemented?

sjg20 commented 4 months ago

I forgot to mention that I found that QEMUDriver has a console. But I still cannot work out how this line in client.py can work since there is no NetworkSerialPort:

    async def _console(self, place, target, timeout, *, logfile=None, loop=False, listen_only=False):
        name = self.args.name
        from ..resource import NetworkSerialPort
        resource = target.get_resource(NetworkSerialPort, name=name, wait_avail=False)
JoshuaWatt commented 4 months ago

It's not quite clear to me what you mean by "Multi-function device" from your example, or how the UART factors into your Servo Resource?

sjg20 commented 4 months ago

The servo contains:

So when there is a servo device, I want to provide a reset controller and a UART from that one device

JoshuaWatt commented 4 months ago

Got it. It sounds like the servo is probably the DUT? In this case, the way you would do that is to export each UART as a separate serial resource on the exporter (e.g. RawSerialPort or USBSerialPort), and also export the "reset" mechanism using what ever resource type is appropriate (e.g. GPIO?). In general Resources aren't really concerned with how they "combine" together to make cohesive sense as a view into a DUT; each one just explains how to access some given device (e.g. a serial port, GPIO, etc).

This is nice because you almost never need to write new Resources, meaning you rarely need to make changes to the exporter config just to add new functionality (in my experience).

On top of the Resources (which just say there is a device you can access), there are Drivers which are higher level "behaviors" that consumer Resources or other Drivers to implement specific feature. For example the SerialDriver binds to a SerialPort and implements the logic to actually read and write data to the serial port. On top of this, the ShellDriver can bind to a SerialDriver and implements the specifics about how to login to a Linux root console, run commands, capture output, etc.

The final level is the Place, which is effectively a group of Resources that (usually) correspond to the DUT. It's important to note this is defined at the coordinator not the exporter. When you want to interact with a DUT, you reserve it's place, which gives you access to it's Resources, then supply which Drivers you want to use based on which behaviors you need for your testing. The mechanism for supplying drivers varies; for example, labgrid-client will create a driver ad hoc based on what you are doing (e.g. if you do labgrid-client shell it will try to as hoc make SerialDriver). If you are running tests with pytest, the environment YAML file specifies the Drivers your tests needs.

TL; DR

You probably don't need a Servo Resource; instead have the exporter export each serial port and your reset mechaism independently and combine them higher in the stack

sjg20 commented 4 months ago

Thanks for all the info!

The servo is a separate board from the DUT...it connects with a single cable and provides access to DUT features as well as reset control, etc.

There is a 'servod' daemon which runs separately. You can query that daemon to obtain the /dev/ptyXX for a UART. So perhaps I need some sort of ServoSerialPort where it can query the actual device when it starts up?

I do see mention of ManagedResources but only in the docs (not the code), so I don't know what is doing on there.

JoshuaWatt commented 4 months ago

Ah, got it. I think you might be onto the right track with the ManagedResource; I think you would do something like:

class ServodManager(ResourceManager):
  ...

class ServodResource(ManagedResource):
   ...

class ServodSerialPort(ServodResource, SerialPort):
    """"
    Args:
        serialnumber (str): serial number of the servo device
        servod_port (int): port index to use for servod process
    """
    serial = attr.ib(validator=attr.validators.instance_of(str))
    servod_port = attr.ib(validator=attr.validators.instance_of(int),
                    converter=int)

class ServodReset(ServodResource): # This may not need to be a ServodResource if it's not dynamically assigned a kernel driver
    """"
    Args:
        serialnumber (str): serial number of the servo device
        servod_port (int): port index to use for servod process
    """
    serial = attr.ib(validator=attr.validators.instance_of(str))
    servod_port = attr.ib(validator=attr.validators.instance_of(int),
                    converter=int)

You'll need to figure out how to implement the correct ResourceManager stuff to make ServodSerialPort dynamically assign its port attribute (inherited from SerialPort) after querying servod; I've not done that before but if you look in udev.py it should have a good example

The key here is to split of the resources so each one exports a single device

sjg20 commented 4 months ago

Thank you Joshua, that is very helpful!

I think I am on the right track now. I hope, along the way, to learn how the exporter actually works. I'll update this with some notes or a PR

sjg20 commented 4 months ago

OK I have got this working...quite a few confusing things which I floundered my way through.

Things I am / was unclear about:

I wonder if I could add some documentation somewhere, perhaps in the code? It would be better if labgrid required full comments for all class members.

Anyway, thank you again for your help, without which I would likely have given up!