Introduce ACPI netlink multicast sub-protocol.

Dale MellorSat Mar 29 15:14:44+0100 2025

3b26ac8

Introduce ACPI netlink multicast sub-protocol. This high-level API allows applications to listen for ACPI event notifications coming from a Linux kernel. * Makefile.am: add netlink/acpi.scm to list of source files. * netlink/acpi.scm: new file.

Makefile.am

11
include guile.am
22
33
SOURCES= \
4+
  netlink/acpi.scm \
45
  netlink/connection.scm \
56
  netlink/constant.scm \
67
  netlink/data.scm \

netlink/acpi.scm unknown status 1

1+
;;;; This file is part of Guile Netlink
2+
;;;;
3+
;;;; Copyright (C) 2025 Dale Mellor
4+
;;;; Copyright (C) 2025 Julien Lepiller <julien@lepiller.eu>
5+
;;;; 
6+
;;;; This library is free software: you can redistribute it and/or modify
7+
;;;; it under the terms of the GNU General Public License as published by
8+
;;;; the Free Software Foundation, either version 3 of the License, or
9+
;;;; (at your option) any later version.
10+
;;;;
11+
;;;; This library is distributed in the hope that it will be useful,
12+
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14+
;;;; GNU General Public License for more details.
15+
;;;;
16+
;;;; You should have received a copy of the GNU General Public License
17+
;;;; along with this library.  If not, see <https://www.gnu.org/licenses/>.
18+
19+
20+
(define-module (netlink acpi)
21+
  #:use-module (ice-9 match)
22+
  #:use-module ((netlink connection)  #:prefix NL:)
23+
  #:use-module ((netlink constant)    #:prefix NL:)
24+
  #:use-module ((netlink data)        #:prefix NL:)
25+
  #:use-module ((netlink deserialize) #:prefix NL:)
26+
  #:use-module ((netlink message)     #:prefix NL:)
27+
  #:use-module ((netlink route)       #:prefix NL:)
28+
  #:use-module ((netlink route attrs) #:prefix NL:)
29+
  #:use-module (rnrs bytevectors)
30+
  #:use-module (rnrs bytevectors gnu)  ;; For bytevector-slice.
31+
  #:export (
32+
    ;; Return accessor procedures to a port listening for power state change
33+
    ;; notifications from the kernel.  The application SHOULD (close-port _)
34+
    ;; on it when it becomes of no further use (the common exception being
35+
    ;; when the port is required for the entire lifetime of the application).
36+
    ;;
37+
    ;; This procedure returns three procedures (use (ice-9 receive) to unpack
38+
    ;; them): (read-select), (process-data ACTION), and (close-connection).
39+
    ;; The ACTION argument to the second procedure should be a procedure like
40+
    ;; (action EVENT), and the four procedures below can be used to extract
41+
    ;; data from the EVENT object.
42+
    connect-events  ;;   > [ read-selector  process-data  close-connection ]
43+
44+
    ;; The above takes a procedure ACTION, of acpi-event; the following
45+
    ;; procedures allow for the extraction of data items from such an object.
46+
    event-device-class  ;;  < event   > string
47+
    event-bus-id        ;;  < event   > string
48+
    event-kind          ;;  < event   > u32
49+
    event-data))        ;;  < event   > u32
50+
51+
52+
53+
54+
;; GLOSSARY
55+
;;
56+
;;  ACPI : advanced configuration and power interface (firmware standard)
57+
;;  ATTR : attribute
58+
;;  BV   : bytevector
59+
;;  GENL : generic netlink
60+
;;  MSG  : message
61+
;;  NL   : netlink
62+
;;  POS  : position (byte offset)
63+
64+
65+
66+
;; These are privately defined for the kernel in drivers/acpi/event.c.
67+
;;
68+
;; The terminal nulls are needed because serializing string types does not
69+
;; work properly.
70+
(define linux/ACPI_GENL_FAMILY_NAME      "acpi_event\0")
71+
(define linux/ACPI_GENL_MCAST_GROUP_NAME "acpi_mc_group\0")
72+
(define linux/ACPI_GENL_ATTR_EVENT       1)
73+
(define linux/ACPI_GENL_CMD_EVENT        1)
74+
75+
76+
(define (bytevector-string-length bv max)
77+
  "Return the position of the first null byte in the bytevector @var{bv}, up to
78+
a possible @var{max} value."
79+
  (let loop ((i 0))
80+
    (if (or (eq? max i)
81+
            (eq? 0 (array-ref bv i)))
82+
        i
83+
        (loop (1+ i)))))
