guile-netlink/netlink/acpi.scm

acpi.scm

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)))))
306