Skip to content

Commit

Permalink
Use address space abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
alexforencich committed Nov 17, 2021
1 parent 3a0b60a commit 3161b0a
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 518 deletions.
180 changes: 16 additions & 164 deletions cocotbext/pcie/core/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
from cocotb.queue import Queue
from cocotb.triggers import Event, Timer, First

from cocotbext.axi import AddressSpace

from .caps import PcieCapList, PcieExtCapList
from .caps import PmCapability, PcieCapability
from .region import MemoryTlpRegion, IoTlpRegion
from .tlp import Tlp, TlpType, TlpAttr, TlpTc, CplStatus
from .utils import PcieId

Expand Down Expand Up @@ -118,6 +121,15 @@ def __init__(self, *args, **kwargs):
self.pcie_cap = PcieCapability()
self.register_capability(self.pcie_cap)

self.mem_address_space = AddressSpace(2**64)
self.io_address_space = AddressSpace(2**32)

self.mem_region = MemoryTlpRegion(self)
self.io_region = IoTlpRegion(self)

self.mem_address_space.register_region(self.mem_region, 0, 2**64)
self.io_address_space.register_region(self.io_region, 0, 2**32)

super().__init__(*args, **kwargs)

@property
Expand Down Expand Up @@ -544,46 +556,7 @@ async def handle_config_0_write_tlp(self, tlp):
await self.upstream_send(cpl)

async def io_read(self, addr, length, timeout=0, timeout_unit='ns'):
n = 0
data = b''

if not self.bus_master_enable:
self.log.warning("Bus mastering not enabled, aborting")
return None

while True:
tlp = Tlp()
tlp.fmt_type = TlpType.IO_READ
tlp.requester_id = self.pcie_id

first_pad = addr % 4
byte_length = min(length-n, 4-first_pad)
tlp.set_addr_be(addr, byte_length)

tlp.tag = await self.alloc_tag()

await self.send(tlp)
cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit)

self.release_tag(tlp.tag)

if not cpl:
raise Exception("Timeout")
if cpl.status != CplStatus.SC:
raise Exception("Unsuccessful completion")
else:
assert cpl.length == 1
d = cpl.get_data()

data += d[first_pad:]

n += byte_length
addr += byte_length

if n >= length:
break

return data[:length]
return await self.io_address_space.read(addr, length, timeout=timeout, timeout_unit=timeout_unit)

async def io_read_words(self, addr, count, byteorder='little', ws=2, timeout=0, timeout_unit='ns'):
data = await self.io_read(addr, count*ws, timeout, timeout_unit)
Expand Down Expand Up @@ -611,38 +584,7 @@ async def io_read_qword(self, addr, byteorder='little', timeout=0, timeout_unit=
return (await self.io_read_qwords(addr, 1, byteorder, timeout, timeout_unit))[0]

async def io_write(self, addr, data, timeout=0, timeout_unit='ns'):
n = 0

if not self.bus_master_enable:
self.log.warning("Bus mastering not enabled, aborting")
return

while True:
tlp = Tlp()
tlp.fmt_type = TlpType.IO_WRITE
tlp.requester_id = self.pcie_id

first_pad = addr % 4
byte_length = min(len(data)-n, 4-first_pad)
tlp.set_addr_be_data(addr, data[n:n+byte_length])

tlp.tag = await self.alloc_tag()

await self.send(tlp)
cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit)

self.release_tag(tlp.tag)

if not cpl:
raise Exception("Timeout")
if cpl.status != CplStatus.SC:
raise Exception("Unsuccessful completion")

n += byte_length
addr += byte_length

if n >= len(data):
break
await self.io_address_space.write(addr, data, timeout=timeout, timeout_unit=timeout_unit)

