GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 72.0% 121 / 0 / 168
Functions: 75.0% 12 / 0 / 16
Branches: 40.7% 44 / 0 / 108

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