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
|
// SPDX-License-Identifier: GPL-2.0
/*
* xHCI host controller sideband support
*
* Copyright (c) 2023-2025, Intel Corporation.
*
* Author: Mathias Nyman
*/
#include <linux/usb/xhci-sideband.h>
#include <linux/dma-direct.h>
#include "xhci.h"
/* sideband internal helpers */
static struct sg_table *
xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring)
{
struct xhci_segment *seg;
struct sg_table *sgt;
unsigned int n_pages;
struct page **pages;
struct device *dev;
size_t sz;
int i;
dev = xhci_to_hcd(sb->xhci)->self.sysdev;
sz = ring->num_segs * TRB_SEGMENT_SIZE;
n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL);
if (!pages)
return NULL;
sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
if (!sgt) {
kvfree(pages);
return NULL;
}
seg = ring->first_seg;
if (!seg)
goto err;
/*
* Rings can potentially have multiple segments, create an array that
* carries page references to allocated segments. Utilize the
* sg_alloc_table_from_pages() to create the sg table, and to ensure
* that page links are created.
*/
for (i = 0; i < ring->num_segs; i++) {
dma_get_sgtable(dev, sgt, seg->trbs, seg->dma,
TRB_SEGMENT_SIZE);
pages[i] = sg_page(sgt->sgl);
sg_free_table(sgt);
seg = seg->next;
}
if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL))
goto err;
/*
* Save first segment dma address to sg dma_address field for the sideband
* client to have access to the IOVA of the ring.
*/
sg_dma_address(sgt->sgl) = ring->first_seg->dma;
return sgt;
err:
kvfree(pages);
kfree(sgt);
return NULL;
}
static void
__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep)
{
/*
* Issue a stop endpoint command when an endpoint is removed.
* The stop ep cmd handler will handle the ring cleanup.
*/
xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL);
ep->sideband = NULL;
sb->eps[ep->ep_index] = NULL;
}
/* sideband api functions */
/**
* xhci_sideband_notify_ep_ring_free - notify client of xfer ring free
* @sb: sideband instance for this usb device
* @ep_index: usb endpoint index
*
* Notifies the xHCI sideband client driver of a xHCI transfer ring free
* routine. This will allow for the client to ensure that all transfers
* are completed.
*
* The callback should be synchronous, as the ring free happens after.
*/
void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb,
unsigned int ep_index)
{
struct xhci_sideband_event evt;
evt.type = XHCI_SIDEBAND_XFER_RING_FREE;
evt.evt_data = &ep_index;
if (sb->notify_client)
sb->notify_client(sb->intf, &evt);
}
EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free);
/**
* xhci_sideband_add_endpoint - add endpoint to sideband access list
* @sb: sideband instance for this usb device
* @host_ep: usb host endpoint
*
* Adds an endpoint to the list of sideband accessed endpoints for this usb
* device.
* After an endpoint is added the sideband client can get the endpoint transfer
* ring buffer by calling xhci_sideband_endpoint_buffer()
*
* Return: 0 on success, negative error otherwise.
*/
int
xhci_sideband_add_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
mutex_lock(&sb->mutex);
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = &sb->vdev->eps[ep_index];
if (ep->ep_state & EP_HAS_STREAMS) {
mutex_unlock(&sb->mutex);
return -EINVAL;
}
/*
* Note, we don't know the DMA mask of the audio DSP device, if its
* smaller than for xhci it won't be able to access the endpoint ring
* buffer. This could be solved by not allowing the audio class driver
* to add the endpoint the normal way, but instead offload it immediately,
* and let this function add the endpoint and allocate the ring buffer
* with the smallest common DMA mask
*/
if (sb->eps[ep_index] || ep->sideband) {
mutex_unlock(&sb->mutex);
return -EBUSY;
}
ep->sideband = sb;
sb->eps[ep_index] = ep;
mutex_unlock(&sb->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint);
/**
* xhci_sideband_remove_endpoint - remove endpoint from sideband access list
* @sb: sideband instance for this usb device
* @host_ep: usb host endpoint
*
* Removes an endpoint from the list of sideband accessed endpoints for this usb
* device.
* sideband client should no longer touch the endpoint transfer buffer after
* calling this.
*
* Return: 0 on success, negative error otherwise.
*/
int
xhci_sideband_remove_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
mutex_lock(&sb->mutex);
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->sideband || ep->sideband != sb) {
mutex_unlock(&sb->mutex);
return -ENODEV;
}
__xhci_sideband_remove_endpoint(sb, ep);
xhci_initialize_ring_info(ep->ring);
mutex_unlock(&sb->mutex);
return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint);
int
xhci_sideband_stop_endpoint(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->sideband || ep->sideband != sb)
return -EINVAL;
return xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint);
/**
* xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address
* @sb: sideband instance for this usb device
* @host_ep: usb host endpoint
*
* Returns the address of the endpoint buffer where xHC controller reads queued
* transfer TRBs from. This is the starting address of the ringbuffer where the
* sideband client should write TRBs to.
*
* Caller needs to free the returned sg_table
*
* Return: struct sg_table * if successful. NULL otherwise.
*/
struct sg_table *
xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb,
struct usb_host_endpoint *host_ep)
{
struct xhci_virt_ep *ep;
unsigned int ep_index;
ep_index = xhci_get_endpoint_index(&host_ep->desc);
ep = sb->eps[ep_index];
if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb)
return NULL;
return xhci_ring_to_sgtable(sb, ep->ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer);
/**
* xhci_sideband_get_event_buffer - return the event buffer for this device
* @sb: sideband instance for this usb device
*
* If a secondary xhci interupter is set up for this usb device then this
* function returns the address of the event buffer where xHC writes
* the transfer completion events.
*
* Caller needs to free the returned sg_table
*
* Return: struct sg_table * if successful. NULL otherwise.
*/
struct sg_table *
xhci_sideband_get_event_buffer(struct xhci_sideband *sb)
{
if (!sb || !sb->ir)
return NULL;
return xhci_ring_to_sgtable(sb, sb->ir->event_ring);
}
EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);
/**
* xhci_sideband_create_interrupter - creates a new interrupter for this sideband
* @sb: sideband instance for this usb device
* @num_seg: number of event ring segments to allocate
* @ip_autoclear: IP autoclearing support such as MSI implemented
*
* Sets up a xhci interrupter that can be used for this sideband accessed usb
* device. Transfer events for this device can be routed to this interrupters
* event ring by setting the 'Interrupter Target' field correctly when queueing
* the transfer TRBs.
* Once this interrupter is created the interrupter target ID can be obtained
* by calling xhci_sideband_interrupter_id()
*
* Returns 0 on success, negative error otherwise
*/
int
xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
bool ip_autoclear, u32 imod_interval, int intr_num)
{
int ret = 0;
if (!sb || !sb->xhci)
return -ENODEV;
mutex_lock(&sb->mutex);
if (sb->ir) {
ret = -EBUSY;
goto out;
}
sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci),
num_seg, imod_interval,
intr_num);
if (!sb->ir) {
ret = -ENOMEM;
goto out;
}
sb->ir->ip_autoclear = ip_autoclear;
out:
mutex_unlock(&sb->mutex);
return ret;
}
EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
/**
* xhci_sideband_remove_interrupter - remove the interrupter from a sideband
* @sb: sideband instance for this usb device
*
* Removes a registered interrupt for a sideband. This would allow for other
* sideband users to utilize this interrupter.
*/
void
xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
if (!sb || !sb->ir)
return;
mutex_lock(&sb->mutex);
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
mutex_unlock(&sb->mutex);
}
EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
/**
* xhci_sideband_interrupter_id - return the interrupter target id
* @sb: sideband instance for this usb device
*
* If a secondary xhci interrupter is set up for this usb device then this
* function returns the ID used by the interrupter. The sideband client
* needs to write this ID to the 'Interrupter Target' field of the transfer TRBs
* it queues on the endpoints transfer ring to ensure transfer completion event
* are written by xHC to the correct interrupter event ring.
*
* Returns interrupter id on success, negative error othgerwise
*/
int
xhci_sideband_interrupter_id(struct xhci_sideband *sb)
{
if (!sb || !sb->ir)
return -ENODEV;
return sb->ir->intr_num;
}
EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id);
/**
* xhci_sideband_register - register a sideband for a usb device
* @intf: usb interface associated with the sideband device
*
* Allows for clients to utilize XHCI interrupters and fetch transfer and event
* ring parameters for executing data transfers.
*
* Return: pointer to a new xhci_sideband instance if successful. NULL otherwise.
*/
struct xhci_sideband *
xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type,
int (*notify_client)(struct usb_interface *intf,
struct xhci_sideband_event *evt))
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct xhci_virt_device *vdev;
struct xhci_sideband *sb;
/*
* Make sure the usb device is connected to a xhci controller. Fail
* registration if the type is anything other than XHCI_SIDEBAND_VENDOR,
* as this is the only type that is currently supported by xhci-sideband.
*/
if (!udev->slot_id || type != XHCI_SIDEBAND_VENDOR)
return NULL;
sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev));
if (!sb)
return NULL;
mutex_init(&sb->mutex);
/* check this device isn't already controlled via sideband */
spin_lock_irq(&xhci->lock);
vdev = xhci->devs[udev->slot_id];
if (!vdev || vdev->sideband) {
xhci_warn(xhci, "XHCI sideband for slot %d already in use\n",
udev->slot_id);
spin_unlock_irq(&xhci->lock);
kfree(sb);
return NULL;
}
sb->xhci = xhci;
sb->vdev = vdev;
sb->intf = intf;
sb->type = type;
sb->notify_client = notify_client;
vdev->sideband = sb;
spin_unlock_irq(&xhci->lock);
return sb;
}
EXPORT_SYMBOL_GPL(xhci_sideband_register);
/**
* xhci_sideband_unregister - unregister sideband access to a usb device
* @sb: sideband instance to be unregistered
*
* Unregisters sideband access to a usb device and frees the sideband
* instance.
* After this the endpoint and interrupter event buffers should no longer
* be accessed via sideband. The xhci driver can now take over handling
* the buffers.
*/
void
xhci_sideband_unregister(struct xhci_sideband *sb)
{
struct xhci_hcd *xhci;
int i;
if (!sb)
return;
xhci = sb->xhci;
mutex_lock(&sb->mutex);
for (i = 0; i < EP_CTX_PER_DEV; i++)
if (sb->eps[i])
__xhci_sideband_remove_endpoint(sb, sb->eps[i]);
mutex_unlock(&sb->mutex);
xhci_sideband_remove_interrupter(sb);
spin_lock_irq(&xhci->lock);
sb->xhci = NULL;
sb->vdev->sideband = NULL;
spin_unlock_irq(&xhci->lock);
kfree(sb);
}
EXPORT_SYMBOL_GPL(xhci_sideband_unregister);
MODULE_DESCRIPTION("xHCI sideband driver for secondary interrupter management");
MODULE_LICENSE("GPL");
|