Skip to content

Conversation

zkrx
Copy link

@zkrx zkrx commented Oct 9, 2020

I use tbot with Zephyr. I've tried to properly add (basic) Zephyr support and figured I should try upstreaming.

I'm not that good at Python, so this is mainly a big copy and paste with some renaming. I did my best to respect
what's in place though :).

For starters: I'm unfortunately testing with an older version of Zephyr (v1.13 vs v2.4.0). I'm not sure how
Zephyr's shell evolved since. I've had a look to Zephyr's documentation [1] but I should ideally try to test this
on v2.4.0, if I find some time...

I copied what was done for U-Boot and trimmed what I thought wasn't needed:
For command execution, I didn't find a way to get the command's return value ("$?"), so I've just removed retval
and removed the exec0 and test functions. Also, at least to my knowledge, Zephyr doesn't support environment
variables, so this had to go too. I also removed U-Boot-specific stuff such as boot() and ram_base().

In the current state, this PR is missing tc/ and Documentation/ updates (and other stuff that I may
have missed).

Also, Zephyr's shell is fairly simple. I've removed the escape function as I don't think it is needed
to escape anything, but maybe someone will tell me otherwise?

Do you think there's a chance to merge this? If so, I can try to add what's missing in tc/ and Documentation/
(and other bits you may find lacking).

[1] https://docs.zephyrproject.org/latest/reference/shell/index.html

For now, only provide a ZephyrShell and the associated decorators. A ZephyrShell
is rather simple and has no mean to retrieve the last command's return value.

This work is mainly a copy and paste of board.uboot with the necessary changes:
 - no return value in exec()
 - no environment variables
 - no boot() or ram_base()

Signed-off-by: Xavier Ruppen <xruppen@gmail.com>
@Rahix
Copy link
Owner

Rahix commented Oct 12, 2020

Zephyr support does sound very interesting, I definitely support adding that! Though I do have to say that I'm lacking experience here ... Do you know if there's a good way to run a Zephyr demo e.g. in qemu?

Looking at your changeset, it's going in the right direction but as you probably noticed, it needs a lot of additions in different places which isn't too nice (thinking it terms of maintainability). You catched me a bit early with this, as I am currently working on making this entire part more flexible and generic, which would make the zephyr addition much easier. If you'd be okay with relying on your fork for now and giving me a few more weeks, I would suggest waiting for that refactor first. With that in place, this would hopefully get much easier :)

@zkrx
Copy link
Author

zkrx commented Oct 12, 2020

Do you know if there's a good way to run a Zephyr demo e.g. in qemu?

There is a way to run Zephyr natively with a "Native POSIX" board, see [1]. It seems that you can also emulate ARM (among others) architectures with qemu boards, see [2].

[1] https://docs.zephyrproject.org/latest/boards/posix/native_posix/doc/index.html
[2] https://docs.zephyrproject.org/latest/boards/arm/qemu_cortex_m3/doc/index.html

I would suggest waiting for that refactor first

Alright, ping me once it's ready, although it will probably depend on my schedule at that time.

Thanks for checking!

@Rahix
Copy link
Owner

Rahix commented Oct 12, 2020

There is a way to run Zephyr natively with a "Native POSIX" board

Oh, cool, I'll look into that!

Alright, ping me once it's ready, although it will probably depend on my schedule at that time.

Will do! If you're too busy, maybe I can already start moving code from your branch here over to the new system. But let's see ...

@Rahix Rahix added the feature Enhancement/New feature label Oct 12, 2020
if (time.monotonic() - self._timeout_start) > self.boot_timeout:
raise TimeoutError("Zephyr did not reach shell in time")
try:
self.ch.read_until_prompt(timeout=0.2)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an issue with that code: my testcases succeed 95% of the time, but miss the prompt every so often which results in a TimeoutError exception. Adding print(buf) in read_until_prompt() just before [1] shows that buf is truncated when this happens. Here's an sample output of such a timeout, looking for b'# MY_PROMPT':

[...]
bytearray(b'[... hundreds\r\n of\r\n bytes]\r\n # MY_P')                                                                                 
Ebytearray(b'R')                                                                                                                                                                                                                        
SUbytearray(b'ROM')                                                                                                                                                                                                                     
Lbytearray(b'ROMP')                                                                                                                                                                                                                     
bytearray(b'ROMPT\r')                                                                                                                                                                                                                   
│   │   │    <>                                                                                                     
bytearray(b'ROMPT\r\n')                                                                                                                                                                                                                 
│   │   ├─Result: TIMEOUT                                                                                           
│   │   └─Fail. (46.198s)                                                                                                                                                                                                               

The problem disappears when I set this timeout to something big (10 seconds) (in my case, boot takes around 2 seconds to get to the prompt). As this timeout value is given to read_iter() in channel.py, I believe that based on luck, a timeout can occur right during the display of the prompt. This splits the buffer in the middle of the prompt and we therefore miss it.

This problem seems to be present for U-Boot boards too (I took that code from there), but perhaps boot time is far less than 200ms in most cases, so it doesn't happen in practice. I'm not sure about how Linux boards handle that part.

I don't know what would be the best way to handle this. Perhaps read_iter() could store the buffer and restore it after a Timeout exception? I'm new to that Iterator/yield() stuff, so bear with me here.

[1] https://github.com/Rahix/tbot/blob/master/tbot/machine/channel/channel.py#L881

Copy link
Author

@zkrx zkrx Oct 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that ^C is sent on timeout, which displays the prompt again. So U-Boot has probably no issue with that, I guess you can dismiss that comment :).

EDIT: ^C doesn't have the same effect on a Zephyr shell though. Replacing ^C by \n should work for Zephyr.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the idea is that e.g. if the board is already turned on, the prompt won't re-appear on its own when attaching to the serial console. So I send ^C which makes U-Boot show the prompt again and tbot can then continue.

I guess the same would be useful for Zephyr but as you noticed ^C does not work, we'll have to find another mechanism ... Using \n is a bit dangerous: For example, there might be a half-written command currently waiting on the console before tbot attaches. Sending \n would then execute it which should be avoided. Is there any way we could prevent this? E.g. is there any key sequence we can use to clear everything currently written after the prompt (short of sending a bunch of backspaces :p)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced self.ch.sendintr() below with self.ch.sendline() and that seems to do the trick.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't refresh my page and missed your reply.

Using \n is a bit dangerous

Oh yeah, I didn't think about that. I was mainly concerned by the "prompt split in two" issue.

E.g. is there any key sequence we can use to clear everything currently written after the prompt (short of sending a bunch of backspaces :p)?

So it looks like latest Zephyr versions handle ctrl keys [1] but I've never tried. Looks like it should work though!

[1] https://github.com/zephyrproject-rtos/zephyr/blob/master/subsys/shell/shell.c#L822

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

Successfully merging this pull request may close these issues.

2 participants