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

bluetooth: shell: refactor shell print for Bluetooth-specific context #74652

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

ndrs-pst
Copy link
Contributor

@ndrs-pst ndrs-pst commented Jun 21, 2024

This PR aims to eliminate the dependency on ctx_shell in the Bluetooth host/shell/*, making the code more maintainable.

  • Introduced bt_shell_private.c and bt_shell_private.h to provide common functions for the Bluetooth shell.
  • Limit the usage of ctx_shell to cases where printing requires it and sh is not available.
    (Interim commit before switch to bt_shell_*)
  • Refactor shell print to eliminate ctx_shell usage in the Bluetooth host/shell/*.

Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

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

I don't think this is the right direction (but please do convince me if you believe so).

Some of the changes are welcome, but the idea of a bt_shell is IMO not.

In your commit message you mention that you want to remove the code footprint, but you also mention an increase cost of this new bt_shell. Aren't those two sentences conflicting?

The BT shell definitely needs improvement. I created a issue #70945 related to this, that requests the removed of the ctx_shell. The removal of the ctx_shell would directly conflict with the majority of this PR, as all the bt_shell functions you've defined will simply call the shell functions directly (as they would need the shell pointer again).

Could you provide some data on the codesize that you've increase/decreased?

@ndrs-pst
Copy link
Contributor Author

@Thalley Thank you for your feedback. It's good to hear that you're addressing ctx_shell. 😃

Aren't those two sentences conflicting?

At first glance, this might seem controversial.
Currently, many shell print require passing parameters like sh and color repeatedly.
By centralizing these interactions within specific bt_shell functions, we streamline the code.

Could you provide some data on the codesize that you've increase/decreased?

By picking up periodic_adv_conn sample with nrf52840dk boards and adding CONFIG_BT_SHELL=y
the footprint of shell\* which get from rom_report shown that

MODULE FLASH SIZE (BEFORE) FLASH SIZE (AFTER) INCREASE/DECREASE
bluetooth\shell\* 15,795 B 14,963 B -832 B
bt.c 13,251 B 12,275 B -976 B
bt_shell_private.c 0 B 144 B +144 B

Here is the footprint from each wrapper
bt_shell_private

@ndrs-pst
Copy link
Contributor Author

To stretch further, I think the functions that should be improved are
shell_fprintf_impl(sh, color, fmt, ##__VA_ARGS__) itself to have a wrapper for the color parameter.

void __printf_like(2, 3) shell_fprintf_error_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_info_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_print_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_warn_impl(const struct shell *sh, const char *fmt, ...);

Since in reality the same message printed out inside the shell is always fixed to a specific color, and if we prioritize the code size, this should make sense.

Otherwise, every time, for example in Cortex-M, r1 needs to be allocated to pass the color value (while r0 is for sh), which causes the caller code to save the previously used r1.

@Thalley
Copy link
Collaborator

Thalley commented Jun 28, 2024

Just tried your PR with the BT shell test application for the nrf5340dk_nrf5340_cpuapp board and got the following results:

With PR

Memory region         Used Size  Region Size  %age Used
           FLASH:      317356 B         1 MB     30.27%
             RAM:       52992 B       448 KB     11.55%
        IDT_LIST:          0 GB        32 KB      0.00%

Without PR

Memory region         Used Size  Region Size  %age Used
           FLASH:      318668 B         1 MB     30.39%
             RAM:       52992 B       448 KB     11.55%
        IDT_LIST:          0 GB        32 KB      0.00%

So while I do not agree with the direction of this PR, as we really need to get rid of ctx_shell, I can't disagree with the 1312 Bytes saved.

I'm not at a point where I'll approve, but I'm also not going to reject this change. I'd like to get input from the other BT collaborators on this.

@Thalley Thalley self-requested a review June 28, 2024 09:18
@Thalley Thalley dismissed their stale review June 28, 2024 09:18

Not sure yet

@ndrs-pst
Copy link
Contributor Author

@Thalley, thank you for the additional information. It seems we should start by getting rid of ctx_shell and then find a way to reduce the footprint.

@ndrs-pst ndrs-pst marked this pull request as draft June 28, 2024 09:32
@ndrs-pst
Copy link
Contributor Author

To stretch further, I think the functions that should be improved are shell_fprintf_impl(sh, color, fmt, ##__VA_ARGS__) itself to have a wrapper for the color parameter.

void __printf_like(2, 3) shell_fprintf_error_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_info_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_print_impl(const struct shell *sh, const char *fmt, ...);
void __printf_like(2, 3) shell_fprintf_warn_impl(const struct shell *sh, const char *fmt, ...);

Since in reality the same message printed out inside the shell is always fixed to a specific color, and if we prioritize the code size, this should make sense.

Otherwise, every time, for example in Cortex-M, r1 needs to be allocated to pass the color value (while r0 is for sh), which causes the caller code to save the previously used r1.

@Thalley Just a reminder that this stretch was merged in this PR: #75340. 👀

Copy link

This pull request has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this pull request will automatically be closed in 14 days. Note, that you can always re-open a closed pull request at any time.

@ndrs-pst
Copy link
Contributor Author

@Thalley I just added another commit that introduces CONFIG_BT_SHELL_BACKEND for Bluetooth shell output when the shell context is unavailable (e.g., in callbacks).

While exploring ways to eliminate ctx_shell, could we initialize it during startup and hide it from other shell commands?
Since ctx_shell is currently used in multiple places like audio/shell, mesh/shell, and services/ias/shell.


#include "bt_shell_private.h"

extern const struct shell *ctx_shell;
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't bt_shell be more appropriate, since this is now basically an internal API?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's also fine!
FWIW, I borrowed the name from #include "net_shell_private.h", so I was following that pattern. 😄

@Thalley
Copy link
Collaborator

Thalley commented Oct 16, 2024

As for the previous question, I believe we can initialize it to backend_name, which is in CONFIG_BT_SHELL_BACKEND, by utilizing shell_backend_get_by_name("backend_name"). Even though we can have one or more shells, why can't we specify unsolicited output to just one specific backend?"

I'm not an expert in the shells, but the current ctx_shell is not a backend, but just a reference to a struct shell. Multiple struct shells can use the same backend as far as I can tell, so not sure how you are expecting to use a specific backend.

If you have 2 or more shells that each may call the BT shell command handler, then currently the ctx_shell only refers to one at them at any one time and may chance between them whenever ctx_shell is reassigned. This is clearly not ideal.
Question is whether, and how, doing command X on shell S1 that trigger callback Y should send the string from the callback to just S1 or also S2 (another shell instance)?

@ndrs-pst
Copy link
Contributor Author

If you have 2 or more shells that each may call the BT shell command handler, then currently the ctx_shell only refers to one at them at any one time and may chance between them whenever ctx_shell is reassigned. This is clearly not ideal.

Yeah, that's why I'm bringing up the real use case. Even if we have two or more shells, do we really need to support BT shell responses for every situation? It feels like we're trying to cover cases that might not happen often. 😃

just S1 or also S2 (another shell instance)?

As you mentioned as an alternative in #70945. A kind of shell_wall_print may look like this


void bt_shell_wall_print_impl(const char *fmt, ...)
{
	va_list args;
	int count;
	const struct shell *sh;

	va_start(args, fmt);
	count = shell_backend_count_get();
	for (int i = 0; i < count; i++) {
	    va_list args_copy;

	    va_copy(args_copy, args);   /* Create a copy of 'args' to safely reuse */
	    sh = shell_backend_get(i);
	    shell_vfprintf(sh, SHELL_NORMAL, fmt, args_copy);

	    va_end(args_copy);          /* Clean up to prevent resource leaks */
	}
	va_end(args);
}

