GCC Code Coverage Report


Directory: ./
File: libfprint/fpi-spi-transfer.c
Date: 2024-09-16 14:36:32
Exec Total Coverage
Lines: 121 178 68.0%
Functions: 12 17 70.6%
Branches: 44 114 38.6%

Line Branch Exec Source
1 /*
2 * FPrint SPI transfer handling
3 * Copyright (C) 2019-2020 Benjamin Berg <bberg@redhat.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "fpi-spi-transfer.h"
21 #include <sys/ioctl.h>
22 #include <linux/spi/spidev.h>
23 #include <errno.h>
24
25 /* spidev can only handle the specified block size, which defaults to 4096. */
26 #define SPIDEV_BLOCK_SIZE_PARAM "/sys/module/spidev/parameters/bufsiz"
27 #define SPIDEV_BLOCK_SIZE_FALLBACK 4096
28 static gsize block_size = 0;
29
30 /**
31 * SECTION:fpi-spi-transfer
32 * @title: SPI transfer helpers
33 * @short_description: Helpers to ease SPI transfers
34 *
35 * #FpiSpiTransfer is a structure to simplify the SPI transfer handling
36 * for the linux spidev device. The main goal are to ease memory management
37 * and provide a usable asynchronous API to libfprint drivers.
38 *
39 * Currently only transfers with a write and subsequent read are supported.
40 *
41 * Drivers should always use this API rather than calling read/write/ioctl on
42 * the spidev device.
43 *
44 * Setting G_MESSAGES_DEBUG and FP_DEBUG_TRANSFER will result in the message
45 * content to be dumped.
46 */
47
48
49 G_DEFINE_BOXED_TYPE (FpiSpiTransfer, fpi_spi_transfer, fpi_spi_transfer_ref, fpi_spi_transfer_unref)
50
51 static void
52 dump_buffer (guchar *buf, gssize dump_len)
53 {
54 g_autoptr(GString) line = NULL;
55
56 line = g_string_new ("");
57 /* Dump the buffer. */
58 for (gssize i = 0; i < dump_len; i++)
59 {
60 g_string_append_printf (line, "%02x ", buf[i]);
61 if ((i + 1) % 16 == 0)
62 {
63 g_debug ("%s", line->str);
64 g_string_set_size (line, 0);
65 }
66 }
67
68 if (line->len)
69 g_debug ("%s", line->str);
70 }
71
72 static void
73 31008 log_transfer (FpiSpiTransfer *transfer, gboolean submit, GError *error)
74 {
75
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 31008 times.
31008 if (g_getenv ("FP_DEBUG_TRANSFER"))
76 {
77 if (submit)
78 {
79 g_debug ("Transfer %p submitted, write length %zd, read length %zd",
80 transfer,
81 transfer->length_wr,
82 transfer->length_rd);
83
84 if (transfer->buffer_wr)
85 dump_buffer (transfer->buffer_wr, transfer->length_wr);
86 }
87 else
88 {
89 g_autofree gchar *error_str = NULL;
90 if (error)
91 error_str = g_strdup_printf ("with error (%s)", error->message);
92 else
93 error_str = g_strdup ("successfully");
94
95 g_debug ("Transfer %p completed %s, write length %zd, read length %zd",
96 transfer,
97 error_str,
98 transfer->length_wr,
99 transfer->length_rd);
100 if (transfer->buffer_rd)
101 dump_buffer (transfer->buffer_rd, transfer->length_rd);
102 }
103 }
104 31008 }
105
106 /**
107 * fpi_spi_transfer_new:
108 * @device: The #FpDevice the transfer is for
109 * @spidev_fd: The file descriptor for the spidev device
110 *
111 * Creates a new #FpiSpiTransfer.
112 *
113 * Returns: (transfer full): A newly created #FpiSpiTransfer
114 */
115 FpiSpiTransfer *
116 15504 fpi_spi_transfer_new (FpDevice * device, int spidev_fd)
117 {
118 15504 FpiSpiTransfer *self;
119
120
1/2
✓ Branch 1 taken 15504 times.
✗ Branch 2 not taken.
15504 g_assert (FP_IS_DEVICE (device));
121
122
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15503 times.
15504 if (G_UNLIKELY (block_size == 0))
123 {
124 1 g_autoptr(GError) error = NULL;
125
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 g_autofree char *contents = NULL;
126
127 1 block_size = SPIDEV_BLOCK_SIZE_FALLBACK;
128
129
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (g_file_get_contents (SPIDEV_BLOCK_SIZE_PARAM, &contents, NULL, &error))
130 {
131 block_size = MIN (g_ascii_strtoull (contents, NULL, 0), G_MAXUINT16);
132 if (block_size == 0)
133 {
134 block_size = SPIDEV_BLOCK_SIZE_FALLBACK;
135 g_warning ("spidev blocksize could not be decoded, using %" G_GSIZE_FORMAT, block_size);
136 }
137 }
138 else
139 {
140 1 g_message ("Failed to read spidev block size, using %" G_GSIZE_FORMAT, block_size);
141 }
142 }
143
144 15504 self = g_slice_new0 (FpiSpiTransfer);
145 15504 self->ref_count = 1;
146
147 /* Purely to enhance the debug log output. */
148 15504 self->length_wr = -1;
149 15504 self->length_rd = -1;
150
151 15504 self->device = device;
152 15504 self->spidev_fd = spidev_fd;
153
154 15504 return self;
155 }
156
157 static void
158 15504 fpi_spi_transfer_free (FpiSpiTransfer *self)
159 {
160
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 g_assert (self);
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_assert_cmpint (self->ref_count, ==, 0);
162
163
2/4
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15504 times.
✗ Branch 3 not taken.
15504 if (self->free_buffer_wr && self->buffer_wr)
164 15504 self->free_buffer_wr (self->buffer_wr);
165
3/4
✓ Branch 0 taken 7392 times.
✓ Branch 1 taken 8112 times.
✓ Branch 2 taken 7392 times.
✗ Branch 3 not taken.
15504 if (self->free_buffer_rd && self->buffer_rd)
166 7392 self->free_buffer_rd (self->buffer_rd);
167 15504 self->buffer_wr = NULL;
168 15504 self->buffer_rd = NULL;
169
170 15504 g_slice_free (FpiSpiTransfer, self);
171 15504 }
172
173 /**
174 * fpi_spi_transfer_ref:
175 * @self: A #FpiSpiTransfer
176 *
177 * Increments the reference count of @self by one.
178 *
179 * Returns: (transfer full): @self
180 */
181 FpiSpiTransfer *
182 fpi_spi_transfer_ref (FpiSpiTransfer *self)
183 {
184 g_return_val_if_fail (self, NULL);
185 g_return_val_if_fail (self->ref_count, NULL);
186
187 g_atomic_int_inc (&self->ref_count);
188
189 return self;
190 }
191
192 /**
193 * fpi_spi_transfer_unref:
194 * @self: A #FpiSpiTransfer
195 *
196 * Decrements the reference count of @self by one, freeing the structure when
197 * the reference count reaches zero.
198 */
199 void
200 15504 fpi_spi_transfer_unref (FpiSpiTransfer *self)
201 {
202
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 g_return_if_fail (self);
203
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (self->ref_count);
204
205
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 if (g_atomic_int_dec_and_test (&self->ref_count))
206 15504 fpi_spi_transfer_free (self);
207 }
208
209 /**
210 * fpi_spi_transfer_write:
211 * @transfer: The #FpiSpiTransfer
212 * @length: The buffer size to allocate
213 *
214 * Prepare the write part of an SPI transfer allocating a new buffer
215 * internally that will be free'ed automatically.
216 */
217 void
218 15504 fpi_spi_transfer_write (FpiSpiTransfer *transfer,
219 gsize length)
220 {
221 15504 fpi_spi_transfer_write_full (transfer,
222 15504 g_malloc0 (length),
223 length,
224 g_free);
225 15504 }
226
227 /**
228 * fpi_spi_transfer_write_full:
229 * @transfer: The #FpiSpiTransfer
230 * @buffer: The data to write.
231 * @length: The size of @buffer
232 * @free_func: (destroy buffer): Destroy notify for @buffer
233 *
234 * Prepare the write part of an SPI transfer.
235 */
236 void
237 15504 fpi_spi_transfer_write_full (FpiSpiTransfer *transfer,
238 guint8 *buffer,
239 gsize length,
240 GDestroyNotify free_func)
241 {
242
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 g_assert (buffer != NULL);
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (transfer);
244
245 /* Write is always before read, so ensure both are NULL. */
246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (transfer->buffer_wr == NULL);
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (transfer->buffer_rd == NULL);
248
249 15504 transfer->buffer_wr = buffer;
250 15504 transfer->length_wr = length;
251 15504 transfer->free_buffer_wr = free_func;
252 }
253
254 /**
255 * fpi_spi_transfer_read:
256 * @transfer: The #FpiSpiTransfer
257 * @length: The buffer size to allocate
258 *
259 * Prepare the read part of an SPI transfer allocating a new buffer
260 * internally that will be free'ed automatically.
261 */
262 void
263 7392 fpi_spi_transfer_read (FpiSpiTransfer *transfer,
264 gsize length)
265 {
266 7392 fpi_spi_transfer_read_full (transfer,
267 7392 g_malloc0 (length),
268 length,
269 g_free);
270 7392 }
271
272 /**
273 * fpi_spi_transfer_read_full:
274 * @transfer: The #FpiSpiTransfer
275 * @buffer: Buffer to read data into.
276 * @length: The size of @buffer
277 * @free_func: (destroy buffer): Destroy notify for @buffer
278 *
279 * Prepare the read part of an SPI transfer.
280 */
281 void
282 15391 fpi_spi_transfer_read_full (FpiSpiTransfer *transfer,
283 guint8 *buffer,
284 gsize length,
285 GDestroyNotify free_func)
286 {
287
1/2
✓ Branch 0 taken 15391 times.
✗ Branch 1 not taken.
15391 g_assert (buffer != NULL);
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15391 times.
15391 g_return_if_fail (transfer);
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15391 times.
15391 g_return_if_fail (transfer->buffer_rd == NULL);
290
291 15391 transfer->buffer_rd = buffer;
292 15391 transfer->length_rd = length;
293 15391 transfer->free_buffer_rd = free_func;
294 }
295
296 static void
297 15504 transfer_finish_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
298 {
299 15504 GTask *task = G_TASK (res);
300 15504 FpiSpiTransfer *transfer = g_task_get_task_data (task);
301 15504 GError *error = NULL;
302 15504 FpiSpiTransferCallback callback;
303
304 15504 g_task_propagate_boolean (task, &error);
305
306 15504 log_transfer (transfer, FALSE, error);
307
308 15504 callback = transfer->callback;
309 15504 transfer->callback = NULL;
310 15504 callback (transfer, transfer->device, transfer->user_data, error);
311 15504 }
312
313 static int
314 15504 transfer_chunk (FpiSpiTransfer *transfer, gsize full_length, gsize *transferred)
315 {
316 15504 struct spi_ioc_transfer xfer[2] = { 0 };
317 15504 gsize skip = *transferred;
318 15504 gsize len = 0;
319 15504 int transfers = 0;
320 15504 int status;
321
322
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 if (transfer->buffer_wr)
323 {
324
2/4
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15504 times.
✗ Branch 3 not taken.
15504 if (skip < transfer->length_wr && len < block_size)
325 {
326 15504 xfer[transfers].tx_buf = (gsize) transfer->buffer_wr + skip;
327 15504 xfer[transfers].len = MIN (block_size, transfer->length_wr - skip);
328
329 15504 len += xfer[transfers].len;
330 15504 skip += xfer[transfers].len;
331
332 15504 transfers += 1;
333 }
334
335 /* How much we need to skip in the next transfer. */
336 15504 skip -= transfer->length_wr;
337 }
338
339
2/2
✓ Branch 0 taken 15391 times.
✓ Branch 1 taken 113 times.
15504 if (transfer->buffer_rd)
340 {
341
2/4
✓ Branch 0 taken 15391 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15391 times.
✗ Branch 3 not taken.
15391 if (skip < transfer->length_rd && len < block_size)
342 {
343 15391 xfer[transfers].rx_buf = (gsize) transfer->buffer_rd + skip;
344 15391 xfer[transfers].len = MIN (block_size, transfer->length_rd - skip);
345
346 15391 len += xfer[transfers].len;
347 /* skip += xfer[transfers].len; */
348
349 15391 transfers += 1;
350 }
351
352 /* How much we need to skip in the next transfer. */
353 /* skip -= transfer->length_rd; */
354 }
355
356
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 113 times.
15504 g_assert (transfers > 0);
357
358 /* We have not transferred everything; ask driver to not deselect the chip.
359 * Unfortunately, this is inherently racy in case there are further devices
360 * on the same bus. In practice, it is hopefully unlikely to be an issue,
361 * but print a message once to help with debugging.
362 */
363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 if (full_length < *transferred + len)
364 {
365 static gboolean warned = FALSE;
366
367 if (!warned)
368 {
369 g_message ("Split SPI transfer. In case of issues, try increasing the spidev buffer size.");
370 warned = TRUE;
371 }
372
373 xfer[transfers - 1].cs_change = TRUE;
374 }
375
376 /* This ioctl cannot be interrupted. */
377 15504 status = ioctl (transfer->spidev_fd, SPI_IOC_MESSAGE (transfers), xfer);
378
379
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 if (status >= 0)
380 15504 *transferred += len;
381
382 15504 return status;
383 }
384
385 static void
386 15504 transfer_thread_func (GTask *task,
387 gpointer source_object,
388 gpointer task_data,
389 GCancellable *cancellable)
390 {
391 15504 FpiSpiTransfer *transfer = (FpiSpiTransfer *) task_data;
392 15504 gsize full_length;
393 15504 gsize transferred = 0;
394 15504 int status = 0;
395
396
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
15504 if (transfer->buffer_wr == NULL && transfer->buffer_rd == NULL)
397 {
398 g_task_return_new_error (task,
399 G_IO_ERROR,
400 G_IO_ERROR_INVALID_ARGUMENT,
401 "Transfer with neither write or read!");
402 return;
403 }
404
405 15504 full_length = 0;
406
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 if (transfer->buffer_wr)
407 15504 full_length += transfer->length_wr;
408
2/2
✓ Branch 0 taken 15391 times.
✓ Branch 1 taken 113 times.
15504 if (transfer->buffer_rd)
409 15391 full_length += transfer->length_rd;
410
411
2/2
✓ Branch 0 taken 15504 times.
✓ Branch 1 taken 15504 times.
31008 while (transferred < full_length && status >= 0)
412 15504 status = transfer_chunk (transfer, full_length, &transferred);
413
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 if (status < 0)
415 {
416 g_task_return_new_error (task,
417 G_IO_ERROR,
418 g_io_error_from_errno (errno),
419 "Error invoking ioctl for SPI transfer (%d)",
420 errno);
421 }
422 else
423 {
424 15504 g_task_return_boolean (task, TRUE);
425 }
426 }
427
428 /**
429 * fpi_spi_transfer_submit:
430 * @transfer: (transfer full): The transfer to submit, must have been filled.
431 * @cancellable: Cancellable to use, e.g. fpi_device_get_cancellable()
432 * @callback: Callback on completion or error
433 * @user_data: Data to pass to callback
434 *
435 * Submit an SPI transfer with a specific timeout and callback functions.
436 *
437 * The underlying transfer cannot be cancelled. The current implementation
438 * will only call @callback after the transfer has been completed.
439 *
440 * Note that #FpiSpiTransfer will be stolen when this function is called.
441 * So that all associated data will be free'ed automatically, after the
442 * callback ran unless fpi_usb_transfer_ref() is explicitly called.
443 */
444 void
445 15504 fpi_spi_transfer_submit (FpiSpiTransfer *transfer,
446 GCancellable *cancellable,
447 FpiSpiTransferCallback callback,
448 gpointer user_data)
449 {
450 31008 g_autoptr(GTask) task = NULL;
451
452
1/2
✓ Branch 0 taken 15504 times.
✗ Branch 1 not taken.
15504 g_return_if_fail (transfer);
453
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (callback);
454
455 /* Recycling is allowed, but not two at the same time. */
456
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15504 times.
15504 g_return_if_fail (transfer->callback == NULL);
457
458 15504 transfer->callback = callback;
459 15504 transfer->user_data = user_data;
460
461 15504 log_transfer (transfer, TRUE, NULL);
462
463 15504 task = g_task_new (transfer->device,
464 cancellable,
465 transfer_finish_cb,
466 NULL);
467 15504 g_task_set_task_data (task,
468 g_steal_pointer (&transfer),
469 (GDestroyNotify) fpi_spi_transfer_unref);
470
471
1/2
✓ Branch 1 taken 15504 times.
✗ Branch 2 not taken.
15504 g_task_run_in_thread (task, transfer_thread_func);
472 }
473
474 /**
475 * fpi_spi_transfer_submit_sync:
476 * @transfer: The transfer to submit, must have been filled.
477 * @error: Location to store #GError to
478 *
479 * Synchronously submit an SPI transfer. Use of this function is discouraged
480 * as it will block all other operations in the application.
481 *
482 * Note that you still need to fpi_spi_transfer_unref() the
483 * #FpiSpiTransfer afterwards.
484 *
485 * Returns: #TRUE on success, otherwise #FALSE and @error will be set
486 */
487 gboolean
488 fpi_spi_transfer_submit_sync (FpiSpiTransfer *transfer,
489 GError **error)
490 {
491 g_autoptr(GTask) task = NULL;
492 GError *err = NULL;
493 gboolean res;
494
495 g_return_val_if_fail (transfer, FALSE);
496
497 /* Recycling is allowed, but not two at the same time. */
498 g_return_val_if_fail (transfer->callback == NULL, FALSE);
499
500 log_transfer (transfer, TRUE, NULL);
501
502 task = g_task_new (transfer->device,
503 NULL,
504 NULL,
505 NULL);
506 g_task_set_task_data (task,
507 fpi_spi_transfer_ref (transfer),
508 (GDestroyNotify) fpi_spi_transfer_unref);
509
510 g_task_run_in_thread_sync (task, transfer_thread_func);
511
512 res = g_task_propagate_boolean (task, &err);
513
514 log_transfer (transfer, FALSE, err);
515
516 g_propagate_error (error, err);
517
518 return res;
519 }
520