async def io_write_words(self, addr, data, byteorder='little', ws=2, timeout=0, timeout_unit='ns'):
words = data
Expand Down Expand Up @@ -670,72 +612,7 @@ async def io_write_qword(self, addr, data, byteorder='little', timeout=0, timeou
await self.io_write_qwords(addr, [data], byteorder, timeout, timeout_unit)

async def mem_read(self, addr, length, timeout=0, timeout_unit='ns', attr=TlpAttr(0), tc=TlpTc.TC0):
n = 0
data = b''

if not self.bus_master_enable:
self.log.warning("Bus mastering not enabled, aborting")
return None

while True:
tlp = Tlp()
if addr > 0xffffffff:
tlp.fmt_type = TlpType.MEM_READ_64
else:
tlp.fmt_type = TlpType.MEM_READ
tlp.requester_id = self.pcie_id
tlp.attr = attr
tlp.tc = tc

first_pad = addr % 4
# remaining length
byte_length = length-n
# limit to max read request size
if byte_length > (128 << self.pcie_cap.max_read_request_size) - first_pad:
# split on 128-byte read completion boundary
byte_length = min(byte_length, (128 << self.pcie_cap.max_read_request_size) - (addr & 0x1f))
# 4k align
byte_length = min(byte_length, 0x1000 - (addr & 0xfff))
tlp.set_addr_be(addr, byte_length)

tlp.tag = await self.alloc_tag()

await self.send(tlp)

m = 0

while True:
cpl = await self.recv_cpl(tlp.tag, timeout, timeout_unit)

if not cpl:
self.release_tag(tlp.tag)
raise Exception("Timeout")
if cpl.status != CplStatus.SC:
self.release_tag(tlp.tag)
raise Exception("Unsuccessful completion")
else:
assert cpl.byte_count+3+(cpl.lower_address & 3) >= cpl.length*4
assert cpl.byte_count == max(byte_length - m, 1)

d = cpl.get_data()

offset = cpl.lower_address & 3
data += d[offset:offset+cpl.byte_count]

m += len(d)-offset

if m >= byte_length:
break

self.release_tag(tlp.tag)

n += byte_length
addr += byte_length

if n >= length:
break

return data[:length]
return await self.mem_address_space.read(addr, length, timeout=timeout, timeout_unit=timeout_unit, attr=attr, tc=tc)

async def mem_read_words(self, addr, count, byteorder='little', ws=2, timeout=0, timeout_unit='ns', attr=TlpAttr(0), tc=TlpTc.TC0):
data = await self.mem_read(addr, count*ws, timeout, timeout_unit, attr, tc)
Expand Down Expand Up @@ -763,32 +640,7 @@ async def mem_read_qword(self, addr, byteorder='little', timeout=0, timeout_unit
return (await self.mem_read_qwords(addr, 1, byteorder, timeout, timeout_unit, attr, tc))[0]

async def mem_write(self, addr, data, timeout=0, timeout_unit='ns', attr=TlpAttr(0), tc=TlpTc.TC0):
n = 0

if not self.bus_master_enable:
self.log.warning("Bus mastering not enabled, aborting")
return

while n < len(data):
tlp = Tlp()
if addr > 0xffffffff:
tlp.fmt_type = TlpType.MEM_WRITE_64
else:
tlp.fmt_type = TlpType.MEM_WRITE
tlp.requester_id = self.pcie_id
tlp.attr = attr
tlp.tc = tc

first_pad = addr % 4
byte_length = len(data)-n
byte_length = min(byte_length, (128 << self.pcie_cap.max_payload_size)-first_pad) # max payload size
byte_length = min(byte_length, 0x1000 - (addr & 0xfff)) # 4k align
tlp.set_addr_be_data(addr, data[n:n+byte_length])

await self.send(tlp)

n += byte_length
addr += byte_length
await self.mem_address_space.write(addr, data, timeout=timeout, timeout_unit=timeout_unit, attr=attr, tc=tc)

async def mem_write_words(self, addr, data, byteorder='little', ws=2, timeout=0, timeout_unit='ns', attr=TlpAttr(0), tc=TlpTc.TC0):
words = data
Expand Down
133 changes: 133 additions & 0 deletions cocotbext/pcie/core/msi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Copyright (c) 2021 Alex Forencich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

import struct

import cocotb
from cocotb.triggers import Event

from cocotbext.axi import Region

from .caps import PcieCapId


class MsiRegion(Region):
def __init__(self, rc, size=16, *kwargs):
super().__init__(size=size, *kwargs)
self.rc = rc

self.msi_msg_limit = 0
self.msi_events = {}
self.msi_callbacks = {}

async def read(self, addr, length, **kwargs):
return bytearray(1)*length

async def write(self, addr, data, **kwargs):
assert addr == 0
assert len(data) == 4
number, = struct.unpack('<L', data)
self.rc.log.info("MSI interrupt: 0x%08x, 0x%04x", addr, number)
assert number in self.msi_events
self.msi_events[number].set()
for cb in self.msi_callbacks[number]:
cocotb.fork(cb())

async def configure_msi(self, dev):
if not self.rc.tree:
raise Exception("Enumeration has not yet been run")
ti = self.rc.tree.find_child_dev(dev)
if not ti:
raise Exception("Invalid device")
if ti.get_capability_offset(PcieCapId.MSI) is None:
raise Exception("Device does not support MSI")
if ti.msi_addr is not None and ti.msi_data is not None:
# already configured
return

self.rc.log.info("Configure MSI on %s", ti.pcie_id)

msg_ctrl = await self.rc.capability_read_dword(dev, PcieCapId.MSI, 0)

msi_64bit = (msg_ctrl >> 23) & 1
msi_mmcap = (msg_ctrl >> 17) & 7

msi_addr = self.get_absolute_address(0)

# message address
await self.rc.capability_write_dword(dev, PcieCapId.MSI, 4, msi_addr & 0xfffffffc)

if msi_64bit:
# 64 bit message address
# message upper address
await self.rc.capability_write_dword(dev, PcieCapId.MSI, 8, (msi_addr >> 32) & 0xffffffff)
# message data
await self.rc.capability_write_dword(dev, PcieCapId.MSI, 12, self.msi_msg_limit)

else:
# 32 bit message address
# message data
await self.rc.capability_write_dword(dev, PcieCapId.MSI, 8, self.msi_msg_limit)

# enable and set enabled messages
msg_ctrl |= 1 << 16
msg_ctrl = (msg_ctrl & ~(7 << 20)) | (msi_mmcap << 20)
await self.rc.capability_write_dword(dev, PcieCapId.MSI, 0, msg_ctrl)

ti.msi_count = 2**msi_mmcap
ti.msi_addr = msi_addr
ti.msi_data = self.msi_msg_limit

self.rc.log.info("MSI count: %d", ti.msi_count)
self.rc.log.info("MSI address: 0x%08x", ti.msi_addr)
self.rc.log.info("MSI base data: 0x%08x", ti.msi_data)

for k in range(32):
self.msi_events[self.msi_msg_limit] = Event()
self.msi_callbacks[self.msi_msg_limit] = []
self.msi_msg_limit += 1

def get_event(self, dev, number=0):
if not self.rc.tree:
return None
ti = self.rc.tree.find_child_dev(dev)
if not ti:
raise Exception("Invalid device")
if ti.msi_data is None:
raise Exception("MSI not configured on device")
if number < 0 or number >= ti.msi_count or ti.msi_data+number not in self.msi_events:
raise Exception("MSI number out of range")
return self.msi_events[ti.msi_data+number]

def register_callback(self, dev, callback, number=0):
if not self.rc.tree:
return
ti = self.rc.tree.find_child_dev(dev)
if not ti:
raise Exception("Invalid device")
if ti.msi_data is None:
raise Exception("MSI not configured on device")
if number < 0 or number >= ti.msi_count or ti.msi_data+number not in self.msi_callbacks:
raise Exception("MSI number out of range")
self.msi_callbacks[ti.msi_data+number].append(callback)
Loading

0 comments on commit 3161b0a

Please sign in to comment.