Source code for usbcore.cpu.eptri

#!/usr/bin/env python3

from enum import IntEnum

from migen import *
from migen.genlib import fifo
from migen.genlib import cdc

from litex.soc.interconnect import stream
from litex.soc.interconnect import wishbone
from litex.soc.interconnect import csr_eventmanager as ev
from litex.soc.interconnect.csr import *

from litex.soc.cores.gpio import GPIOOut

from ..endpoint import EndpointType, EndpointResponse
from ..pid import PID, PIDTypes
from ..sm.transfer import UsbTransfer
from .usbwishbonebridge import USBWishboneBridge

"""
Register Interface:

pullup_out_read: Read the status of the USB "FS" pullup.
pullup_out_write: Write the USB "FS" pullup state

SETUP - Responding to a SETUP packet from the host
setup_read: Read the contents of the last SETUP transaction
setup_ack: Write a "1" here to advance the data_read fifo
setup_empty: "0" if there is no SETUP data.
setup_epno: The endpoint the SETUP packet was destined for

EPOUT - Data from the host to this device
epout_data_read: Read the contents of the last transaction on the EP0
epout_data_ack: Write a "1" here to advance the data_read fifo
epout_last_tok: Bits 2 and 3 of the last token, from the following table:
   USB_PID_OUT   = 0
   USB_PID_SOF   = 1
   USB_PID_IN    = 2
   USB_PID_SETUP = 3
epout_epno: Which endpoint contained the last data
epout_queued: A response is queued and has yet to be acknowledged by the host

EPIN - Requests from the host to read data from this device
epin_data_write: Write 8 bits to the EP0 queue
epin_data_empty: Return 1 if the queue is empty
epin_epno: Which endpoint the data is for.  You must write this byte to indicate data is ready to be sent.
epin_queued: A response is queued and has yet to be acknowledged by the host

ep_stall: a 32-bit field representing endpoitns to respond with STALL.
"""

[docs]class SetupHandler(Module, AutoCSR): """Handle SETUP packets. SETUP packets must always respond with ACK. Sometimes, they are followed by DATA packets, but not always. Attributes ---------- data : CSR Data from the last SETUP packet. It will be 10 bytes long, because it will include the CRC16. This is a FIFO; use `DATA_ACK` to advance the queue. .. wavedrom:: :caption: data CSR Interface { "reg": [ { "name": "DATA", "bits": 8, "attr": "RO", "description": "The next byte of SETUP data" } ] } status : CSRStatus Status about the most recent SETUP packet, and the state of the FIFO. .. wavedrom:: :caption: status CSR Interface { "reg": [ { "name": "HAVE", "bits": 1, "attr": "RO", "description": "`1` if there is data in the FIFO." }, { "bits": 1 }, { "name": "EPNO", "bits": 4, "attr": "RO", "description": "The destination endpoint for the most recent SETUP packet." }, { "bits": 2 } ] } ctrl : CSRStorage Controls for managing `SETUP` packets. .. wavedrom:: :caption: CTRL CSR Interface { "reg": [ { "name": "ADVANCE", "bits": 1, "attr": "WO", "description": "Write a `1` here to advance the `DATA` FIFO." }, { "bits": 7 } ] } """ def __init__(self, usb_core): self.submodules.ev = ev.EventManager() self.ev.submodules.error = ev.EventSourcePulse() self.ev.submodules.packet = ev.EventSourcePulse() self.ev.finalize() self.trigger = self.ev.packet.trigger self.data_recv_payload = Signal(8) self.data_recv_put = Signal() self.have_data_stage = Signal() self.submodules.data = buf = ClockDomainsRenamer({"write": "usb_12", "read": "sys"})(ResetInserter()(fifo.SyncFIFOBuffered(width=8, depth=10))) self.data = CSR(8) self.status = CSRStatus(6) self.ctrl = CSRStorage(1) # How to respond to requests: # - 0 - ACK # - 1 - NAK # Since we must always ACK a SETUP packet, set this to 0. self.response = Signal() self.comb += self.response.eq(0), epno = Signal(4) # Wire up the `STATUS` register self.comb += self.status.status.eq(Cat(buf.readable, Signal(), epno)) # Wire up the "SETUP" endpoint. self.comb += [ # Set the FIFO output to be the current buffer HEAD self.data.w.eq(buf.dout), # Advance the FIFO when anything is written to the control bit buf.re.eq(self.ctrl.re & self.ctrl.storage[0]), If(usb_core.tok == PID.SETUP, buf.din.eq(self.data_recv_payload), buf.we.eq(self.data_recv_put), self.ev.packet.trigger.eq(usb_core.commit), ) ] # When we get the start of a SETUP packet, update the `epno` value. check_reset = Signal() data_bytes = Signal(3) self.sync.usb_12 += [ check_reset.eq(usb_core.start), If(check_reset, If(usb_core.tok == PID.SETUP, epno.eq(usb_core.endp), buf.reset.eq(1), data_bytes.eq(0), self.have_data_stage.eq(0), ).Else( buf.reset.eq(0), ) ).Else( buf.reset.eq(0), ), # The 6th and 7th bytes of SETUP data are # the wLength field. If these are nonzero, # then there will be a Data stage following # this Setup stage. If(self.data_recv_put, data_bytes.eq(data_bytes + 1), If(self.data_recv_payload, If(data_bytes == 6, self.have_data_stage.eq(1), ).Elif(data_bytes == 7, self.have_data_stage.eq(1), ) ) ) ]
[docs]class InHandler(Module, AutoCSR): """Endpoint for Device->Host data. Reads from the buffer memory. Raises packet IRQ when packet has been sent. CPU writes to the head CSR to push data onto the FIFO. """ def __init__(self, usb_core): self.submodules.ev = ev.EventManager() self.ev.submodules.error = ev.EventSourcePulse() self.ev.submodules.packet = ev.EventSourcePulse() self.ev.finalize() self.trigger = self.ev.packet.trigger self.dtb = Signal() # Keep track of the current DTB for each of the 16 endpoints dtbs = Signal(16, reset=0xffff) self.submodules.data_buf = buf = fifo.SyncFIFOBuffered(width=8, depth=128) #w { #w "reg_definition": { #w "reg_name": "DATA", #w "reg_description": "Write data to this register. It is a FIFO, so any bytes that are written here will be transmitted in-order. The FIFO queue is automatically advanced. The FIFO queue is 64 bytes deep. If you exceed this amount, the result is undefined.", #w "reg": [ #w { "name": "DATA", "bits": 8, "attr": "WO", "description": "The next byte to add to the queue." } #w ] #w } #w } self.data = CSRStorage(8) #w { #w "reg_definition": { #w "reg_name": "STATUS", #w "reg_description": "Determine the status of the `IN` pathway.", #w "reg": [ #w { "name": "HAVE", "bits": 1, "attr": "RO", "description": "This value is '0' if the FIFO is empty." }, #w { "name": "IDLE", "bits": 1, "attr": "RO", "description": "This value is '1' if the packet has finished transmitting." }, #w { "bits": 6 } #w ] #w } #w } self.status = CSRStatus(2) #w { #w "reg_definition": { #w "reg_name": "EP", #w "reg_description": "After writing data to the `data` register, update this register with the destination endpoint number. Writing to this register queues the packet for transmission.", #w "reg": [ #w { "name": "EP", "bits": 4, "attr": "WO", "description": "The endpoint number for the transaction that is queued in the FIFO." } #w { "bits": 4 } #w ] #w } #w } self.epno = CSRStorage(4) xxxx_readable = Signal() self.specials.crc_readable = cdc.MultiReg(~buf.readable, xxxx_readable) # How to respond to requests: # - 0 - ACK # - 1 - NAK self.response = Signal() # This value goes "1" when data is pending, and returns to "0" when it's done. queued = Signal() # Outgoing data will be placed on this signal self.data_out = Signal(8) # This is "1" if `data_out` contains data self.data_out_have = Signal() # Pulse this to advance the data output self.data_out_advance = Signal() # Pulse this to reset the DTB value self.dtb_reset = Signal() self.comb += [ # We will respond with "ACK" if the register matches the current endpoint number If(usb_core.endp == self.epno.storage, self.response.eq(queued) ).Else( self.response.eq(1) ), # Wire up the "status" register self.status.status.eq( Cat(~xxxx_readable, ~queued) ), self.dtb.eq(dtbs >> usb_core.endp), self.data_out.eq(buf.dout), self.data_out_have.eq(buf.readable), buf.re.eq(self.data_out_advance), buf.we.eq(self.data.re), buf.din.eq(self.data.storage), ] self.sync += [ # When the user updates the `epno` register, enable writing. If(self.epno.re, queued.eq(1) ) # When the USB core finishes operating on this packet, # de-assert the queue flag .Elif(usb_core.end, If(usb_core.endp == self.epno.storage, queued.eq(0), ), # Toggle the "DTB" line if we transmitted data If(usb_core.arm & ~usb_core.sta, dtbs.eq(Replicate(self.dtb_reset, 16) | (dtbs ^ (1 << self.epno.storage))), ), ), ]
[docs]class OutHandler(Module, AutoCSR): def __init__(self, usb_core): # EPOUT - Data from the host to this device self.submodules.ev = ev.EventManager() self.ev.submodules.error = ev.EventSourcePulse() self.ev.submodules.packet = ev.EventSourcePulse() self.ev.finalize() self.trigger = self.ev.packet.trigger self.submodules.data_buf = buf = fifo.SyncFIFOBuffered(width=8, depth=128) #w { #w "reg_definition": { #w "reg_name": "DATA", #w "reg_description": "Data received from the host will go into a FIFO. This register reflects the contents of the top byte in that FIFO.", #w "reg": [ #w { "name": "DATA", "bits": 8, "attr": "RO", "description": "The top byte of the receive FIFO." } #w ] #w } #w } self.data = CSR(8) #w { #w "reg_definition": { #w "reg_name": "STATUS", #w "reg_description": "Status about the contents of the OUT endpoint.", #w "reg": [ #w { "name": "HAVE", "bits": 1, "attr": "RO", "description": "`1` if there is data in the FIFO." }, #w { "name": "IDLE", "bits": 1, "attr": "RO", "description": "`1` if the packet has finished receiving." }, #w { "name": "EPNO", "bits": 4, "attr": "RO", "description": "The destination endpoint for the most recent SETUP packet." }, #w { "bits": 2 } #w ] #w } #w } self.status = CSRStatus(6) #w { #w "reg_definition": { #w "reg_name": "CTRL", #w "reg_description": "Controls for managing `SETUP` packets.", #w "reg": [ #w { "name": "ADVANCE", "bits": 1, "attr": "WO", "description": "Write a `1` here to advance the `DATA` FIFO." }, #w { "name": "ENABLE", "bits": 1, "attr": "WO", "description": "Write a `1` here to enable recieving data" }, #w { "bits": 6 } #w ] #w } #w } self.ctrl = CSRStorage(2) # How to respond to requests: # - 1 - ACK # - 0 - NAK # Send a NAK if the buffer contains data, or if "ENABLE" has not been set. self.response = Signal() self.comb += self.response.eq((~buf.readable) & (self.ctrl.storage[1])) epno = Signal(4) is_idle = Signal(reset=1) # Connect the buffer to the USB system self.data_recv_payload = Signal(8) self.data_recv_put = Signal() self.comb += [ buf.din.eq(self.data_recv_payload), buf.we.eq(self.data_recv_put), self.data.w.eq(buf.dout), # When a "1" is written to ctrl, advance the FIFO buf.re.eq(self.ctrl.storage[0] & self.ctrl.re), self.status.status.eq(Cat(buf.readable, epno, is_idle)), ] # If we get a packet, turn off the "IDLE" flag and keep it off until the packet has finished. self.sync += [ If(usb_core.commit & buf.readable, is_idle.eq(1), ).Elif(self.data_recv_put, is_idle.eq(0), ) ]
[docs]class TriEndpointInterface(Module, AutoCSR): """ Implements a CPU interface with three endpoints: * SETUP * EPIN * EPOUT Each endpoint has: * A FIFO with one end connected to CSRs and the other to the USB core. * Control bits. * A pending flag. An output FIFO is written to using CSR registers. An input FIFO is read using CSR registers. Extra CSR registers set the response type (ACK/NAK/STALL). """ def __init__(self, iobuf, debug=False): # USB Core self.submodules.usb_core = usb_core = UsbTransfer(iobuf) self.submodules.pullup = GPIOOut(usb_core.iobuf.usb_pullup) self.iobuf = usb_core.iobuf self.eps_idx = eps_idx = Signal(5) self.comb += [ self.eps_idx.eq(Cat(usb_core.endp, usb_core.tok == PID.IN)), ] # Generate debug signals, in case debug is enabled. debug_packet_detected = Signal() debug_data_mux = Signal(8) debug_data_ready_mux = Signal() debug_sink_data = Signal(8) debug_sink_data_ready = Signal() debug_ack_response = Signal() # Wire up debug signals if required if debug: debug_bridge = USBWishboneBridge(self.usb_core) self.submodules.debug_bridge = ClockDomainsRenamer("usb_12")(debug_bridge) self.comb += [ debug_packet_detected.eq(~self.debug_bridge.n_debug_in_progress), ] ems = [] trigger_all = [] self.submodules.setup = setup_handler = ClockDomainsRenamer("usb_12")(SetupHandler(usb_core)) ems.append(setup_handler.ev) trigger_all.append(setup_handler.trigger.eq(1)), self.submodules.epin = in_handler = ClockDomainsRenamer("usb_12")(InHandler(usb_core)) ems.append(in_handler.ev) trigger_all.append(in_handler.trigger.eq(1)), self.submodules.epout = out_handler = ClockDomainsRenamer("usb_12")(OutHandler(usb_core)) ems.append(out_handler.ev) trigger_all.append(out_handler.trigger.eq(1)), self.submodules.ev = ev.SharedIRQ(*ems) self.comb += [ If(~iobuf.usb_pullup, *trigger_all, ), ] #w { #w "reg_definition": { #w "reg_name": "ENABLE_OUT0", #w "reg_description": "Set a `1` to enable endpoints 0-7 OUT -- otherwise a STALL will be sent.", #w "reg": [ #w { "name": "EPOUT", "bits": 8, "attr": "WO", "description": "Set a `1` here to enable the given OUT endpoint" }, #w ] #w } #w } self.enable_out0 = CSRStorage(8) #w { #w "reg_definition": { #w "reg_name": "ENABLE_OUT1", #w "reg_description": "Set a `1` to enable endpoints 8-15 IN -- otherwise a STALL will be sent.", #w "reg": [ #w { "name": "EPOUT", "bits": 8, "attr": "WO", "description": "Set a `1` here to enable the given OUT endpoint" }, #w ] #w } #w } self.enable_out1 = CSRStorage(8) #w { #w "reg_definition": { #w "reg_name": "ENABLE_IN0", #w "reg_description": "Set a `1` to enable endpoints 0-7 IN -- otherwise a STALL will be sent.", #w "reg": [ #w { "name": "EPIN", "bits": 8, "attr": "WO", "description": "Set a `1` here to enable the given IN endpoint" } #w ] #w } #w } self.enable_in0 = CSRStorage(8) #w { #w "reg_definition": { #w "reg_name": "ENABLE_IN1", #w "reg_description": "Set a `1` to enable endpoints 8-15 IN -- otherwise a STALL will be sent.", #w "reg": [ #w { "name": "EPIN", "bits": 8, "attr": "WO", "description": "Set a `1` here to enable the given IN endpoint" } #w ] #w } #w } self.enable_in1 = CSRStorage(8) enable = Signal(32) should_stall = Signal() #w { #w "reg_definition": { #w "reg_name": "ADDRESS", #w "reg_description": "Sets the USB device address, to ignore packets going to other devices.", #w "reg": [ #w { "name": "ADDRESS", "bits": 7, "attr": "WO", "description": "Write the USB address from USB `SET_ADDRESS packets.`" }, #w { "bits": 1 } #w ] #w } #w } self.address = CSRStorage(7) self.comb += usb_core.addr.eq(self.address.storage) self.submodules.stage = stage = FSM() # If the SETUP stage should have data, this will be 1 setup_data_stage = Signal() # Which of the 10 SETUP bytes (8 + CRC16) we're currently looking at setup_data_byte = Signal(4) setup_data_byte_ce = Signal() setup_data_byte_rst = Signal() # 1 if it's an IN packet, 0 if it's an OUT setup_is_in = Signal() self.sync += [ If(setup_data_byte_rst, setup_data_byte.eq(0), setup_is_in.eq(0), setup_data_stage.eq(0), ).Elif(setup_data_byte_ce, setup_data_byte.eq(setup_data_byte + 1), If(setup_data_byte == 0, If(usb_core.data_recv_payload[7], setup_is_in.eq(1), ) ).Elif(setup_data_byte == 6, If(usb_core.data_recv_payload, setup_data_stage.eq(1) ) ).Elif(setup_data_byte == 7, If(usb_core.data_recv_payload, setup_data_stage.eq(1) ) ), ) ] stage.act("IDLE", setup_data_byte_rst.eq(1), If(usb_core.start, NextState("WAIT_TOK") ) ) stage.act("WAIT_TOK", If(usb_core.tok == PID.SETUP, NextState("SETUP"), # SETUP packets must be ACKed usb_core.sta.eq(0), usb_core.arm.eq(1), ).Elif(usb_core.tok == PID.IN, NextState("IN"), usb_core.sta.eq(should_stall), usb_core.arm.eq(in_handler.response | should_stall), ).Elif(usb_core.tok == PID.OUT, NextState("OUT"), usb_core.sta.eq(should_stall), usb_core.arm.eq(out_handler.response | should_stall), ).Elif(usb_core.tok == PID.SOF, NextState("IDLE"), ) ) if debug: stage.act("DEBUG", usb_core.data_send_payload.eq(self.debug_bridge.sink_data), usb_core.data_send_have.eq(self.debug_bridge.sink_valid), usb_core.sta.eq(0), If(usb_core.endp == 0, usb_core.arm.eq(self.debug_bridge.send_ack | self.debug_bridge.sink_valid), ).Else( usb_core.arm.eq(0) ), usb_core.dtb.eq(1), If(~debug_packet_detected, NextState("IDLE") ) ) stage.act("SETUP", # SETUP packet setup_handler.data_recv_payload.eq(usb_core.data_recv_payload), setup_handler.data_recv_put.eq(usb_core.data_recv_put), in_handler.dtb_reset.eq(1), # We aren't allowed to STALL a SETUP packet usb_core.sta.eq(0), # Always ACK a SETUP packet usb_core.arm.eq(1), setup_handler.trigger.eq(usb_core.commit), # If the transfer size is nonzero, proceed to handle data packets If(usb_core.data_recv_put, setup_data_byte_ce.eq(1), ), If(debug_packet_detected, NextState("DEBUG") ), If(usb_core.end, If(setup_is_in, If(setup_data_stage, NextState("CONTROL_IN"), usb_core.sta.eq(should_stall), usb_core.arm.eq(in_handler.response | should_stall), ).Else( NextState("WAIT_CONTROL_ACK_IN"), ) ).Else( If(setup_data_stage, NextState("CONTROL_OUT"), usb_core.sta.eq(should_stall), usb_core.arm.eq(out_handler.response | should_stall), ).Else( NextState("WAIT_CONTROL_ACK_OUT"), ) ) ) ) stage.act("CONTROL_IN", If(usb_core.endp == 0, If(usb_core.tok == PID.IN, usb_core.data_send_have.eq(in_handler.data_out_have), usb_core.data_send_payload.eq(in_handler.data_out), in_handler.data_out_advance.eq(usb_core.data_send_get), usb_core.sta.eq(should_stall), usb_core.arm.eq(in_handler.response | should_stall), usb_core.dtb.eq(in_handler.dtb), in_handler.trigger.eq(usb_core.commit), ).Elif(usb_core.tok == PID.OUT, usb_core.sta.eq(0), usb_core.arm.eq(1), # After an IN transfer, the host sends an OUT # packet. We must ACK this and then return to IDLE. NextState("WAIT_DONE"), ) ) ) stage.act("CONTROL_OUT", If(usb_core.endp == 0, If(usb_core.tok == PID.OUT, out_handler.data_recv_payload.eq(usb_core.data_recv_payload), out_handler.data_recv_put.eq(usb_core.data_recv_put), usb_core.sta.eq(should_stall), usb_core.arm.eq(out_handler.response | should_stall), out_handler.trigger.eq(usb_core.commit), ).Elif(usb_core.tok == PID.IN, usb_core.sta.eq(0), usb_core.arm.eq(1), NextState("WAIT_DONE"), ) ), ) stage.act("WAIT_CONTROL_ACK_IN", usb_core.sta.eq(0), usb_core.arm.eq(1), usb_core.dtb.eq(1), If(usb_core.end, NextState("IDLE") ), ) stage.act("WAIT_CONTROL_ACK_OUT", usb_core.sta.eq(0), usb_core.arm.eq(1), usb_core.dtb.eq(1), If(usb_core.end, NextState("IDLE") ), ) stage.act("IN", # If(usb_core.tok == PID.IN, # # IN packet (device-to-host) usb_core.data_send_have.eq(in_handler.data_out_have), usb_core.data_send_payload.eq(in_handler.data_out), in_handler.data_out_advance.eq(usb_core.data_send_get), usb_core.sta.eq(should_stall), usb_core.arm.eq(in_handler.response | should_stall), usb_core.dtb.eq(in_handler.dtb), in_handler.trigger.eq(usb_core.commit), # After an IN transfer, the host sends an OUT # packet. We must ACK this and then return to IDLE. If(usb_core.end, NextState("IDLE"), ), # ), ) stage.act("OUT", # OUT packet (host-to-device) out_handler.data_recv_payload.eq(usb_core.data_recv_payload), out_handler.data_recv_put.eq(usb_core.data_recv_put), usb_core.sta.eq(should_stall), usb_core.arm.eq(out_handler.response | should_stall), out_handler.trigger.eq(usb_core.commit), # After an OUT transfer, the host sends an IN # packet. We must ACK this and then return to IDLE. If(usb_core.end, NextState("IDLE"), ), ) stage.act("WAIT_DONE", usb_core.sta.eq(0), usb_core.arm.eq(1), If(usb_core.commit, NextState("IDLE"), ), ) self.comb += [ enable.eq(Cat(self.enable_out0.storage, self.enable_out1.storage, self.enable_in0.storage, self.enable_in1.storage)), should_stall.eq(~(enable >> eps_idx)), ] error_count = Signal(8) self.sync += [ # Reset the transfer state machine if it gets into an error If(usb_core.error, error_count.eq(error_count + 1), usb_core.reset.eq(1), ), ]