forked from Xilinx/SLASH
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslash_qdma.c
More file actions
2496 lines (2211 loc) · 94.2 KB
/
Copy pathslash_qdma.c
File metadata and controls
2496 lines (2211 loc) · 94.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved.
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* @file slash_qdma.c
*
* QDMA (Queue-based DMA) subsystem for the SLASH FPGA driver.
*
* This file implements the QDMA data-plane for SLASH, an AMD Alveo V80
* partial-reconfiguration FPGA design. It wraps the Xilinx libqdma
* library (from submodules/qdma_drv/QDMA/linux-kernel/driver/libqdma/)
* to provide queue-pair-based DMA transfers between host memory and the
* FPGA fabric.
*
* The QDMA subsystem binds to PF1 (PCI device ID 0x50B5), while the
* control device (slash_ctldev) binds to PF2 (device ID 0x50B6).
*
* Queue pair lifecycle:
* add -> start -> I/O (via anon_inode fd) -> stop -> del
*
* Key design decisions:
* - **Poll mode** (no interrupts): avoids interrupt overhead for
* streaming workloads; the host polls HW-written completion status.
* - **Synchronous transfers**: qdma_request_submit() blocks until the
* DMA completes or times out (10 s default).
* - **XArray for qpair tracking**: provides dynamic ID allocation,
* built-in locking, and automatic index management for up to 256
* concurrent queue pairs.
* - **Reference counting**: kref on both the device and each qpair
* entry; the anon_inode fd holds a ref, preventing premature
* destruction while userspace still has the fd open.
*/
#include "slash_qdma.h"
#include "libqdma_export.h"
#include "slash.h"
#include <asm/cacheflush.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/xarray.h>
#include <linux/anon_inodes.h>
/*
* Direction bitmask constants.
*
* These map 1:1 with the libqdma queue_type_t enum values (Q_H2C,
* Q_C2H, Q_CMPT) but expressed as bit positions so they can be
* OR'd together in a single dir_mask field.
*
* SLASH_QDMA_DIR_H2C — Host-to-Card (write path)
* SLASH_QDMA_DIR_C2H — Card-to-Host (read path)
* SLASH_QDMA_DIR_CMPT — Completion queue (status/metadata from card)
*/
#define SLASH_QDMA_DIR_H2C BIT(0)
#define SLASH_QDMA_DIR_C2H BIT(1)
#define SLASH_QDMA_DIR_CMPT BIT(2)
#define SLASH_QDMA_DIR_MASK (SLASH_QDMA_DIR_H2C | SLASH_QDMA_DIR_C2H | \
SLASH_QDMA_DIR_CMPT)
/**
* SLASH_QDMA_QTYPE_COUNT - Number of queue types tracked per queue pair.
*
* Equals Q_CMPT + 1 (i.e., 3): one slot each for H2C, C2H, and CMPT.
* Used to size the per-qpair qhndl[] array.
*/
#define SLASH_QDMA_QTYPE_COUNT (Q_CMPT + 1)
/**
* SLASH_QDMA_MAX_QPAIRS - Maximum number of simultaneous queue pairs.
*
* This matches the conf.qsets_max value passed to qdma_device_open()
* in slash_qdma_conf_options(), keeping the xarray ID space and the
* HW queue-set limit in sync.
*/
#define SLASH_QDMA_MAX_QPAIRS 256
/**
* SLASH_QDMA_QPAIR_ID_RANGE - XArray allocation range for qpair IDs.
*
* Constrains xa_alloc() to assign IDs in [0, 255]. The xarray handles
* thread-safe allocation and lookup of queue pair entries within this
* range.
*/
#define SLASH_QDMA_QPAIR_ID_RANGE XA_LIMIT(0, SLASH_QDMA_MAX_QPAIRS - 1)
/*
* Debug logging infrastructure.
*
* When SLASH_QDMA_OP_DEBUG is non-zero (compile-time flag), every
* libqdma call and state transition is logged via pr_info / dev_info.
* In production builds the macros expand to nothing to avoid log spam.
*/
#ifndef SLASH_QDMA_OP_DEBUG
#define SLASH_QDMA_OP_DEBUG 0
#endif
#if SLASH_QDMA_OP_DEBUG
#define SLASH_QDMA_OP_LOG(fmt, ...) \
pr_info("slash: qdma: " fmt, ##__VA_ARGS__)
#define SLASH_QDMA_OP_DEV_LOG(dev, fmt, ...) \
dev_info((dev), "slash: qdma: " fmt, ##__VA_ARGS__)
#else
#define SLASH_QDMA_OP_LOG(fmt, ...) \
do { \
} while (0)
#define SLASH_QDMA_OP_DEV_LOG(dev, fmt, ...) \
do { \
} while (0)
#endif
/* Forward declaration; full definition follows. */
struct slash_qdma_dev;
/**
* struct slash_qdma_qpair_entry - Per-queue-pair state.
* @ref: Reference count. Starts at 1 (held by the xarray slot);
* an additional ref is taken when an anon_inode fd is handed
* to userspace, so the entry outlives the xarray removal if
* the fd is still open.
* @qhndl: Array of libqdma queue handles, one per queue type
* (Q_H2C, Q_C2H, Q_CMPT). Entries that are not in use
* hold the sentinel QDMA_QUEUE_IDX_INVALID.
* @dir_mask: Bitmask of active directions (SLASH_QDMA_DIR_H2C, etc.).
* Updated as individual queues are added or removed.
* @mode: Queue operating mode (QDMA_Q_MODE_MM or QDMA_Q_MODE_ST).
* @irq_mode: Interrupt mode. Currently always 0 (poll mode).
* @irq_vector: MSI-X vector assignment. Currently unused (poll mode).
*/
struct slash_qdma_qpair_entry {
struct kref ref;
unsigned long qhndl[SLASH_QDMA_QTYPE_COUNT];
u32 dir_mask;
enum qdma_q_mode mode;
u32 irq_mode;
u32 irq_vector;
};
/**
* struct slash_qdma_dev - Per-PCI-device QDMA state.
* @pdev: The PCI device (PF1) this instance is bound to.
* @qdma_handle: Opaque handle returned by qdma_device_open();
* passed to every subsequent libqdma call.
* @misc: Miscdevice registered under /dev/slash_qdma_ctlN.
* Userspace opens this to issue queue management ioctls.
* @ref: Device-level reference count. The miscdevice open
* path and each anon_inode fd hold a ref; the device
* structure is freed when the last ref drops.
* @lock: Serialises ioctl paths and protects @qpairs,
* @hw_shutdown, and @have_qdma_handle.
* @qpairs: XArray mapping qpair IDs (u32) to
* &struct slash_qdma_qpair_entry pointers. Using an
* xarray gives us O(1) lookup, thread-safe auto-ID
* allocation, and safe concurrent iteration during
* teardown.
* @have_qdma_handle: True once qdma_device_open() succeeds; false after
* qdma_device_close(). Guards against use-after-close.
* @is_misc_registered: True while the miscdevice is live. Prevents double
* deregistration on error paths.
* @hw_shutdown: Set to true during destroy to signal that the HW is
* going away. Any ioctl arriving after this flag is
* set returns -ENODEV immediately.
*
* The three booleans (@have_qdma_handle, @is_misc_registered,
* @hw_shutdown) track partially-constructed state during probe/remove
* error paths; outside of create/destroy they should always reflect a
* fully initialised device.
*/
struct slash_qdma_dev {
struct pci_dev *pdev;
unsigned long qdma_handle;
struct miscdevice misc;
struct kref ref;
struct mutex lock;
struct xarray qpairs;
/*
* Initialization booleans.
* Assume these are always true outside of create/destroy.
*/
bool have_qdma_handle;
bool is_misc_registered;
bool hw_shutdown;
};
/**
* typedef slash_qdma_queue_cmd_fn - Function pointer for queue lifecycle ops.
*
* Matches the signature of qdma_queue_start(), qdma_queue_stop(), and
* slash_qdma_queue_remove_safe(), allowing slash_qdma_ioctl_qpair_op_apply()
* to iterate over all directions in a queue pair and apply the same
* operation generically.
*/
typedef int (*slash_qdma_queue_cmd_fn)(unsigned long qdma_handle,
unsigned long qhndl,
char *errbuf,
int errbuf_sz);
/* Forward declaration — defined below after its helper functions. */
static int slash_qdma_queue_remove_safe(unsigned long qdma_handle,
unsigned long qhndl,
char *errbuf,
int errbuf_sz);
/* ─────────────────────────────────────────────────────────────────────
* Direction / queue-type conversion helpers
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_dir_to_qtype() - Convert a direction bitmask bit to a queue type.
* @dir_bit: Exactly one of SLASH_QDMA_DIR_H2C, _C2H, or _CMPT.
*
* Return: The corresponding libqdma queue_type_t value.
*
* Note: currently unused (hence __attribute__((unused))), but kept as
* the inverse of slash_qdma_qtype_to_dir() for completeness.
*/
__attribute__((unused))
static enum queue_type_t slash_qdma_dir_to_qtype(u32 dir_bit)
{
switch (dir_bit) {
case SLASH_QDMA_DIR_H2C:
return Q_H2C;
case SLASH_QDMA_DIR_C2H:
return Q_C2H;
case SLASH_QDMA_DIR_CMPT:
return Q_CMPT;
default:
return Q_H2C; /* should never reach */
}
}
/**
* slash_qdma_qtype_to_dir() - Convert a queue type to its direction bitmask bit.
* @qtype: One of Q_H2C, Q_C2H, or Q_CMPT.
*
* Return: The corresponding SLASH_QDMA_DIR_* bitmask value, or 0 for
* an unrecognised queue type.
*/
static u32 slash_qdma_qtype_to_dir(enum queue_type_t qtype)
{
switch (qtype) {
case Q_H2C:
return SLASH_QDMA_DIR_H2C;
case Q_C2H:
return SLASH_QDMA_DIR_C2H;
case Q_CMPT:
return SLASH_QDMA_DIR_CMPT;
default:
return 0;
}
}
/**
* slash_qdma_qhndl_is_valid() - Check if a queue handle is valid.
* @qhndl: Queue handle from libqdma.
*
* Return: true if @qhndl is not the sentinel QDMA_QUEUE_IDX_INVALID,
* meaning the queue has been successfully added to the HW.
*/
static inline bool slash_qdma_qhndl_is_valid(unsigned long qhndl)
{
return qhndl != QDMA_QUEUE_IDX_INVALID;
}
/* ─────────────────────────────────────────────────────────────────────
* Queue removal with state-machine safety
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_queue_remove_safe() - Stop-then-remove a queue, handling any state.
* @qdma_handle: Device handle from qdma_device_open().
* @qhndl: Queue handle to remove.
* @errbuf: Buffer for libqdma error messages.
* @errbuf_sz: Size of @errbuf.
*
* The QDMA HW queue state machine requires that an ONLINE queue be
* stopped before it can be removed. This helper queries the current
* state and performs the correct transitions:
*
* - Q_STATE_ONLINE -> stop, then remove
* - Q_STATE_ENABLED -> remove directly (already stopped)
* - Q_STATE_DISABLED -> no-op (already removed)
* - anything else -> return -EINVAL
*
* This "check-before-stop" pattern prevents errors from trying to stop
* an already-stopped queue or remove an already-removed one, which is
* important during teardown where we may not know the current state.
*
* Return: 0 on success, negative errno on failure.
*/
static int slash_qdma_queue_remove_safe(unsigned long qdma_handle,
unsigned long qhndl,
char *errbuf,
int errbuf_sz)
{
struct qdma_q_state qstate = {0};
int err;
if (!errbuf || errbuf_sz <= 0)
return -EINVAL;
errbuf[0] = '\0';
/* Query the current HW queue state */
SLASH_QDMA_OP_LOG("qdma_get_queue_state start: handle=%lu qhndl=%lu\n",
qdma_handle, qhndl);
err = qdma_get_queue_state(qdma_handle, qhndl, &qstate, errbuf, errbuf_sz);
if (err) {
SLASH_QDMA_OP_LOG("qdma_get_queue_state failed: qhndl=%lu err=%d (%s)\n",
qhndl, err, errbuf);
return err;
}
SLASH_QDMA_OP_LOG("qdma_get_queue_state done: qhndl=%lu state=%u\n",
qhndl, qstate.qstate);
switch (qstate.qstate) {
case Q_STATE_ONLINE:
/* Queue is active — must stop before removing. */
SLASH_QDMA_OP_LOG("qdma_queue_stop start: qhndl=%lu\n", qhndl);
err = qdma_queue_stop(qdma_handle, qhndl, errbuf, errbuf_sz);
if (err) {
SLASH_QDMA_OP_LOG("qdma_queue_stop failed: qhndl=%lu err=%d (%s)\n",
qhndl, err, errbuf);
return err;
}
SLASH_QDMA_OP_LOG("qdma_queue_stop done: qhndl=%lu\n", qhndl);
break;
case Q_STATE_ENABLED:
/* Queue is added but not started — can remove directly. */
break;
case Q_STATE_DISABLED:
/* Queue is already removed. */
SLASH_QDMA_OP_LOG("queue already disabled, skip remove: qhndl=%lu\n",
qhndl);
return 0;
default:
snprintf(errbuf, errbuf_sz, "queue in unexpected state %u",
qstate.qstate);
SLASH_QDMA_OP_LOG("qdma_get_queue_state unexpected state: qhndl=%lu state=%u\n",
qhndl, qstate.qstate);
return -EINVAL;
}
/* State is now ENABLED — safe to remove. */
SLASH_QDMA_OP_LOG("qdma_queue_remove start: qhndl=%lu\n", qhndl);
err = qdma_queue_remove(qdma_handle, qhndl, errbuf, errbuf_sz);
if (err) {
SLASH_QDMA_OP_LOG("qdma_queue_remove failed: qhndl=%lu err=%d (%s)\n",
qhndl, err, errbuf);
return err;
}
SLASH_QDMA_OP_LOG("qdma_queue_remove done: qhndl=%lu\n", qhndl);
return 0;
}
/* ─────────────────────────────────────────────────────────────────────
* Queue pair xarray helpers (lookup, refcount, insert, remove)
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_qpair_lookup() - Find a qpair entry by ID.
* @qdma_dev: QDMA device whose xarray to search.
* @qid: Queue pair ID.
*
* Return: Pointer to the entry, or NULL if @qid is not allocated.
*
* Note: the caller must hold @qdma_dev->lock or otherwise guarantee
* that the entry will not be freed during use (e.g., by holding a ref).
*/
static inline struct slash_qdma_qpair_entry *
slash_qdma_qpair_lookup(struct slash_qdma_dev *qdma_dev, u32 qid)
{
return xa_load(&qdma_dev->qpairs, qid);
}
/**
* slash_qdma_qpair_entry_release() - kref release callback for qpair entries.
* @ref: kref embedded in the slash_qdma_qpair_entry being released.
*
* Called when the last reference to a qpair entry is dropped. Frees
* the entry structure. By this point, all associated HW queues must
* already have been removed.
*/
static void slash_qdma_qpair_entry_release(struct kref *ref)
{
struct slash_qdma_qpair_entry *entry =
container_of(ref, struct slash_qdma_qpair_entry, ref);
kfree(entry);
}
/**
* slash_qdma_qpair_get() - Acquire a reference on a qpair entry.
* @entry: The entry to reference.
*
* Used when handing out an anon_inode fd so the entry survives until
* the fd is closed, even if the qpair is deleted from the xarray.
*/
static inline void slash_qdma_qpair_get(struct slash_qdma_qpair_entry *entry)
{
kref_get(&entry->ref);
}
/**
* slash_qdma_qpair_put() - Release a reference on a qpair entry.
* @entry: The entry to dereference.
*
* When the last reference drops, the entry is freed via
* slash_qdma_qpair_entry_release().
*/
static inline void slash_qdma_qpair_put(struct slash_qdma_qpair_entry *entry)
{
kref_put(&entry->ref, slash_qdma_qpair_entry_release);
}
/**
* slash_qdma_qpair_insert() - Allocate a new qpair ID and insert the entry.
* @qdma_dev: QDMA device whose xarray receives the entry.
* @entry: The new entry to insert. Its kref is initialised here.
* @id: [out] The auto-assigned queue pair ID.
*
* Uses xa_alloc() to atomically pick the lowest available ID in
* [0, SLASH_QDMA_MAX_QPAIRS-1] and store @entry at that index.
*
* Return: 0 on success, -EBUSY if all 256 IDs are in use, or other
* negative errno.
*/
static inline int
slash_qdma_qpair_insert(struct slash_qdma_dev *qdma_dev, struct slash_qdma_qpair_entry *entry, u32 *id)
{
kref_init(&entry->ref);
return xa_alloc(&qdma_dev->qpairs, id, entry, SLASH_QDMA_QPAIR_ID_RANGE, GFP_KERNEL);
}
/**
* slash_qdma_qpair_remove() - Erase a qpair from the xarray and drop its ref.
* @qdma_dev: QDMA device.
* @qid: Queue pair ID to remove.
*
* After this call, the ID is available for reuse. The entry itself is
* only freed when all references (including any held by open fds) are
* released.
*/
static inline void
slash_qdma_qpair_remove(struct slash_qdma_dev *qdma_dev, u32 qid)
{
struct slash_qdma_qpair_entry *entry;
entry = xa_erase(&qdma_dev->qpairs, qid);
if (entry)
slash_qdma_qpair_put(entry);
}
/* ─────────────────────────────────────────────────────────────────────
* Anon-inode file context and I/O control block
* ───────────────────────────────────────────────────────────────────── */
/**
* struct slash_qdma_qpair_file_ctx - Private data for an anon_inode qpair fd.
* @qdma_dev: Back-pointer to the owning QDMA device (ref held).
* @entry: The queue pair entry this fd operates on (ref held).
* @qid: Queue pair ID, cached for debug logging.
*
* Allocated in slash_qdma_ioctl_qpair_get_fd_w() and freed in
* slash_qdma_qpair_release(). Both @qdma_dev and @entry have their
* reference counts incremented when the ctx is created, and decremented
* when the fd is closed.
*/
struct slash_qdma_qpair_file_ctx {
struct slash_qdma_dev *qdma_dev;
struct slash_qdma_qpair_entry *entry;
u32 qid;
};
/**
* struct slash_qdma_io_cb - I/O control block for a single DMA transfer.
* @buf: User-space buffer address (source for H2C, destination for C2H).
* @len: Transfer length in bytes.
* @pages_nr: Number of user pages pinned by get_user_pages_fast().
* @sgl: Scatter-gather list of qdma_sw_sg entries, one per pinned page.
* Allocated as a single contiguous block together with @pages.
* @pages: Array of struct page pointers for the pinned user pages.
* Points into the same allocation as @sgl (immediately after it).
* @req: The libqdma request structure submitted to qdma_request_submit().
*
* This is a stack-local structure (allocated in slash_qdma_qpair_read_write)
* that bundles all per-transfer state. The SGL and page array are heap-
* allocated in slash_qdma_map_user_buf_to_sgl() and freed in
* slash_qdma_iocb_release().
*/
struct slash_qdma_io_cb {
void __user *buf;
size_t len;
unsigned int pages_nr;
struct qdma_sw_sg *sgl;
struct page **pages;
struct qdma_request req;
};
/* ─────────────────────────────────────────────────────────────────────
* Forward declarations
* ───────────────────────────────────────────────────────────────────── */
static int slash_qdma_probe(struct pci_dev *pdev, const struct pci_device_id *id);
static void slash_qdma_remove(struct pci_dev *pdev);
static int slash_qdma_create_qdma_device(struct pci_dev *pdev, struct slash_qdma_dev **pdevice);
static void slash_qdma_destroy_qdma_device(struct slash_qdma_dev *device);
static void slash_qdma_dev_release(struct kref *ref);
static void slash_qdma_conf_options(struct qdma_dev_conf *conf, struct pci_dev *pdev);
static int slash_qdma_ioctl_info_w(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
void __user *uarg);
static int slash_qdma_ioctl_qpair_add_w(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
void __user *uarg);
static int slash_qdma_ioctl_qpair_add(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
struct slash_qdma_qpair_add *req);
static int slash_qdma_ioctl_qpair_add_q(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
struct slash_qdma_qpair_add *req,
struct slash_qdma_qpair_entry *entry,
enum queue_type_t qtype);
static void slash_qdma_ioctl_qpair_rm_q(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
struct slash_qdma_qpair_entry *entry,
enum queue_type_t qtype);
static int slash_qdma_ioctl_qpair_op_w(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
void __user *uarg);
static int slash_qdma_ioctl_qpair_op(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
struct slash_qdma_qpair_op *req);
static int slash_qdma_ioctl_qpair_op_apply(struct slash_qdma_dev *qdma_dev,
struct slash_qdma_qpair_entry *entry,
struct slash_qdma_qpair_op *req,
slash_qdma_queue_cmd_fn fn,
const char *op_name,
bool stop_on_err);
static int slash_qdma_ioctl_qpair_get_fd_w(struct miscdevice *misc,
struct slash_qdma_dev *qdma_dev,
void __user *uarg);
static ssize_t slash_qdma_qpair_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos);
static ssize_t slash_qdma_qpair_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos);
static int slash_qdma_qpair_release(struct inode *inode, struct file *file);
static long slash_qdma_qpair_ioctl(struct file *file,
unsigned int cmd, unsigned long arg);
/**
* slash_qdma_qpair_fops - File operations for per-qpair anon_inode fds.
*
* read() performs a C2H (card-to-host) DMA transfer.
* write() performs an H2C (host-to-card) DMA transfer.
* llseek uses default_llseek so that pread/pwrite can set the
* device-side address via the file position.
* ioctl is a stub that returns -ENOTTY (no per-fd ioctls defined yet).
* release drops the refs on the qpair entry and device.
*/
static const struct file_operations slash_qdma_qpair_fops = {
.owner = THIS_MODULE,
.read = slash_qdma_qpair_read,
.write = slash_qdma_qpair_write,
.unlocked_ioctl = slash_qdma_qpair_ioctl,
.release = slash_qdma_qpair_release,
.llseek = default_llseek,
};
static int slash_qdma_fop_open(struct inode *inode, struct file *file);
static int slash_qdma_fop_release(struct inode *inode, struct file *file);
static long slash_qdma_fop_ioctl(struct file *file, unsigned int op, unsigned long arg);
static void slash_qdma_ioctl_info(struct miscdevice *misc, struct slash_qdma_dev *qdma_dev, struct slash_qdma_info *qdma_info);
/**
* slash_qdma_ids - PCI device ID table for the QDMA PF.
*
* Matches only PF1 (device ID 0x50B5) on AMD/Xilinx V80 cards.
*/
static const struct pci_device_id slash_qdma_ids[] = {
{PCI_DEVICE(SLASH_QDMA_PCI_VENDOR_ID, SLASH_QDMA_PCI_DEVICE_ID)},
{0,}
};
MODULE_DEVICE_TABLE(pci, slash_qdma_ids);
/**
* slash_qdma_driver - PCI driver structure for the QDMA subsystem.
*
* Registered in slash_qdma_init(); triggers slash_qdma_probe() for each
* matching PF1 device discovered during PCI enumeration.
*/
static struct pci_driver slash_qdma_driver = {
.name = SLASH_QDMA_DRV_NAME,
.id_table = slash_qdma_ids,
.probe = slash_qdma_probe,
.remove = slash_qdma_remove,
};
/**
* slash_qdma_fops - File operations for the QDMA control miscdevice.
*
* The miscdevice (/dev/slash_qdma_ctlN) is the management interface:
* userspace opens it and issues ioctls to add/start/stop/delete queue
* pairs and to obtain per-qpair I/O fds.
*/
static struct file_operations slash_qdma_fops = {
.owner = THIS_MODULE,
.open = slash_qdma_fop_open,
.release = slash_qdma_fop_release,
.unlocked_ioctl = slash_qdma_fop_ioctl,
};
/* ─────────────────────────────────────────────────────────────────────
* BDF-to-device-number map (stable /dev/slash_qdma_ctlN across hotplug)
* ───────────────────────────────────────────────────────────────────── */
/**
* struct slash_qdma_id_entry - Stable BDF-to-number mapping entry.
* @node: Intrusive list linkage for @slash_qdma_id_map.
* @bdf: Full PCI BDF string including function (e.g. "0000:61:00.1").
* @number: The /dev/slash_qdma_ctl<N> suffix permanently assigned to this BDF.
* @in_use: True while the device is bound to the driver. Cleared on remove,
* set on probe. A probe that finds @in_use already true indicates
* the kernel handed us a device that was never properly unbound —
* this should never happen under normal operation.
*
* Entries are allocated in probe and intentionally never freed. They survive
* hotplug remove+rescan cycles so that a device always gets back the same N.
*/
struct slash_qdma_id_entry {
struct list_head node;
char bdf[32]; /* "DDDD:BB:SS.F\0" fits comfortably in 32 bytes */
int number;
bool in_use;
};
/** Persistent BDF-to-number map; entries live for the module's lifetime. */
static LIST_HEAD(slash_qdma_id_map);
/** Serialises all accesses to @slash_qdma_id_map and @in_use fields. */
static DEFINE_MUTEX(slash_qdma_id_map_lock);
/** Source of new numbers; only incremented when a BDF is seen for the first time. */
static atomic_t slash_qdma_devcount = ATOMIC_INIT(0);
/**
* slash_qdma_id_get() - Look up or allocate a stable number for a BDF.
* @bdf: Full PCI BDF string (e.g. "0000:61:00.1") from pci_name().
*
* Called from probe. Returns the number permanently associated with @bdf,
* allocating a new one if this BDF is seen for the first time. Also marks
* the entry as in_use = true.
*
* If an existing entry is found with in_use already set, the device was
* never properly unbound before probe was called again — this indicates a
* kernel PCI driver bug. The function logs a loud error and returns
* -EBUSY so that probe aborts without touching the device.
*
* Return: non-negative stable device number on success, negative errno on
* failure (-ENOMEM if allocation fails, -EBUSY if already in use).
*/
static int slash_qdma_id_get(const char *bdf)
{
struct slash_qdma_id_entry *entry;
int number;
mutex_lock(&slash_qdma_id_map_lock);
list_for_each_entry(entry, &slash_qdma_id_map, node) {
if (strcmp(entry->bdf, bdf) != 0)
continue;
if (entry->in_use) {
pr_err("slash_qdma: BUG: probe called for %s but entry is already in_use "
"(number=%d); refusing to bind\n", bdf, entry->number);
mutex_unlock(&slash_qdma_id_map_lock);
return -EBUSY;
}
entry->in_use = true;
number = entry->number;
mutex_unlock(&slash_qdma_id_map_lock);
pr_info("slash_qdma: reusing number %d for %s\n", number, bdf);
return number;
}
/* First time we've seen this BDF — allocate a fresh entry. */
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
mutex_unlock(&slash_qdma_id_map_lock);
return -ENOMEM;
}
strscpy(entry->bdf, bdf, sizeof(entry->bdf));
entry->number = atomic_inc_return(&slash_qdma_devcount) - 1;
entry->in_use = true;
list_add_tail(&entry->node, &slash_qdma_id_map);
number = entry->number;
mutex_unlock(&slash_qdma_id_map_lock);
pr_info("slash_qdma: assigned number %d to %s\n", number, bdf);
return number;
}
/**
* slash_qdma_id_release() - Mark a BDF's entry as no longer in use.
* @bdf: Full PCI BDF string passed to the matching slash_qdma_id_get() call.
*
* Called when the misc device is deregistered (remove path, or probe error
* unwind after misc_register succeeds). Clears in_use so that the next probe
* for the same BDF can reuse the stored number. The entry itself is not freed.
*
* If no entry exists for @bdf (should never happen after a successful probe),
* the call is a no-op and a warning is logged.
*/
static void slash_qdma_id_release(const char *bdf)
{
struct slash_qdma_id_entry *entry;
mutex_lock(&slash_qdma_id_map_lock);
list_for_each_entry(entry, &slash_qdma_id_map, node) {
if (strcmp(entry->bdf, bdf) != 0)
continue;
entry->in_use = false;
mutex_unlock(&slash_qdma_id_map_lock);
pr_info("slash_qdma: released number %d for %s\n", entry->number, bdf);
return;
}
/* Should be unreachable: release without a prior successful id_get. */
pr_warn("slash_qdma: WARNING: release called for %s but no entry found\n", bdf);
mutex_unlock(&slash_qdma_id_map_lock);
}
/* ─────────────────────────────────────────────────────────────────────
* Module init / exit
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_init() - Initialise the QDMA subsystem.
* @num_threads: Worker thread count for libqdma's internal processing.
* @debugfs: Optional debugfs mount path, or NULL to disable.
*
* Called from the top-level module init. Initialises the libqdma
* library first (which sets up internal data structures and worker
* threads), then registers the PCI driver so that slash_qdma_probe()
* fires for each PF1 device.
*
* Return: 0 on success, negative errno on failure.
*/
int __init slash_qdma_init(unsigned int num_threads, char *debugfs)
{
int err;
SLASH_QDMA_OP_LOG("init start: num_threads=%u debugfs=%s\n",
num_threads, debugfs ? debugfs : "(null)");
err = libqdma_init(num_threads, debugfs);
if (err) {
SLASH_QDMA_OP_LOG("libqdma_init failed: err=%d\n", err);
pr_err("slash: libqdma_init failed: %d\n", err);
return err;
}
SLASH_QDMA_OP_LOG("libqdma_init done\n");
err = pci_register_driver(&slash_qdma_driver);
if (err) {
SLASH_QDMA_OP_LOG("pci_register_driver failed: err=%d\n", err);
pr_err("slash: register qdma driver failed: %d\n", err);
goto err_exit_libqdma;
}
SLASH_QDMA_OP_LOG("pci_register_driver done\n");
return 0;
err_exit_libqdma:
SLASH_QDMA_OP_LOG("libqdma_exit start (init rollback)\n");
libqdma_exit();
SLASH_QDMA_OP_LOG("libqdma_exit done (init rollback)\n");
return err;
}
/**
* slash_qdma_exit() - Tear down the QDMA subsystem.
*
* Called from the top-level module exit. Unregisters the PCI driver
* (which triggers slash_qdma_remove() for each probed device) and then
* shuts down the libqdma library.
*/
void slash_qdma_exit(void)
{
SLASH_QDMA_OP_LOG("exit start\n");
pci_unregister_driver(&slash_qdma_driver);
SLASH_QDMA_OP_LOG("pci_unregister_driver done\n");
libqdma_exit();
SLASH_QDMA_OP_LOG("libqdma_exit done\n");
}
/* ─────────────────────────────────────────────────────────────────────
* PCI probe / remove
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_probe() - PCI probe callback for QDMA devices.
* @pdev: The PCI device being probed.
* @id: Matching entry from slash_qdma_ids[].
*
* Verifies that the device is PF1 (the QDMA IP is only on PF1; PF2 is
* the control function handled by slash_ctldev). Then:
* 1. Allocates and initialises a slash_qdma_dev structure.
* 2. Configures and opens the libqdma device via qdma_device_open().
* 3. Registers the management miscdevice (/dev/slash_qdma_ctlN).
*
* On any failure, the partially-constructed device is torn down and
* the probe returns the error.
*
* Return: 0 on success, negative errno on failure.
*/
static int slash_qdma_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int err;
struct qdma_dev_conf conf;
struct slash_qdma_dev *device = NULL;
memset(&conf, 0, sizeof(conf));
dev_info(&pdev->dev, "slash: qdma: probe start for %s\n", pci_name(pdev));
SLASH_QDMA_OP_DEV_LOG(&pdev->dev,
"probe details: vendor=0x%04x device=0x%04x fn=%u\n",
pdev->vendor, pdev->device, PCI_FUNC(pdev->devfn));
/* Reject anything that is not PF1 — the QDMA IP lives only on PF1. */
if (PCI_FUNC(pdev->devfn) != SLASH_QDMA_PF) {
dev_err(&pdev->dev, "slash: expected PF %u, got %u\n", SLASH_QDMA_PF, PCI_FUNC(pdev->devfn));
return -EINVAL;
}
/* Allocate and initialise the per-device structure. */
err = slash_qdma_create_qdma_device(pdev, &device);
if (err) {
goto err_free;
}
/* Configure and open the libqdma device. */
slash_qdma_conf_options(&conf, pdev);
SLASH_QDMA_OP_DEV_LOG(&pdev->dev,
"qdma_device_open start: name=%s qsets_max=%d qsets_base=%d\n",
SLASH_NAME, conf.qsets_max, conf.qsets_base);
err = qdma_device_open(SLASH_NAME, &conf, &device->qdma_handle);
if (err) {
SLASH_QDMA_OP_DEV_LOG(&pdev->dev, "qdma_device_open failed: err=%d\n",
err);
dev_err(&pdev->dev, "slash: qdma: could not open qdma device %d", err);
goto err_free;
}
SLASH_QDMA_OP_DEV_LOG(&pdev->dev,
"qdma_device_open done: handle=%lu\n",
device->qdma_handle);
device->have_qdma_handle = true;
/* Register the management miscdevice so userspace can issue ioctls. */
err = misc_register(&device->misc);
if (err) {
dev_err(&pdev->dev, "slash: qdma: could not register misc device: %d", err);
/*
* is_misc_registered is still false here, so slash_qdma_destroy_qdma_device
* will not call misc_deregister or id_release. Release the id explicitly.
*/
slash_qdma_id_release(pci_name(pdev));
goto err_free;
}
device->is_misc_registered = true;
return 0;
err_free:
if (device) {
slash_qdma_destroy_qdma_device(device);
kref_put(&device->ref, slash_qdma_dev_release);
}
return err;
}
/**
* slash_qdma_remove() - PCI remove callback for QDMA devices.
* @pdev: The PCI device being removed.
*
* Tears down all HW queues, closes the libqdma device, deregisters the
* miscdevice, and drops the device reference.
*/
static void slash_qdma_remove(struct pci_dev *pdev)
{
struct slash_qdma_dev *device = pci_get_drvdata(pdev);
if (!device)
return;
slash_qdma_destroy_qdma_device(device);
kref_put(&device->ref, slash_qdma_dev_release);
}
/* ─────────────────────────────────────────────────────────────────────
* Device allocation and teardown
* ───────────────────────────────────────────────────────────────────── */
/**
* slash_qdma_create_qdma_device() - Allocate and initialise a QDMA device.
* @pdev: PCI device to bind to.
* @pdevice: [out] Receives a pointer to the new device on success.
*
* Allocates the slash_qdma_dev, initialises its mutex, xarray, kref,
* and miscdevice fields, and stores it in the PCI drvdata. A static
* atomic counter provides unique /dev node numbering across devices.
*
* Return: 0 on success, negative errno on failure.
*/
static int slash_qdma_create_qdma_device(struct pci_dev *pdev, struct slash_qdma_dev **pdevice)
{
int err;
struct slash_qdma_dev *device;
int id;
device = kzalloc(sizeof(*device), GFP_KERNEL);
if (!device) {
return -ENOMEM;
}
device->pdev = pdev;
kref_init(&device->ref);
mutex_init(&device->lock);
xa_init_flags(&device->qpairs, XA_FLAGS_ALLOC);
device->hw_shutdown = false;
pci_set_drvdata(pdev, device);
{ /* Miscdevice setup */
device->misc.minor = MISC_DYNAMIC_MINOR;
device->misc.fops = &slash_qdma_fops;
device->misc.parent = &pdev->dev;
device->misc.mode = SLASH_CTLDEV_QDMA_MODE;
/* Name visible in /sys/class/misc, includes PCI BDF for uniqueness. */
device->misc.name = kasprintf(GFP_KERNEL, SLASH_QDMA_CTLDEV_NAME_FMT, pci_name(device->pdev));
if (!device->misc.name) {
dev_err(&device->pdev->dev, "qdma: kasprintf(name) failed\n");
err = -ENOMEM;
goto err_free;
}
/* /dev node name: stable numeric index from BDF-to-number map. */
id = slash_qdma_id_get(pci_name(device->pdev));
if (id < 0) {
dev_err(&device->pdev->dev, "qdma: id_get failed: %d\n", id);
err = id;
goto err_free_name;
}
device->misc.nodename = kasprintf(GFP_KERNEL, SLASH_QDMA_CTLDEV_NODENAME_FMT, id);
if (!device->misc.nodename) {
dev_err(&device->pdev->dev, "qdma: kasprintf(nodename) failed\n");
err = -ENOMEM;
goto err_release_id;
}
}
*pdevice = device;
return 0;