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

i/builtin: add gpio-chardev interface #15172

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

ZeyadYasser
Copy link
Contributor

This implements the gpio-chardev interface according to the SD129 spec which is currently hidden behind an experimental feature flag until kernel improvements to the gpio-aggreagtor lands.

Also, I had to extend the systemd backend to support specifying Wants/WantedBy/After/Before directives 44562bb.

@ZeyadYasser ZeyadYasser added the Needs Samuele review Needs a review from Samuele before it can land label Mar 5, 2025
@ZeyadYasser ZeyadYasser requested review from bboozzoo and pedronis March 5, 2025 18:04
Copy link

codecov bot commented Mar 5, 2025

Codecov Report

Attention: Patch coverage is 95.13514% with 9 lines in your changes missing coverage. Please review.

Project coverage is 78.14%. Comparing base (d6d95f0) to head (510fc2f).
Report is 34 commits behind head on master.

Files with missing lines Patch % Lines
interfaces/builtin/gpio_chardev.go 95.04% 4 Missing and 2 partials ⚠️
strutil/range.go 94.23% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #15172      +/-   ##
==========================================
+ Coverage   78.09%   78.14%   +0.04%     
==========================================
  Files        1190     1192       +2     
  Lines      158458   159239     +781     
==========================================
+ Hits       123746   124431     +685     
- Misses      27017    27076      +59     
- Partials     7695     7732      +37     
Flag Coverage Δ
unittests 78.14% <95.13%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

github-actions bot commented Mar 5, 2025

Fri Mar 21 12:43:06 UTC 2025
The following results are from: https://github.com/canonical/snapd/actions/runs/13990024789

Failures:

Preparing:

  • google-distro-1:debian-11-64:tests/main/snap-handle-link
  • google:ubuntu-24.10-64:tests/main/interfaces-pulseaudio
  • google:ubuntu-24.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow

Executing:

  • google-distro-1:debian-11-64:tests/main/snap-user-service-restart-on-upgrade
  • google-distro-1:debian-11-64:tests/main/interfaces-personal-files
  • google-distro-1:debian-11-64:tests/main/interfaces-desktop
  • google-distro-1:debian-11-64:tests/main/prepare-image-uboot
  • google-distro-1:debian-11-64:tests/main/interfaces-mount-observe
  • google-distro-1:debian-11-64:tests/main/snapshot-cross-revno
  • google-distro-1:debian-11-64:tests/main/snap-debug-stacktrace
  • google-distro-1:debian-11-64:tests/main/install-errors:withreexec
  • google-core:ubuntu-core-20-64:tests/main/interfaces-browser-support:disallow
  • google-core:ubuntu-core-20-64:tests/main/interfaces-mount-observe
  • google-core:ubuntu-core-20-64:tests/main/interfaces-browser-support:allow
  • google-core:ubuntu-core-20-64:tests/smoke/sandbox
  • google-core:ubuntu-core-20-64:tests/smoke/install
  • google:ubuntu-20.04-64:tests/main/lxd:snapd_cgroup_just_inside
  • google:ubuntu-20.04-64:tests/main/progress
  • google:ubuntu-20.04-64:tests/main/install-errors:withreexec
  • google:ubuntu-20.04-64:tests/main/cgroup-tracking:test
  • google:ubuntu-20.04-64:tests/main/desktop-portal-screenshot
  • google:ubuntu-20.04-64:tests/main/non-home:var
  • google:ubuntu-20.04-64:tests/main/interfaces-libvirt
  • google:ubuntu-20.04-64:tests/main/interfaces-polkit
  • google:ubuntu-20.04-64:tests/main/dbus-activation-session
  • google:ubuntu-20.04-64:tests/main/interfaces-network-status-classic
  • google:ubuntu-24.10-64:tests/main/snap-debug-stacktrace
  • google:ubuntu-24.10-64:tests/main/non-home:home2
  • google:ubuntu-24.10-64:tests/main/interfaces-shared-memory-private
  • google:ubuntu-24.10-64:tests/main/interfaces-polkit
  • google:ubuntu-24.10-64:tests/main/snap-user-service-start-on-install
  • google:ubuntu-24.10-64:tests/main/snapshot-users
  • google:ubuntu-24.10-64:tests/main/refresh-app-awareness:classic
  • google:ubuntu-24.10-64:tests/main/interfaces-requests-activates-handlers
  • google:ubuntu-24.10-64:tests/main/snap-confine-undesired-mode-group
  • google:ubuntu-24.10-64:tests/main/install-errors:withreexec
  • google:ubuntu-24.04-64:tests/main/try

