Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to implement a multi-function device? #1382

Open
sjg20 opened this issue Apr 29, 2024 · 8 comments
Open

How to implement a multi-function device? #1382

sjg20 opened this issue Apr 29, 2024 · 8 comments

Comments

@sjg20
Copy link
Contributor

sjg20 commented Apr 29, 2024

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
Copy link
Contributor Author

sjg20 commented Apr 29, 2024

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
Copy link
Contributor

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
Copy link
Contributor Author

sjg20 commented Apr 29, 2024

The servo contains:

  • a reset controller, so the board can be reset
  • several UARTs, one of which is for the AP (main CPU)

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

@JoshuaWatt
Copy link
Contributor

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
Copy link
Contributor Author

sjg20 commented Apr 29, 2024

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
Copy link
Contributor

JoshuaWatt commented Apr 29, 2024

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
Copy link
Contributor Author

sjg20 commented Apr 30, 2024

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
Copy link
Contributor Author

sjg20 commented May 1, 2024

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

Things I am / was unclear about:

  • what the parameters are in ResourceExport - e.g. self.child, self.data['cls'] - in general the docs here seems lacking and there aren't even comments as to the meaning of the fields
  • what the client does when it sees SerialPort / NetworkSerialPort...or really the mechanism for handling both and why they are needed (local, data, _get_params(), etc.)
  • the ordering requirement in the environment file is a pain - I get an error if a serial port is listed after the thing which needs it (this is very confusing)

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants