usb: xhci: Implement DMA mapping
An XHCI controller that sits behind an IOMMU needs to map and unmap
its memory buffers to do DMA. Implement this by inroducing new
xhci_dma_map() and xhci_dma_unmap() helper functions. The
xhci_dma_map() function replaces the existing xhci_virt_to_bus()
function in the sense that it returns the bus address in the case
of simple address translation in the absence of an IOMMU. The
xhci_bus_to_virt() function is eliminated by storing the CPU
address of the allocated scratchpad memory in struct xhci_ctrl.
Signed-off-by: Mark Kettenis <kettenis@openbsd.org>
Reviewed-by: Marek Vasut <marex@denx.de>
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index eb6dfcd..c8260cb 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -24,6 +24,24 @@
#include <usb/xhci.h>
+/*
+ * Returns zero if the TRB isn't in this segment, otherwise it returns the DMA
+ * address of the TRB.
+ */
+dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg,
+ union xhci_trb *trb)
+{
+ unsigned long segment_offset;
+
+ if (!seg || !trb || trb < seg->trbs)
+ return 0;
+ /* offset in TRBs */
+ segment_offset = trb - seg->trbs;
+ if (segment_offset >= TRBS_PER_SEGMENT)
+ return 0;
+ return seg->dma + (segment_offset * sizeof(*trb));
+}
+
/**
* Is this TRB a link TRB or was the last TRB the last TRB in this event ring
* segment? I.e. would the updated event TRB pointer step off the end of the
@@ -180,10 +198,8 @@
* @param trb_fields pointer to trb field array containing TRB contents
* Return: pointer to the enqueued trb
*/
-static struct xhci_generic_trb *queue_trb(struct xhci_ctrl *ctrl,
- struct xhci_ring *ring,
- bool more_trbs_coming,
- unsigned int *trb_fields)
+static dma_addr_t queue_trb(struct xhci_ctrl *ctrl, struct xhci_ring *ring,
+ bool more_trbs_coming, unsigned int *trb_fields)
{
struct xhci_generic_trb *trb;
int i;
@@ -197,7 +213,7 @@
inc_enq(ctrl, ring, more_trbs_coming);
- return trb;
+ return xhci_trb_virt_to_dma(ring->enq_seg, (union xhci_trb *)trb);
}
/**
@@ -271,19 +287,15 @@
* @param cmd Command type to enqueue
* Return: none
*/
-void xhci_queue_command(struct xhci_ctrl *ctrl, u8 *ptr, u32 slot_id,
+void xhci_queue_command(struct xhci_ctrl *ctrl, dma_addr_t addr, u32 slot_id,
u32 ep_index, trb_type cmd)
{
u32 fields[4];
- u64 val_64 = 0;
BUG_ON(prepare_ring(ctrl, ctrl->cmd_ring, EP_STATE_RUNNING));
- if (ptr)
- val_64 = xhci_virt_to_bus(ctrl, ptr);
-
- fields[0] = lower_32_bits(val_64);
- fields[1] = upper_32_bits(val_64);
+ fields[0] = lower_32_bits(addr);
+ fields[1] = upper_32_bits(addr);
fields[2] = 0;
fields[3] = TRB_TYPE(cmd) | SLOT_ID_FOR_TRB(slot_id) |
ctrl->cmd_ring->cycle_state;
@@ -399,12 +411,15 @@
*/
void xhci_acknowledge_event(struct xhci_ctrl *ctrl)
{
+ dma_addr_t deq;
+
/* Advance our dequeue pointer to the next event */
inc_deq(ctrl, ctrl->event_ring);
/* Inform the hardware */
- xhci_writeq(&ctrl->ir_set->erst_dequeue,
- xhci_virt_to_bus(ctrl, ctrl->event_ring->dequeue) | ERST_EHB);
+ deq = xhci_trb_virt_to_dma(ctrl->event_ring->deq_seg,
+ ctrl->event_ring->dequeue);
+ xhci_writeq(&ctrl->ir_set->erst_dequeue, deq | ERST_EHB);
}
/**
@@ -490,17 +505,19 @@
struct xhci_ctrl *ctrl = xhci_get_ctrl(udev);
struct xhci_ring *ring = ctrl->devs[udev->slot_id]->eps[ep_index].ring;
union xhci_trb *event;
+ u64 addr;
u32 field;
printf("Resetting EP %d...\n", ep_index);
- xhci_queue_command(ctrl, NULL, udev->slot_id, ep_index, TRB_RESET_EP);
+ xhci_queue_command(ctrl, 0, udev->slot_id, ep_index, TRB_RESET_EP);
event = xhci_wait_for_event(ctrl, TRB_COMPLETION);
field = le32_to_cpu(event->trans_event.flags);
BUG_ON(TRB_TO_SLOT_ID(field) != udev->slot_id);
xhci_acknowledge_event(ctrl);
- xhci_queue_command(ctrl, (void *)((uintptr_t)ring->enqueue |
- ring->cycle_state), udev->slot_id, ep_index, TRB_SET_DEQ);
+ addr = xhci_trb_virt_to_dma(ring->enq_seg,
+ (void *)((uintptr_t)ring->enqueue | ring->cycle_state));
+ xhci_queue_command(ctrl, addr, udev->slot_id, ep_index, TRB_SET_DEQ);
event = xhci_wait_for_event(ctrl, TRB_COMPLETION);
BUG_ON(TRB_TO_SLOT_ID(le32_to_cpu(event->event_cmd.flags))
!= udev->slot_id || GET_COMP_CODE(le32_to_cpu(
@@ -521,9 +538,10 @@
struct xhci_ctrl *ctrl = xhci_get_ctrl(udev);
struct xhci_ring *ring = ctrl->devs[udev->slot_id]->eps[ep_index].ring;
union xhci_trb *event;
+ u64 addr;
u32 field;
- xhci_queue_command(ctrl, NULL, udev->slot_id, ep_index, TRB_STOP_RING);
+ xhci_queue_command(ctrl, 0, udev->slot_id, ep_index, TRB_STOP_RING);
event = xhci_wait_for_event(ctrl, TRB_TRANSFER);
field = le32_to_cpu(event->trans_event.flags);
@@ -539,8 +557,9 @@
event->event_cmd.status)) != COMP_SUCCESS);
xhci_acknowledge_event(ctrl);
- xhci_queue_command(ctrl, (void *)((uintptr_t)ring->enqueue |
- ring->cycle_state), udev->slot_id, ep_index, TRB_SET_DEQ);
+ addr = xhci_trb_virt_to_dma(ring->enq_seg,
+ (void *)((uintptr_t)ring->enqueue | ring->cycle_state));
+ xhci_queue_command(ctrl, addr, udev->slot_id, ep_index, TRB_SET_DEQ);
event = xhci_wait_for_event(ctrl, TRB_COMPLETION);
BUG_ON(TRB_TO_SLOT_ID(le32_to_cpu(event->event_cmd.flags))
!= udev->slot_id || GET_COMP_CODE(le32_to_cpu(
@@ -609,8 +628,8 @@
u64 addr;
int ret;
u32 trb_fields[4];
- u64 val_64 = xhci_virt_to_bus(ctrl, buffer);
- void *last_transfer_trb_addr;
+ u64 buf_64 = xhci_dma_map(ctrl, buffer, length);
+ dma_addr_t last_transfer_trb_addr;
int available_length;
debug("dev=%p, pipe=%lx, buffer=%p, length=%d\n",
@@ -633,7 +652,7 @@
* we send request in more than 1 TRB by chaining them.
*/
running_total = TRB_MAX_BUFF_SIZE -
- (lower_32_bits(val_64) & (TRB_MAX_BUFF_SIZE - 1));
+ (lower_32_bits(buf_64) & (TRB_MAX_BUFF_SIZE - 1));
trb_buff_len = running_total;
running_total &= TRB_MAX_BUFF_SIZE - 1;
@@ -678,7 +697,7 @@
* that the buffer should not span 64KB boundary. if so
* we send request in more than 1 TRB by chaining them.
*/
- addr = val_64;
+ addr = buf_64;
if (trb_buff_len > length)
trb_buff_len = length;
@@ -754,7 +773,7 @@
}
if ((uintptr_t)(le64_to_cpu(event->trans_event.buffer)) !=
- (uintptr_t)xhci_virt_to_bus(ctrl, last_transfer_trb_addr)) {
+ (uintptr_t)last_transfer_trb_addr) {
available_length -=
(int)EVENT_TRB_LEN(le32_to_cpu(event->trans_event.transfer_len));
xhci_acknowledge_event(ctrl);
@@ -768,6 +787,7 @@
record_transfer_result(udev, event, available_length);
xhci_acknowledge_event(ctrl);
xhci_inval_cache((uintptr_t)buffer, length);
+ xhci_dma_unmap(ctrl, buf_64, length);
return (udev->status != USB_ST_NOT_PROC) ? 0 : -1;
}
@@ -911,7 +931,7 @@
if (length > 0) {
if (req->requesttype & USB_DIR_IN)
field |= TRB_DIR_IN;
- buf_64 = xhci_virt_to_bus(ctrl, buffer);
+ buf_64 = xhci_dma_map(ctrl, buffer, length);
trb_fields[0] = lower_32_bits(buf_64);
trb_fields[1] = upper_32_bits(buf_64);
@@ -961,8 +981,10 @@
}
/* Invalidate buffer to make it available to usb-core */
- if (length > 0)
+ if (length > 0) {
xhci_inval_cache((uintptr_t)buffer, length);
+ xhci_dma_unmap(ctrl, buf_64, length);
+ }
if (GET_COMP_CODE(le32_to_cpu(event->trans_event.transfer_len))
== COMP_SHORT_TX) {