84+
85+
(define (bytevector->string bv maxlen)
86+
  "Return the string corresponding to the null-terminated bytes in bytevector
87+
@var{bv}, which can hold no more than @var{maxlen} bytes."
88+
  (utf8->string (bytevector-slice bv 0 (bytevector-string-length bv maxlen))))
89+
90+
91+
;; This type, genlmsghdr, is defined at
92+
;; [kernel]/include/uapi/linux/genetlink.h.
93+
(NL:define-data-type
94+
  genlmsghdr
95+
  length-genlmsghdr
96+
  serialize-genlmsghdr
97+
  (cmd genlmsghdr-cmd genlmsghdr-type-cmd)  ;; Single byte linux/CTRL_CMD_*.
98+
  (attrs genlmsghdr-attrs genlmsghdr-type-attrs))  ;; Attributes.
99+
100+
(define (length-genlmsghdr msg)
101+
  "The length of the serialization of this header and its attributes on the wire."
102+
  (+ 4 (NL:route-attr-list-size (genlmsghdr-type-attrs msg))))
103+
104+
(define (serialize-genlmsghdr msg pos bv)
105+
  "Serialize @var{msg} to @var{pos} in the bytevector @var{bv}."
106+
  (match msg
107+
    (($ genlmsghdr-type cmd attrs)
108+
     (bytevector-u8-set! bv pos cmd)
109+
     (bytevector-u8-set! bv (1+ pos) 1)  ;; Version.
110+
     (bytevector-u16-set! bv (+ 2 pos) 0 (native-endianness)) ;; Reserved.
111+
     (NL:serialize-route-attr-list attrs (+ 4 pos) bv))))  ;; Payload
112+
113+
(define (deserialize-genlmsghdr decoder bv pos)
114+
  (make-genlmsghdr (bytevector-u8-ref bv pos)
115+
                   (NL:deserialize-attr-list 'genl-id-attr
116+
                                             decoder
117+
                                             bv
118+
                                             (+ pos 4))))
119+
120+
(NL:define-data-type
121+
  mcast-groups
122+
  length-mcast-groups
123+
  serialize-mcast-groups
124+
  (attrs mcast-groups-attrs mcast-groups-type-attrs))
125+
126+
(define (length-mcast-groups msg)
127+
  (+ 4 (NL:route-attr-list-size (mcast-groups-type-attrs msg))))
128+
129+
(define (serialize-mcast-groups msg pos bv)
130+
  (match msg
131+
    (($ mcast-groups-type attrs)
132+
     (bytevector-u16-set! bv
133+
                          pos
134+
                          (length-mcast-groups msg)
135+
                          (native-endianness))
136+
     (bytevector-u16-set! bv (+ 2 pos) 1 (native-endianness))
137+
     (NL:serialize-route-attr-list attrs (+ 4 pos) bv))))
138+
139+
(define (deserialize-mcast-groups decoder bv pos)
140+
  (let* ((length (bytevector-u16-ref bv pos (native-endianness)))
141+
         (buffer (make-bytevector (- length 4))))
142+
    (bytevector-copy! bv (+ pos 4) buffer 0 (- length 4))
143+
    (let ((ret
144+
           (make-mcast-groups
145+
            (NL:deserialize-attr-list 'mcast-groups-attr
146+
                                      decoder
147+
                                      buffer
148+
                                      0))))
149+
      ret)))
