blob: 72b893d6dd826b7a0046f3e156dafea2064f62fc [file] [edit]
-------------------------- MODULE openbmc_usb_mctp --------------------------
EXTENDS Integers, Sequences, TLC, FiniteSets
CONSTANTS MAX_QUEUE_DEPTH, d1
const_1 == {d1}
symm_1 == Permutations(const_1)
CONSTANT Devices
DeviceStateType == { "unplugged", "plugged" }
DeviceEventType == [ dev: Devices, state: DeviceStateType ]
PlugEvent(dev) == [ dev |-> dev, state |-> "plugged" ]
UnplugEvent(dev) == [ dev |-> dev, state |-> "unplugged"]
InventoryStateType == { "absent", "present" }
InventoryEventType == [ dev: Devices, state: InventoryStateType ]
PublishEvent(dev) == [ dev |-> dev, state |-> "present" ]
RetractEvent(dev) == [ dev |-> dev, state |-> "absent" ]
NetdevStateType == { "absent", "present" }
NetdevEventType == [ dev: Devices, state: NetdevStateType ]
NetdevAddEvent(dev) == [ dev |-> dev, state |-> "present" ]
NetdevRemoveEvent(dev) == [ dev |-> dev, state |-> "absent" ]
MctpdActionType == { "assign", "remove" }
MctpdRequestType == [ dev: Devices, action: MctpdActionType ]
MctpdAssignRequest(dev) == [ dev |-> dev, action |-> "assign" ]
MctpdRemoveRequest(dev) == [ dev |-> dev, action |-> "remove" ]
MctpdEndpointStateType == { "unmanaged", "managed" }
MctpdEndpointEventType == [ dev: Devices, state: MctpdEndpointStateType ]
MctpdEndpointAddEvent(dev) == [ dev |-> dev, state |-> "managed" ]
MctpdEndpointRemoveEvent(dev) == [ dev |-> dev, state |-> "unmanaged" ]
ReactorEndpointStateType == { "unmanaged", "assigning", "unassigned", "assigned", "quarantine", "lost", "recovering", "recovered", "removing", "pending" }
ReactorEndpointEntryType == [ Devices -> ReactorEndpointStateType ]
ReactorEndpointInit == [ dev \in Devices |-> "unmanaged" ]
VARIABLES devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps
vars == <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps>>
TypeInvariant ==
/\ devs \subseteq Devices
/\ usb_config_q \in Seq(DeviceEventType)
/\ inventory_q \in Seq(DeviceEventType)
/\ mctp_iface_q \in Seq(NetdevEventType)
/\ ifaces \subseteq Devices
/\ reactor_dev_q \in Seq(InventoryEventType)
/\ mctpd_q \in Seq(MctpdRequestType)
/\ mctpd_offers \subseteq Devices
/\ assigned \subseteq Devices
/\ dev_accepts \subseteq Devices
/\ mctpd_assigned \subseteq Devices
/\ reactor_ep_q \in Seq(MctpdEndpointEventType)
/\ reactor_eps \in ReactorEndpointEntryType
Invariants == TypeInvariant
Safety ==
/\ [][~(\A dev \in Devices: reactor_eps[dev] = "recovered" /\ reactor_eps[dev]' = "removing")]_reactor_eps
/\ [][~(\E dev \in DOMAIN reactor_eps:
/\ reactor_eps[dev] \in { "unmanaged", "assigning", "unassigned", "quarantine", "lost", "recovering" }
/\ mctpd_q = <<>>
/\ mctpd_q' # <<>>
/\ mctpd_q[1]' = MctpdRemoveRequest(dev))
]_<<reactor_eps, mctpd_q>>
Liveness ==
/\ [](\A dev \in Devices: dev \in devs ~> (reactor_eps[dev] = "assigned" \/ dev \notin devs))
/\ [](\A dev \in Devices:
/\ reactor_eps[dev] = "assigning" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unassigned", "assigned", "quarantine"})
/\ reactor_eps[dev] = "unassigned" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unmanaged", "assigning"})
/\ reactor_eps[dev] = "quarantine" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unmanaged", "recovering", "recovered"})
/\ reactor_eps[dev] = "lost" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unmanaged", "recovering"})
/\ reactor_eps[dev] = "recovering" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"quarantine", "lost", "recovered"})
/\ reactor_eps[dev] = "removing" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unmanaged", "assigning", "pending"})
/\ reactor_eps[dev] = "pending" ~> reactor_eps[dev] \notin (ReactorEndpointStateType \ {"unassigned", "removing"})
)
Properties == Safety /\ Liveness
Init ==
/\ devs = {}
/\ usb_config_q = <<>>
/\ inventory_q = <<>>
/\ mctp_iface_q = <<>>
/\ ifaces = {}
/\ reactor_dev_q = <<>>
/\ mctpd_q = <<>>
/\ mctpd_offers = {}
/\ assigned = {}
/\ dev_accepts = {}
/\ mctpd_assigned = {}
/\ reactor_ep_q = <<>>
/\ reactor_eps = ReactorEndpointInit
Enter(set, dev) == set' = set \union { dev }
Exit(set, dev) == set' = set \ { dev }
Plug(dev) ==
/\ dev \notin devs
/\ Len(usb_config_q) < MAX_QUEUE_DEPTH
/\ Len(inventory_q) < MAX_QUEUE_DEPTH
/\ Enter(devs, dev)
/\ usb_config_q' = Append(usb_config_q, PlugEvent(dev))
/\ inventory_q' = Append(inventory_q, PlugEvent(dev))
/\ UNCHANGED <<mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps>>
Unplug(dev) ==
/\ dev \in devs
/\ Len(usb_config_q) < MAX_QUEUE_DEPTH
/\ Len(inventory_q) < MAX_QUEUE_DEPTH
/\ Exit(devs, dev)
/\ Exit(ifaces, dev)
/\ Exit(assigned, dev)
/\ Exit(dev_accepts, dev)
/\ usb_config_q' = Append(usb_config_q, UnplugEvent(dev))
/\ inventory_q' = Append(inventory_q, UnplugEvent(dev))
/\ UNCHANGED <<mctp_iface_q, reactor_dev_q, mctpd_q, mctpd_offers, mctpd_assigned, reactor_ep_q, reactor_eps>>
PublishInventory(dev) ==
/\ Len(reactor_dev_q) < MAX_QUEUE_DEPTH
/\ reactor_dev_q' = Append(reactor_dev_q, PublishEvent(dev))
UnpublishInventory(dev) ==
/\ Len(reactor_dev_q) < MAX_QUEUE_DEPTH
/\ reactor_dev_q' = Append(reactor_dev_q, RetractEvent(dev))
UsbConfig(dev) ==
/\ Len(mctp_iface_q) < MAX_QUEUE_DEPTH
/\ mctp_iface_q' = Append(mctp_iface_q, NetdevAddEvent(dev))
UsbDeconfig(dev) ==
/\ Len(mctp_iface_q) < MAX_QUEUE_DEPTH
/\ mctp_iface_q' = Append(mctp_iface_q, NetdevRemoveEvent(dev))
MctpdAssignEndpoint(dev) ==
/\ Len(mctpd_q) < MAX_QUEUE_DEPTH
/\ mctpd_q' = Append(mctpd_q, MctpdAssignRequest(dev))
MctpdRemoveEndpoint(dev) ==
/\ Len(mctpd_q) < MAX_QUEUE_DEPTH
/\ mctpd_q' = Append(mctpd_q, MctpdRemoveRequest(dev))
MctpdEndpointAdd(dev) ==
/\ Len(reactor_ep_q) < MAX_QUEUE_DEPTH
/\ reactor_ep_q' = Append(reactor_ep_q, MctpdEndpointAddEvent(dev))
MctpdEndpointRemove(dev) ==
/\ Len(reactor_ep_q) < MAX_QUEUE_DEPTH
/\ reactor_ep_q' = Append(reactor_ep_q, MctpdEndpointRemoveEvent(dev))
MctpdControlEvent ==
/\ \E dev \in dev_accepts:
/\ Exit(dev_accepts, dev)
/\ Enter(mctpd_assigned, dev)
/\ MctpdEndpointAdd(dev)
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, reactor_eps>>
DeviceAcceptOffer(dev) ==
/\ dev \in mctpd_offers
/\ Exit(mctpd_offers, dev)
/\ Enter(assigned, dev)
/\ IF dev \in ifaces THEN Enter(dev_accepts, dev) ELSE UNCHANGED <<dev_accepts>>
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_assigned, reactor_ep_q, reactor_eps>>
MctpdIfaceEvent ==
/\ \E dev \in mctpd_assigned \ ifaces:
/\ Exit(mctpd_assigned, dev)
/\ MctpdEndpointRemove(dev)
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, reactor_eps>>
MctpdQEvent ==
/\ Len(mctpd_q) # 0
/\ mctpd_q' = Tail(mctpd_q)
\* FIXME: reactor_eps hack! Need method response values
/\ LET ev == Head(mctpd_q)
IN \/ /\ ev.action = "assign"
/\ IF ev.dev \in devs /\ ev.dev \in ifaces
THEN /\ Enter(mctpd_offers, ev.dev)
/\ UNCHANGED <<reactor_eps>>
ELSE /\ reactor_eps' = CASE reactor_eps[ev.dev] = "assigning" -> [ reactor_eps EXCEPT ![ev.dev] = "unassigned" ]
[] reactor_eps[ev.dev] = "quarantine" -> [ reactor_eps EXCEPT ![ev.dev] = "unmanaged" ]
[] reactor_eps[ev.dev] = "recovering" -> [ reactor_eps EXCEPT ![ev.dev] = "lost" ]
[] OTHER -> reactor_eps
/\ UNCHANGED <<mctpd_offers>>
/\ UNCHANGED <<mctpd_assigned, reactor_ep_q>>
\/ /\ ev.action = "remove"
/\ IF ev.dev \in mctpd_assigned
THEN /\ Exit(mctpd_assigned, ev.dev)
/\ MctpdEndpointRemove(ev.dev)
ELSE UNCHANGED <<mctpd_assigned, reactor_ep_q>>
/\ UNCHANGED <<mctpd_offers, reactor_eps>>
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, assigned, dev_accepts>>
ReactorDeferredSetup ==
/\ \E dev \in DOMAIN reactor_eps:
/\ reactor_eps[dev] \in { "unassigned", "lost" }
/\ dev \in ifaces \* NOTE: Not present in upstream mctpreactor implementation
/\ MctpdAssignEndpoint(dev)
/\ reactor_eps' = CASE reactor_eps[dev] = "unassigned" -> [ reactor_eps EXCEPT ![dev] = "assigning" ]
[] reactor_eps[dev] = "lost" -> [ reactor_eps EXCEPT ![dev] = "recovering" ]
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q>>
ReactorEndpointQEvent ==
/\ Len(reactor_ep_q) # 0
/\ LET ev == Head(reactor_ep_q)
IN \/ /\ ev.state = "managed"
/\ reactor_eps' = CASE reactor_eps[ev.dev] = "assigning" -> [ reactor_eps EXCEPT ![ev.dev] = "assigned" ]
[] reactor_eps[ev.dev] = "recovering" -> [ reactor_eps EXCEPT ![ev.dev] = "recovered" ]
[] reactor_eps[ev.dev] = "quarantine" -> [ reactor_eps EXCEPT ![ev.dev] = "recovered" ]
[] OTHER -> reactor_eps
\/ /\ ev.state = "unmanaged"
/\ reactor_eps' = CASE reactor_eps[ev.dev] = "assigned" -> [ reactor_eps EXCEPT ![ev.dev] = "lost" ]
[] reactor_eps[ev.dev] = "quarantine" -> [ reactor_eps EXCEPT ![ev.dev] = "unmanaged" ]
[] reactor_eps[ev.dev] = "recovered" -> [ reactor_eps EXCEPT ![ev.dev] = "lost" ]
[] reactor_eps[ev.dev] = "removing" -> [ reactor_eps EXCEPT ![ev.dev] = "unmanaged" ]
[] reactor_eps[ev.dev] = "pending" -> [ reactor_eps EXCEPT ![ev.dev] = "unassigned" ]
[] OTHER -> reactor_eps
/\ reactor_ep_q' = Tail(reactor_ep_q)
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned>>
ReactorDeviceQEvent ==
/\ Len(reactor_dev_q) # 0
/\ LET ev == Head(reactor_dev_q)
IN \/ /\ ev.state = "present"
/\ \/ /\ reactor_eps[ev.dev] \in { "unmanaged" }
/\ MctpdAssignEndpoint(ev.dev)
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "assigning" ]
\/ /\ reactor_eps[ev.dev] \in { "quarantine" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "recovering" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ reactor_eps[ev.dev] \in { "removing" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "pending" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ reactor_eps[ev.dev] \in { "unassigned", "assigning", "assigned", "lost", "recovering", "pending" }
/\ UNCHANGED <<mctpd_q, reactor_eps>>
\/ /\ reactor_eps[ev.dev] \in { "recovered" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "assigned" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ ev.state = "absent"
/\ \/ /\ reactor_eps[ev.dev] \in { "unmanaged", "recovered", "quarantine", "removing" }
/\ UNCHANGED <<mctpd_q, reactor_eps>>
\/ /\ reactor_eps[ev.dev] \in { "assigning", "recovering" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "quarantine" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ reactor_eps[ev.dev] \in { "unassigned", "lost" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "unmanaged" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ reactor_eps[ev.dev] \in { "pending" }
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "removing" ]
/\ UNCHANGED <<mctpd_q>>
\/ /\ reactor_eps[ev.dev] \in { "assigned" }
/\ MctpdRemoveEndpoint(ev.dev)
/\ reactor_eps' = [ reactor_eps EXCEPT ![ev.dev] = "removing" ]
/\ reactor_dev_q' = Tail(reactor_dev_q)
/\ UNCHANGED <<devs, usb_config_q, inventory_q, mctp_iface_q, ifaces, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q>>
MctpIfaceQEvent ==
/\ Len(mctp_iface_q) # 0
/\ LET ev == Head(mctp_iface_q)
IN IF ev.dev \in devs
THEN \/ ev.state = "present" /\ Enter(ifaces, ev.dev)
\/ ev.state = "absent" /\ Exit(ifaces, ev.dev)
ELSE UNCHANGED <<ifaces>>
/\ mctp_iface_q' = Tail(mctp_iface_q)
/\ UNCHANGED <<devs, usb_config_q, inventory_q, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps>>
InventoryQEvent ==
/\ Len(inventory_q) # 0
/\ LET ev == Head(inventory_q)
IN \/ /\ ev.state = "plugged"
/\ PublishInventory(ev.dev)
\/ /\ ev.state = "unplugged"
/\ UnpublishInventory(ev.dev)
/\ inventory_q' = Tail(inventory_q)
/\ UNCHANGED <<devs, usb_config_q, mctp_iface_q, ifaces, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps>>
UsbConfigQEvent ==
/\ Len(usb_config_q) # 0
/\ LET ev == Head(usb_config_q)
IN \/ /\ ev.state = "plugged"
/\ UsbConfig(ev.dev) \* Interface needs an explicit "configure" step when plugged
\/ /\ ev.state = "unplugged"
/\ UNCHANGED <<mctp_iface_q>> \* Interface doesn't need an explicit "deconfigure" step when removed"
/\ usb_config_q' = Tail(usb_config_q)
/\ UNCHANGED <<devs, inventory_q, ifaces, reactor_dev_q, mctpd_q, mctpd_offers, assigned, dev_accepts, mctpd_assigned, reactor_ep_q, reactor_eps>>
Next ==
\/ \E dev \in Devices:
\/ Plug(dev)
\/ Unplug(dev)
\/ DeviceAcceptOffer(dev)
\/ UsbConfigQEvent
\/ InventoryQEvent
\/ MctpIfaceQEvent
\/ ReactorDeviceQEvent
\/ MctpdQEvent
\/ MctpdControlEvent
\/ ReactorEndpointQEvent
\/ ReactorDeferredSetup
\/ MctpdIfaceEvent
Spec == Init /\ [][Next]_vars /\ WF_vars(Next)
=============================================================================
\* Modification History
\* Last modified Sun Nov 02 21:48:08 ACDT 2025 by andrew
\* Created Fri Oct 31 16:01:47 ACDT 2025 by andrew