I'm OK if that's the way we can move forward.
Since the priority of this PR is to reduce the footprint and get closer to getting rid of ctx_shell.

WDYT?

@Thalley
Copy link
Collaborator

Thalley commented Oct 17, 2024

If you have 2 or more shells that each may call the BT shell command handler, then currently the ctx_shell only refers to one at them at any one time and may chance between them whenever ctx_shell is reassigned. This is clearly not ideal.

Yeah, that's why I'm bringing up the real use case. Even if we have two or more shells, do we really need to support BT shell responses for every situation? It feels like we're trying to cover cases that might not happen often. 😃

It's hard to tell how people use these APIs, but the API does allow for such behavior, and thus any solution to our "problems" should consider what's possible, even if it will only happen with madmen :D

As you mentioned as an alternative in #70945. A kind of shell_wall_print may look like this

void bt_shell_wall_print_impl(const char *fmt, ...)
{
	va_list args;
	int count;
	const struct shell *sh;

	va_start(args, fmt);
	count = shell_backend_count_get();
	for (int i = 0; i < count; i++) {
	    va_list args_copy;

	    va_copy(args_copy, args);   /* Create a copy of 'args' to safely reuse */
	    sh = shell_backend_get(i);
	    shell_vfprintf(sh, SHELL_NORMAL, fmt, args_copy);

	    va_end(args_copy);          /* Clean up to prevent resource leaks */
	}
	va_end(args);
}

I'm OK if that's the way we can move forward. Since the priority of this PR is to reduce the footprint and get closer to getting rid of ctx_shell.