Restoring:

  • google-distro-1:debian-11-64:tests/main/snap-handle-link
  • google:ubuntu-24.10-64:tests/main/apparmor-prompting-integration-tests:create_multiple_allow

return lines, nil
}

func validateLines(linesAttr string) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

this could be made a helper in strutil, e.g.:

// RangeSpan represents a span of numbers inside a range. A span with
// equal Start and Stop describes a span of a single number.
type RangeSpan struct {
    Start, Stop uint
}

// Range of discrete numbers represented as set of non overlapping spans.
type Range struct {
    // Spans within the range, ordered by Start.
    Spans []RangeSpan 
}

// ParseRange parses a range represented as a string. The entries are 
// joining them with a comma: n[,m] or as a range: n-m or a combination
// of both, assuming the ranges do not overlap, e.g.: n,m,x-y.
func ParseRange(in str, expectedMax uint) (Range, error) {
    ...
}

and then you can reuse it in the helper. Separately I'd add something to the gadget package, or a subpackage there to convert range to a sequence of lines, which should be quite trivial at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, this now could easily be shared with the helper command, about converting a range into a sequence of lines, I don't think we really need this as the kernel in fact accepts this exact format.

@ZeyadYasser ZeyadYasser requested a review from bboozzoo March 7, 2025 10:22
@ZeyadYasser ZeyadYasser added the Needs security review Can only be merged once security gave a :+1: label Mar 10, 2025
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

this looks reasonable to me

strutil/range.go Outdated
}

func parseRangeSpan(in string) (RangeSpan, error) {
hasNegativeStart := strings.HasPrefix(in, "-")
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need the negative range support?

Copy link
Contributor

Choose a reason for hiding this comment

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

not at all, I discussed dropping it with @ZeyadYasser

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to using uints

@ZeyadYasser ZeyadYasser requested a review from bboozzoo March 12, 2025 11:07
Copy link
Contributor

@bboozzoo bboozzoo left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thanks, couple of small things, question for @bboozzoo

strutil/range.go Outdated
// ParseRange parses a range represented as a string. The entries are joining
// them with a comma: n[,m] or as a range: n-m or a combination of both, assuming
// the ranges are non-negative and do not overlap, e.g.: n,m,x-y.
func ParseRange(in string) (Range, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick: s/in/input/ or s/in/s/

strutil/range.go Outdated
// Parse range e.g. 2-5
tokens := strings.SplitN(in, "-", 2)
if len(tokens) != 2 {
return RangeSpan{}, fmt.Errorf("invalid range span %q", in)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this error is not tested

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This check is not reachable since input that doesn't contain - will have already been parsed as a single number, and doing SplitN on a string that has - will always have exactly two tokens.

The check is redundant, but good to have in case SplitN was swapped to Split for any reason or by mistake.

return nil
}

// XXX: What should be the limit on max range.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@bboozzoo any suggestion here? is there a limit in the kernel? otherwise we should just remove the XXX

Copy link
Contributor

Choose a reason for hiding this comment

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

we can set it to 512, which would still be very high and unlikely to ever be reached in practice.

This implements the gpio-chardev interface (spec SD129) which
is currently hidden behind an experimental feature flag until
kernel improvements to the gpio-aggreagtor lands.

Signed-off-by: Zeyad Gouda <[email protected]>
- strutil: fix range helper typo s/Intersets/Intersects
- strutil: only support positive numbers in ParseRange
- strutil: sort range spans in ParseRange
- i/builtin/gpio-chardev: error message improvements
- i/builtin/gpio-chardev: remove unnesaccery non-negative validation

Signed-off-by: Zeyad Gouda <[email protected]>
@ZeyadYasser ZeyadYasser force-pushed the gpiod-chardev-interface branch from 71b8a46 to 7c6db0b Compare March 13, 2025 12:47
@ZeyadYasser
Copy link
Contributor Author

I had to rebase to resolve conflicts

@ZeyadYasser ZeyadYasser requested a review from pedronis March 13, 2025 12:47
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thanks

@ZeyadYasser ZeyadYasser requested a review from zyga March 19, 2025 12:00
Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

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

Thanks for the improvements. I did a longer pass.

I have some small fixes/improvements to the range code and some questions and comments to the interface code. Please see inline.

return false
}

func (r Range) Size() (size int) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you document this and explain (and check that this is indeed expected) that overlapping range spans are counted multiple times.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for Range, overlapping range spans are documented as unexpected, we could relax this later if we need to. This is also enforced through the ParseRange helper.

// Range of discrete numbers represented as a set of non overlapping RangeSpan(s).
type Range []RangeSpan

strutil/range.go Outdated
}
r = append(r, s)
}
sort.Sort(spansByStart(r))
Copy link
Contributor