150+
151+
(NL:define-data-type
152+
  acpi-event
153+
  (lambda (msg) 44)
154+
  serialize-acpi-event
155+
  ;; This serializes like a char[20].
156+
  (device-class  event-device-class  event-type-device-class)
157+
  ;; This serializes like a char[15], followed by a slack byte(!).
158+
  (bus-id        event-bus-id        event-type-bus-id)
159+
  (kind          event-kind          event-type-kind)
160+
  (data          event-data          event-type-data))
161+
162+
;; We don't need to do this -- but it does help us understand what is
163+
;; happening.
164+
(define (serialize-acpi-event msg pos bv)
165+
  (match msg
166+
      (($ acpi-event-type device-class bus-id kind data)
167+
       (bytevector-copy! bv pos (make-bytevector 36 0) 0 36)
168+
       (let ((source (string->utf8 (event-type-device-class msg))))
169+
         (bytevector-copy! source 0 bv pos (bytevector-length source)))
170+
       (let ((source (string->utf8 (event-type-bus-id msg))))
171+
         (bytevector-copy! source 0 bv (+ pos 20) (bytevector-length source)))
172+
       (bytevector-u32-set! bv (+ pos 36) kind (native-endianness))
173+
       (bytevector-u32-set! bv (+ pos 40) data (native-endianness)))))