WDYT?

I think the wall implementation looks fine :) @alwa-nordic was the one who originally suggested that idea, so maybe he has some input here too

@alwa-nordic
Copy link
Collaborator

The wall implementation looks good to me. I'm a bit confused about why it's called a shell "back end", but no matter.

BTW, I think in the future, we should make the shell commands more like CLI programs on a TTY, where the program exits before the shell returns to a prompt. For that, we could block and wait in command functions. For that duration, we have a tread and its stack as working memory.

@ndrs-pst
Copy link
Contributor Author

ndrs-pst commented Nov 9, 2024

@alwa-nordic Thanks for the comment.
I'll address host/shell first and leave the others, e.g. mesh/shell and audio/shell, as they are.

@ndrs-pst ndrs-pst force-pushed the pr_bt_shell_refactor branch 2 times, most recently from 550a985 to 0bf9681 Compare November 11, 2024 17:03
@ndrs-pst ndrs-pst marked this pull request as ready for review November 11, 2024 17:32
@zephyrbot zephyrbot added the area: Bluetooth ISO Bluetooth LE Isochronous Channels label Nov 11, 2024
@ndrs-pst
Copy link
Contributor Author

Here we go! Finally, this PR is re-opened for review. 😃
Ping @Thalley, @jhedberg, and @alwa-nordic.

alwa-nordic
alwa-nordic previously approved these changes Nov 12, 2024
Copy link
Collaborator

@Thalley Thalley left a comment

Choose a reason for hiding this comment

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

Looks good. Since BT is pretty much the only shell to do this, I think it makes sense to keep it in BT. One comment/question

subsys/bluetooth/host/shell/bt_shell_private.c Outdated Show resolved Hide resolved
@Thalley
Copy link
Collaborator

Thalley commented Nov 14, 2024

Found one other place where "ctx_shell" was used outside of BT, but that was easy to just remove: #81395

subsys/bluetooth/host/shell/bt_shell_private.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/shell/bt_shell_private.h Outdated Show resolved Hide resolved
subsys/bluetooth/host/shell/bt_shell_private.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/shell/bt.c Outdated Show resolved Hide resolved
Introduced `bt_shell_private.c` and `bt_shell_private.h` to provide
common functions for the Bluetooth `shell_wall_print`.
These functions are equivalent to `shell_fprintf`, `shell_info`,
`shell_print`, `shell_warn`, `shell_error` and `shell_hexdump`
but without requiring the `sh` parameter.

The cost of the newly added `bt_shell_fprintf_info` ... `_error` functions
will be negligible when there are many individual calls that need to pass
both the `sh` and `color` parameters each time.

Signed-off-by: Pisit Sawangvonganan <[email protected]>
Limit the usage of `ctx_shell` to cases where printing requires it
and `sh` is not available.

Signed-off-by: Pisit Sawangvonganan <[email protected]>
subsys/bluetooth/host/shell/bt.c Outdated Show resolved Hide resolved
subsys/bluetooth/host/shell/bt.c Outdated Show resolved Hide resolved
This change aims to eliminate the dependency on `ctx_shell` in
the Bluetooth `host/shell/*`, making the code more maintainable.
Replaced `shell_*` functions that depended on `ctx_shell` with
the appropriate `bt_shell_*` functions.

The shell-less functions `bt_do_scan_filter_clear_name`, `bt_do_scan_off`,
and `bt_do_connect_le` were added so they can be called without `sh`.

Signed-off-by: Pisit Sawangvonganan <[email protected]>
@ndrs-pst
Copy link
Contributor Author

Changes made as requested—hope this looks good!

@Thalley
Copy link
Collaborator

Thalley commented Nov 15, 2024

Excellent job and great that you updated it so fast :)

Copy link
Member

Choose a reason for hiding this comment

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

Is there anything really Bluetooth specific in this c-file? Wouldn't other modules benefit from having a "wall" API? Seems to me like this should just be a generic API instead of a Bluetooth specific one.

Copy link
Contributor Author

@ndrs-pst ndrs-pst Nov 16, 2024

Choose a reason for hiding this comment

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

It's true that this C-file isn't Bluetooth-specific, but since it's designed to address a Bluetooth-shell problem, keeping it focused on Bluetooth is fine for now. If similar needs arise elsewhere, it can be refactored into a generic API later.

🤞 Once this is merged, I'll open another PR for audio/shell, mesh/shell, and classic/shell too.

Copy link
Member

Choose a reason for hiding this comment

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

@jakub-uC @carlescufi any comments on this? How easily could this generic API be accepted as part of the shell subsystem?

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

Successfully merging this pull request may close these issues.

5 participants