You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
12 KiB
Python
318 lines
12 KiB
Python
from enum import IntEnum, unique
|
|
|
|
from nmigen import Signal, Elaboratable, Module, ResetInserter, Cat
|
|
from nmigen_soc.wishbone import Interface as WishboneInterface
|
|
from nmigen.lib.fifo import SyncFIFOBuffered
|
|
|
|
from nmigen.back import verilog
|
|
|
|
|
|
@unique
|
|
class RegEnum(IntEnum):
|
|
BT_CTRL = 0
|
|
BMC2HOST_HOST2BMC = 1
|
|
BT_INTMASK = 2
|
|
|
|
|
|
@unique
|
|
class BMCRegEnum(IntEnum):
|
|
IRQ_MASK = 0
|
|
IRQ_STATUS = 1
|
|
BT_CTRL = 4
|
|
BMC2HOST_HOST2BMC = 5
|
|
|
|
|
|
@unique
|
|
class StateEnum(IntEnum):
|
|
IDLE = 0
|
|
ACK = 1
|
|
|
|
|
|
@unique
|
|
class BMCIRQEnum(IntEnum):
|
|
TARGET_TO_BMC_ATTN = 0
|
|
TARGET_NOT_BUSY = 1
|
|
|
|
|
|
class IPMI_BT(Elaboratable):
|
|
def __init__(self, depth=64):
|
|
self.depth = depth
|
|
|
|
self.bmc_wb = WishboneInterface(data_width=8, addr_width=3)
|
|
self.bmc_irq = Signal()
|
|
|
|
self.target_wb = WishboneInterface(data_width=8, addr_width=2)
|
|
self.target_irq = Signal()
|
|
|
|
def elaborate(self, platform):
|
|
m = Module()
|
|
|
|
# Reset signals for the FIFOs, since BT_CTRL needs to be able to clear them
|
|
reset_from_bmc_fifo = Signal()
|
|
reset_from_target_fifo = Signal()
|
|
m.d.sync += [
|
|
reset_from_bmc_fifo.eq(0),
|
|
reset_from_target_fifo.eq(0)
|
|
]
|
|
|
|
# BMC -> Target FIFO
|
|
m.submodules.from_bmc_fifo = from_bmc_fifo = ResetInserter(
|
|
reset_from_bmc_fifo)(SyncFIFOBuffered(width=8, depth=self.depth))
|
|
|
|
# Target -> BMC FIFO
|
|
m.submodules.from_target_fifo = from_target_fifo = ResetInserter(
|
|
reset_from_target_fifo)(SyncFIFOBuffered(width=8, depth=self.depth))
|
|
|
|
# Wire up wishbone to FIFO write
|
|
m.d.comb += [
|
|
from_bmc_fifo.w_data.eq(self.bmc_wb.dat_w),
|
|
from_target_fifo.w_data.eq(self.target_wb.dat_w)
|
|
]
|
|
|
|
# Some wishbone helpers
|
|
is_bmc_write = Signal()
|
|
is_bmc_read = Signal()
|
|
m.d.comb += [
|
|
is_bmc_write.eq(self.bmc_wb.stb & self.bmc_wb.cyc & self.bmc_wb.we),
|
|
is_bmc_read.eq(self.bmc_wb.stb & self.bmc_wb.cyc & ~self.bmc_wb.we)
|
|
]
|
|
is_target_read = Signal()
|
|
is_target_write = Signal()
|
|
m.d.comb += [
|
|
is_target_write.eq(self.target_wb.stb & self.target_wb.cyc & self.target_wb.we),
|
|
is_target_read.eq(self.target_wb.stb & self.target_wb.cyc & ~self.target_wb.we)
|
|
]
|
|
|
|
# BMC and target wishbone state machine
|
|
bmc_state = Signal(StateEnum, reset=StateEnum.IDLE)
|
|
target_state = Signal(StateEnum, reset=StateEnum.IDLE)
|
|
|
|
m.d.sync += [
|
|
from_bmc_fifo.w_en.eq(0),
|
|
from_bmc_fifo.r_en.eq(0),
|
|
from_target_fifo.w_en.eq(0),
|
|
from_target_fifo.r_en.eq(0)
|
|
]
|
|
|
|
m.d.sync += [
|
|
self.bmc_wb.ack.eq(0),
|
|
self.target_wb.ack.eq(0)
|
|
]
|
|
|
|
# Don't read from empty FIFOs
|
|
from_bmc_fifo_read_data = Signal(8)
|
|
m.d.comb += from_bmc_fifo_read_data.eq(0)
|
|
with m.If(from_bmc_fifo.r_rdy):
|
|
m.d.comb += from_bmc_fifo_read_data.eq(from_bmc_fifo.r_data)
|
|
|
|
from_target_fifo_read_data = Signal(8)
|
|
m.d.comb += from_target_fifo_read_data.eq(0)
|
|
with m.If(from_target_fifo.r_rdy):
|
|
m.d.comb += from_target_fifo_read_data.eq(from_target_fifo.r_data)
|
|
|
|
# BT_CTRL bits
|
|
target_to_bmc_attn = Signal()
|
|
bmc_to_target_attn = Signal()
|
|
sms_attn = Signal()
|
|
platform_reserved = Signal()
|
|
bmc_busy = Signal()
|
|
target_busy = Signal()
|
|
|
|
bt_ctrl = Signal(8)
|
|
m.d.comb += bt_ctrl.eq(Cat(0, 0, target_to_bmc_attn, bmc_to_target_attn,
|
|
sms_attn, platform_reserved, target_busy,
|
|
bmc_busy))
|
|
|
|
# BT_INTMASK (target interrupt mask) bits
|
|
bmc_to_target_irq_en = Signal()
|
|
bmc_to_target_irq = Signal()
|
|
|
|
m.d.comb += self.target_irq.eq(bmc_to_target_irq_en & bmc_to_target_irq)
|
|
|
|
# BMC interrupt bits. These are not architected by the IPMI BT spec. The
|
|
# Linux driver expects to get an interrupt whenever target_to_bmc_attn
|
|
# goes high (ready to read) or target_busy goes low (ready to write). We
|
|
# don't interrupt on bmc_to_target_attn going low (which is also required
|
|
# for ready to write) but rely on the target driver setting target_busy low
|
|
# right after it sets bmc_to_target_attn low.
|
|
bmc_irq_en = Signal(2)
|
|
bmc_irq = Signal(2)
|
|
|
|
m.d.comb += self.bmc_irq.eq(bmc_irq_en & bmc_irq)
|
|
|
|
# Target wishbone state machine
|
|
with m.Switch(target_state):
|
|
with m.Case(StateEnum.IDLE):
|
|
with m.If(is_target_write):
|
|
with m.Switch(self.target_wb.adr):
|
|
with m.Case(RegEnum.BT_CTRL):
|
|
# Bit 0, write 1 to clear the write fifo (ie from_target_fifo)
|
|
with m.If(self.target_wb.dat_w[0]):
|
|
m.d.sync += reset_from_target_fifo.eq(1)
|
|
|
|
# Bit 1 is meant to set the read FIFO to the next valid
|
|
# position, but since we only have a single buffer this
|
|
# doesn't need to do anything.
|
|
|
|
with m.If(self.target_wb.dat_w[2]):
|
|
# Trigger an interrupt whenever we set target_to_bmc_attn
|
|
with m.If(bmc_irq_en[BMCIRQEnum.TARGET_TO_BMC_ATTN]):
|
|
m.d.sync += bmc_irq[BMCIRQEnum.TARGET_TO_BMC_ATTN].eq(1)
|
|
m.d.sync += target_to_bmc_attn.eq(1)
|
|
|
|
# Bit 3, write 1 to clear bmc_to_target_attn
|
|
with m.If(self.target_wb.dat_w[3]):
|
|
m.d.sync += bmc_to_target_attn.eq(0)
|
|
|
|
# Bit 4, write 1 to clear sms_attn
|
|
with m.If(self.target_wb.dat_w[4]):
|
|
m.d.sync += sms_attn.eq(0)
|
|
|
|
# Bit 5, write 1 to set platform reserved
|
|
with m.If(self.target_wb.dat_w[5]):
|
|
m.d.sync += platform_reserved.eq(1)
|
|
|
|
# Bit 6, write 1 to toggle target_busy
|
|
with m.If(self.target_wb.dat_w[6]):
|
|
# Trigger an interrupt whenever we clear target_busy
|
|
with m.If(target_busy & bmc_irq_en[BMCIRQEnum.TARGET_NOT_BUSY]):
|
|
m.d.sync += bmc_irq[BMCIRQEnum.TARGET_NOT_BUSY].eq(1)
|
|
m.d.sync += target_busy.eq(~target_busy)
|
|
|
|
# Bit 7, read only
|
|
|
|
with m.Case(RegEnum.BMC2HOST_HOST2BMC):
|
|
# Only assert write if there is space
|
|
m.d.sync += from_target_fifo.w_en.eq(from_target_fifo.w_rdy)
|
|
|
|
with m.Case(RegEnum.BT_INTMASK):
|
|
# Bit 0, 0/1 write
|
|
m.d.sync += bmc_to_target_irq_en.eq(self.target_wb.dat_w[0])
|
|
|
|
# Bit 1, write 1 to clear interrupt
|
|
with m.If(self.target_wb.dat_w[1]):
|
|
m.d.sync += bmc_to_target_irq.eq(0)
|
|
|
|
m.d.sync += [
|
|
self.target_wb.ack.eq(1),
|
|
target_state.eq(StateEnum.ACK)
|
|
]
|
|
|
|
with m.If(is_target_read):
|
|
with m.Switch(self.target_wb.adr):
|
|
with m.Case(RegEnum.BT_CTRL):
|
|
m.d.sync += self.target_wb.dat_r.eq(bt_ctrl)
|
|
|
|
with m.Case(RegEnum.BMC2HOST_HOST2BMC):
|
|
m.d.sync += [
|
|
self.target_wb.dat_r.eq(from_bmc_fifo_read_data),
|
|
from_bmc_fifo.r_en.eq(from_bmc_fifo.r_rdy),
|
|
]
|
|
|
|
with m.Case(RegEnum.BT_INTMASK):
|
|
m.d.sync += self.target_wb.dat_r.eq(Cat(bmc_to_target_irq_en, bmc_to_target_irq))
|
|
|
|
m.d.sync += [
|
|
self.target_wb.ack.eq(1),
|
|
target_state.eq(StateEnum.ACK)
|
|
]
|
|
|
|
with m.Case(StateEnum.ACK):
|
|
m.d.sync += [
|
|
self.target_wb.ack.eq(0),
|
|
target_state.eq(StateEnum.IDLE),
|
|
]
|
|
|
|
# BMC wishbone state machine
|
|
with m.Switch(bmc_state):
|
|
with m.Case(StateEnum.IDLE):
|
|
with m.If(is_bmc_write):
|
|
with m.Switch(self.bmc_wb.adr):
|
|
with m.Case(BMCRegEnum.BT_CTRL):
|
|
# Bit 0, write 1 to clear the write fifo (ie from_bmc_fifo)
|
|
with m.If(self.bmc_wb.dat_w[0]):
|
|
m.d.sync += reset_from_bmc_fifo.eq(1)
|
|
|
|
# Bit 1 is meant to set the read FIFO to the next valid
|
|
# position, but since we only have a single buffer this
|
|
# doesn't need to do anything.
|
|
|
|
# Bit 2, write to clear target_to_bmc_attn
|
|
with m.If(self.bmc_wb.dat_w[2]):
|
|
m.d.sync += target_to_bmc_attn.eq(0)
|
|
|
|
# Bit 3, write 1 to set bmc_to_target_attn
|
|
with m.If(self.bmc_wb.dat_w[3]):
|
|
# Trigger an interrupt whenever we set bmc_to_target_attn
|
|
with m.If(bmc_to_target_irq_en):
|
|
m.d.sync += bmc_to_target_irq.eq(1)
|
|
m.d.sync += bmc_to_target_attn.eq(1)
|
|
|
|
# Bit 4, write 1 to set sms_attn
|
|
with m.If(self.bmc_wb.dat_w[4]):
|
|
# Trigger an interrupt whenever we set sms_attn
|
|
with m.If(bmc_to_target_irq_en):
|
|
m.d.sync += bmc_to_target_irq.eq(1)
|
|
m.d.sync += sms_attn.eq(1)
|
|
|
|
# Bit 5, write 1 to clear platform reserved
|
|
with m.If(self.bmc_wb.dat_w[5]):
|
|
m.d.sync += platform_reserved.eq(0)
|
|
|
|
# Bit 6, read only
|
|
|
|
# Bit 7, write 1 to toggle bmc_busy
|
|
with m.If(self.bmc_wb.dat_w[7]):
|
|
m.d.sync += bmc_busy.eq(~bmc_busy)
|
|
|
|
with m.Case(BMCRegEnum.BMC2HOST_HOST2BMC):
|
|
# Only assert write if there is space
|
|
m.d.sync += from_bmc_fifo.w_en.eq(from_bmc_fifo.w_rdy)
|
|
|
|
with m.Case(BMCRegEnum.IRQ_MASK):
|
|
m.d.sync += bmc_irq_en.eq(self.bmc_wb.dat_w)
|
|
|
|
with m.Case(BMCRegEnum.IRQ_STATUS):
|
|
m.d.sync += bmc_irq.eq(self.bmc_wb.dat_w)
|
|
|
|
m.d.sync += [
|
|
self.bmc_wb.ack.eq(1),
|
|
bmc_state.eq(StateEnum.ACK)
|
|
]
|
|
|
|
with m.If(is_bmc_read):
|
|
with m.Switch(self.bmc_wb.adr):
|
|
with m.Case(BMCRegEnum.BT_CTRL):
|
|
m.d.sync += self.bmc_wb.dat_r.eq(bt_ctrl)
|
|
|
|
with m.Case(BMCRegEnum.BMC2HOST_HOST2BMC):
|
|
m.d.sync += [
|
|
self.bmc_wb.dat_r.eq(from_target_fifo_read_data),
|
|
from_target_fifo.r_en.eq(from_target_fifo.r_rdy),
|
|
]
|
|
|
|
with m.Case(BMCRegEnum.IRQ_MASK):
|
|
m.d.sync += self.bmc_wb.dat_r.eq(bmc_irq_en)
|
|
|
|
with m.Case(BMCRegEnum.IRQ_STATUS):
|
|
m.d.sync += self.bmc_wb.dat_r.eq(bmc_irq)
|
|
|
|
m.d.sync += [
|
|
self.bmc_wb.ack.eq(1),
|
|
bmc_state.eq(StateEnum.ACK)
|
|
]
|
|
|
|
with m.Case(StateEnum.ACK):
|
|
m.d.sync += [
|
|
self.bmc_wb.ack.eq(0),
|
|
bmc_state.eq(StateEnum.IDLE),
|
|
]
|
|
|
|
return m
|
|
|
|
|
|
if __name__ == "__main__":
|
|
top = IPMI_BT()
|
|
with open("ipmi_bt.v", "w") as f:
|
|
f.write(verilog.convert(top))
|