174+
175+
;; This fluid will be filled with the application-supplied procedure to act on
176+
;; ACPI event messages received from the kernel, before the following
177+
;; deserializer gets invoked.
178+
(define %action (make-parameter '()))
179+
180+
(define (deserialize-acpi-event decoder bv pos)
181+
  (let ((ret (make-acpi-event
182+
               (bytevector->string (bytevector-slice bv pos 20) 20)
183+
               (bytevector->string (bytevector-slice bv (+ pos 20) 15) 15)
184+
               (bytevector-u32-ref bv (+ pos 36) (native-endianness))
185+
               (bytevector-u32-ref bv (+ pos 40) (native-endianness)))))
186+
    ((%action) ret)
187+
    ret))
188+
        
189+
190+
;; These are filled in as a side effect of parsing the return from the
191+
;; NETLINK_GENERIC/GENL_ID_CTRL/CTRL_CMD_GETFAMILY message from the kernel, in
192+
;; the two specialized deserializers immediately below.
193+
(define %genl-acpi-family-id (make-parameter 0))
194+
(define %acpi-mcast-group-id (make-parameter 0))
195+
196+
(define (deserialize-mcast-grp-id decoder bv pos)
197+
  (let ((ret (NL:deserialize-route-attr-data-u32 decoder bv pos)))
198+
    (%acpi-mcast-group-id (NL:nl-data-data ret))
199+
    ret))
200+
201+
(define (deserialize-genl-family-id decoder bv pos)
202+
  (let ((ret (NL:deserialize-route-attr-data-u16 decoder bv pos)))
203+
    (%genl-acpi-family-id (NL:nl-data-data ret))
204+
    ret))
205+
206+
207+
(define genl-decoder
208+
  `(;; This is the decoding lookup table used by
209+
    ;; NL:receive-and-decode-message.
210+
    (message ,NL:deserialize-message
211+
             ;; This will catch the final acknowledgement, which we force onto
212+
             ;; all return messages.
213+
             ,@NL:%default-message-decoder
214+
             (,NL:GENL_ID_CTRL . ,deserialize-genlmsghdr))
215+
    ;; This table is used by deserialize-genlmsghdr above.
216+
    (genl-id-attr
217+
       ,(NL:deserialize-route-attr 'genl-id-attr)
218+
       (,NL:CTRL_ATTR_FAMILY_ID . ,deserialize-genl-family-id)
219+
       (,NL:CTRL_ATTR_MCAST_GROUPS . ,deserialize-mcast-groups)
220+
       (default . ,NL:deserialize-route-attr-data-bv))
221+
    ;; This table is used by deserialize-mcast-groups above.
222+
    (mcast-groups-attr
223+
      ,(NL:deserialize-route-attr 'mcast-groups-attr)
224+
      (,NL:CTRL_ATTR_MCAST_GRP_NAME . ,NL:deserialize-route-attr-data-string)
225+
      (,NL:CTRL_ATTR_MCAST_GRP_ID . ,deserialize-mcast-grp-id))))
226+
227+
(define  (connect-events)
228+
  "Return three procedures to manage a connected listener for kernel Netlink ACPI messages.
229+
230+
The procedures are, nominally (but you can call them what you want),
231+
  - (read-selector) which returns an object which can be used in the read
232+
       list of a select procedure call.
233+
  - (process-data ACTION) which applies ACTION to any ACPI events which
234+
       the kernel ??casts to us.  The ACTION procedure should be of the
235+
       form (action event), where EVENT will be an acpi-event object.
236+
  - (close-socket) which closes the connection to the kernel.
237+
238+
The application SHOULD (close-socket) when it becomes of no further use (the
239+
common exception being when the connection is required for the entire lifetime
240+
of the application)."
241+
  (define socket (NL:connect NL:NETLINK_GENERIC
242+
                             (NL:get-addr NL:AF_NETLINK 0 0)
243+
                             #:flags SOCK_NONBLOCK))
244+
245+
  (define msg (NL:make-message
246+
                NL:GENL_ID_CTRL  ;; Sub-system
247+
                NL:NLM_F_REQUEST ;; Flags
248+
                1                   ;; sequence number
249+
                0                   ;; kernel PID
250+
                (make-genlmsghdr
251+
                  NL:CTRL_CMD_GETFAMILY
252+
                  (list
253+
                    (NL:make-route-attr
254+
                      NL:CTRL_ATTR_FAMILY_NAME
255+
                      (NL:make-string-route-attr
256+
                        linux/ACPI_GENL_FAMILY_NAME))))))
257+
258+
  (NL:send-msg msg socket)
259+
260+
  ;;  These get filled in just by calling NL:receive-and-decode-message!
261+
  (parameterize ((%genl-acpi-family-id 0)
262+
                 (%acpi-mcast-group-id 0))
263+
264+
    ;;  We don??t actually care about the decoded message; we are only
265+
    ;;  interested in the side-effect of the decoding of furnishing the fluids
266+
    ;;  %genl-acpi-family-id and %acpi-mcast-group-id.
267+
    (NL:receive-and-decode-msg socket genl-decoder)
268+
269+
    ;;  Subscribe the socket to ACPI broadcasts.
270+
    (NL:add-socket-membership socket (%acpi-mcast-group-id))
271+
272+
    (values
273+
      ;; Get the read-select object.
274+
      (lambda () socket)
275+
276+
      ;; Process data at the port, passing it to (action acpi-event).
277+
      (let ((genl-acpi-family-id (%genl-acpi-family-id)))
278+
        (lambda (action)
279+
          (process-event-port socket genl-acpi-family-id action)))
280+
281+
      ;; Close the connection.
282+
      (lambda () (close-port socket)))))
283+
284+
285+
286+
(define  (process-event-port socket acpi-family-id action)
287+
  "Read Netlink messages from socket and apply ACTION to any acpi-event objects found."
288+
  (parameterize ((%action action))
289+
290+
    ;;  Once again, we are not interested in the actual decoded message, we
291+
    ;;  just use the side-effect of having ACTION run every time we decode an
292+
    ;;  acpi-event object (it happens during the deserialize-acpi-event).
293+
    (NL:receive-and-decode-msg
294+
      socket
295+
      `(;; The table used directly by NL:receive-and-decode-msg.
296+
        (message      ,NL:deserialize-message
297+
                      ,@NL:%default-message-decoder
298+
                      (,acpi-family-id . ,deserialize-genlmsghdr)
299+
                      (default . ,NL:deserialize-message-header))
300+
        ;; The table used by deserialize-genlmsghdr above.
301+
        (genl-id-attr ,(NL:deserialize-route-attr 'genl-id-attr)
302+
                      (,linux/ACPI_GENL_ATTR_EVENT . ,deserialize-acpi-event)
303+
                      (default . ,NL:deserialize-route-attr-data-bv))
304+
        ;; Needed to decode headers repeated in error messages.
305+
        (message-hdr  ,NL:deserialize-message-header)))))