Choose a reason for hiding this comment

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

This sort of sorting is discouraged now. Perhaps you can use sort.Slice:

Suggested change
sort.Sort(spansByStart(r))
sort.Slice(r, func(i, j int) bool { return r[i].Start < r[j].Start })

strutil/range.go Outdated
return r, nil
}

func parseRangeSpan(in string) (RangeSpan, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please move this next to RangeSpan

"strings"
)

// RangeSpan represents a span of numbers inside a range. A span with
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick. I tried to make sense of the naming of RangeSpan and Range but I keep feeling those are off. Could this be just Span and the other one Spans? Feel free to ignore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have strong opinions on this, @bboozzoo @pedronis what do you think?

strutil/range.go Outdated
}
start, err := strconv.ParseUint(tokens[0], 10, 32)
if err != nil {
return RangeSpan{}, fmt.Errorf("invalid range span %q: %w", in, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Small working tweak to make errors clearer:

Suggested change
return RangeSpan{}, fmt.Errorf("invalid range span %q: %w", in, err)
return RangeSpan{}, fmt.Errorf("invalid start of span %q: %w", in, err)

plugName := slot.Name()
plugSnapName := plug.Snap().InstanceName()

target := fmt.Sprintf("/dev/snap/gpio-chardev/%s/%s", slotSnapName, slotName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd really use filepath.Join here.

Type: "oneshot",
RemainAfterExit: true,
ExecStart: fmt.Sprintf("/bin/sh -c 'mkdir -p %q && ln -s %q %q'", filepath.Dir(symlink), target, symlink),
ExecStop: fmt.Sprintf("/bin/sh -c 'rm -f %q'", symlink),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to run this via the shell?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not really, I was keeping it consistent with ExecStart, I can switch to /bin/rm

plugSnapName := plug.Snap().InstanceName()
snippet := "# Allow access to exported gpio chardev lines\n"
// Allow access to exported virtual slot device.
snippet += fmt.Sprintf("/dev/snap/gpio-chardev/%s/%s rwk,\n", slotSnapName, slot.Name())
Copy link
Contributor

Choose a reason for hiding this comment

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

We are computing this many times in the file. Can you use a helper that has the path exactly once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// Allow access to exported virtual slot device.
snippet += fmt.Sprintf("/dev/snap/gpio-chardev/%s/%s rwk,\n", slotSnapName, slot.Name())
// Allow access to plug-side symlink to exported virtual slot device.
snippet += fmt.Sprintf("/dev/snap/gpio-chardev/%s/ r,\n", plugSnapName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can fold this to /{,*} r

@@ -53,6 +57,18 @@ func (s *Service) String() string {
if s.ExecStop != "" {
fmt.Fprintf(&buf, "ExecStop=%s\n", s.ExecStop)
}
if s.Wants != "" {
fmt.Fprintf(&buf, "Wants=%s\n", s.Wants)
Copy link
Contributor

Choose a reason for hiding this comment

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

Buf should have been a strings.Builder not bytes.Buffer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I think this should go into a followup PR, This one is already huge

@ZeyadYasser ZeyadYasser requested a review from zyga March 21, 2025 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Samuele review Needs a review from Samuele before it can land Needs security review Can only be merged once security gave a :+1:
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants