123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- # -*- coding: utf-8 -*-
- #
- # Driver for FPGA based PROFIBUS PHY.
- #
- # Copyright (c) 2019 Michael Buesch <m@bues.ch>
- #
- # Licensed under the terms of the GNU General Public License version 2,
- # or (at your option) any later version.
- #
- from __future__ import division, absolute_import, print_function, unicode_literals
- from pyprofibus.compat import *
- from pyprofibus.phy_fpga_driver.messages import *
- import multiprocessing
- import mmap
- import spidev
- import time
- import sys
- __all__ = [
- "FpgaPhyProc",
- ]
- class FpgaPhyProc(multiprocessing.Process):
- """I/O process.
- """
- # Event IDs
- EVENT_NEWSTAT = 0
- EVENT_RESET = 1
- EVENT_PARERR = 2
- EVENT_NOMAGIC = 3
- EVENT_INVALLEN = 4
- EVENT_PBLENERR = 5
- # Offsets into __shmStatus
- STATUS_RUNNING = 0x0
- STATUS_STOP = 0x40
- STATUS_ERROR = 0x80
- STATUS_CTRL_TXCOUNT = 0xC0
- STATUS_CTRL_RXCOUNT = 0x100
- STATUS_DATA_TXCOUNT = 0x140
- STATUS_DATA_RXCOUNT = 0x180
- STATUS_EVENTCOUNT_BASE = 0x1C0
- STATUS_EVENTCOUNT_NEWSTAT = STATUS_EVENTCOUNT_BASE + EVENT_NEWSTAT
- STATUS_EVENTCOUNT_RESET = STATUS_EVENTCOUNT_BASE + EVENT_RESET
- STATUS_EVENTCOUNT_PARERR = STATUS_EVENTCOUNT_BASE + EVENT_PARERR
- STATUS_EVENTCOUNT_NOMAGIC = STATUS_EVENTCOUNT_BASE + EVENT_NOMAGIC
- STATUS_EVENTCOUNT_INVALLEN = STATUS_EVENTCOUNT_BASE + EVENT_INVALLEN
- STATUS_EVENTCOUNT_PBLENERR = STATUS_EVENTCOUNT_BASE + EVENT_PBLENERR
- # I/O process return codes.
- ERROR_NONE = 0
- ERROR_OSERROR = 1
- ERROR_PERMISSION = 2
- # Meta data offsets.
- META_OFFS_LO = 0
- META_OFFS_HI = 1
- META_LEN = 2
- METASTRUCT_SIZE = 3
- def __init__(self, spiDev, spiChipSelect, spiSpeedHz):
- super(FpgaPhyProc, self).__init__()
- self.__rxDataCount = 0
- self.__rxCtrlCount = 0
- self.__rxCtrlRdOffs = 0
- self.__txCtrlWrOffs = 0
- self.__txDataWrOffs = 0
- self.__eventCountNewStat = 0
- self.__eventCountReset = 0
- self.__eventCountParErr = 0
- self.__eventCountNoMagic = 0
- self.__eventCountInvalLen = 0
- self.__eventCountPBLenErr = 0
- self.__spiDev = spiDev
- self.__spiChipSelect = spiChipSelect
- self.__spiSpeedHz = spiSpeedHz
- def makeSHM(length):
- shm = mmap.mmap(-1, length)
- shm[0:length] = b"\x00" * length
- return shm
- self.__shmLengths = 4096
- self.__shmMask = self.__shmLengths - 1
- self.__shmTxData = makeSHM(self.__shmLengths)
- self.__shmTxDataMeta = makeSHM(self.__shmLengths)
- self.__shmRxData = makeSHM(self.__shmLengths)
- self.__shmRxDataMeta = makeSHM(self.__shmLengths)
- self.__shmTxCtrl = makeSHM(self.__shmLengths)
- self.__shmRxCtrl = makeSHM(self.__shmLengths)
- self.__shmStatus = makeSHM(self.__shmLengths)
- def start(self):
- super(FpgaPhyProc, self).start()
- success = False
- for i in range(500):
- if self.__shmStatus[self.STATUS_RUNNING]:
- success = True
- break
- if self.__shmStatus[self.STATUS_ERROR] != self.ERROR_NONE:
- break
- if not self.is_alive():
- break
- time.sleep(0.01)
- if not success:
- self.shutdownProc()
- return success
- def __incShmStatus(self, index):
- self.__shmStatus[index] = (self.__shmStatus[index] + 1) & 0xFF
- def __ioProcMainLoop(self, spi):
- ctrlWrOffs = 0
- ctrlRdOffs = 0
- dataWrOffs = 0
- txDataCount = 0
- txCtrlCount = 0
- expectedRxLength = 0
- collectedRxLength = 0
- rxDataBuf = bytearray()
- shmMask = self.__shmMask
- CTRL_LEN = FpgaPhyMsgCtrl.CTRL_LEN
- RX_DATA_LEN = 11
- MIN_XFER_LEN = RX_DATA_LEN
- tailData = b""
- while not self.__shmStatus[self.STATUS_STOP]:
- txData = b""
- # Get the TX control data, if any.
- if txCtrlCount != self.__shmStatus[self.STATUS_CTRL_TXCOUNT]:
- # Get the TX control message.
- txData = bytearray(CTRL_LEN)
- for i in range(CTRL_LEN):
- txData[i] = self.__shmTxCtrl[(ctrlRdOffs + i) & shmMask]
- ctrlRdOffs = (ctrlRdOffs + CTRL_LEN) & shmMask
- txCtrlCount = (txCtrlCount + 1) & 0xFF
- # Get the PB TX data, if any.
- elif txDataCount != self.__shmStatus[self.STATUS_DATA_TXCOUNT]:
- metaBegin = txDataCount * self.METASTRUCT_SIZE
- dataRdOffs = self.__shmTxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask]
- dataRdOffs |= self.__shmTxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] << 8
- dataRdLen = self.__shmTxDataMeta[(metaBegin + self.META_LEN) & shmMask]
- # Construct the TX data message.
- txData = bytearray(dataRdLen + 2)
- txData[0] = FpgaPhyMsg.SPI_MS_MAGIC
- txData[1] = 1 << FpgaPhyMsg.SPI_FLG_START
- txData[1] |= FpgaPhyMsg.parity(txData[1]) << FpgaPhyMsg.SPI_FLG_PARITY
- for i in range(dataRdLen):
- txData[i + 2] = self.__shmTxData[(dataRdOffs + i) & shmMask]
- txDataCount = (txDataCount + 1) & 0xFF
- # Pad the TX data, if required.
- if len(txData) < MIN_XFER_LEN:
- txData += FpgaPhyMsg.PADDING_BYTE * (MIN_XFER_LEN - len(txData))
- # Run the SPI transfer (transmit and receive).
- rxData = bytes(spi.xfer2(txData))
- # If we have tail data, prepend it to the received data.
- if tailData:
- rxData = tailData + rxData
- tailData = b""
- # Strip all leading padding bytes.
- rxData = rxData.lstrip(FpgaPhyMsg.PADDING_BYTE)
- if not rxData:
- continue
- # The first byte must be the magic byte.
- if rxData[0] != FpgaPhyMsg.SPI_SM_MAGIC:
- # Magic mismatch. Try to find the magic byte.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_NOMAGIC)
- rxData = rxData[1:]
- while rxData and rxData[0] != FpgaPhyMsg.SPI_SM_MAGIC:
- rxData = rxData[1:]
- if not rxData:
- # Magic byte not found.
- continue
- # If the remaining data is not enough, get more bytes.
- if len(rxData) < 3:
- rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (3 - len(rxData))))
- # Get and check the received flags field.
- flgField = rxData[1]
- if FpgaPhyMsg.parity(flgField):
- # Parity mismatch.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_PARERR)
- continue
- if flgField & (1 << FpgaPhyMsg.SPI_FLG_RESET):
- # FPGA reset detected.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_RESET)
- if flgField & (1 << FpgaPhyMsg.SPI_FLG_NEWSTAT):
- # New STATUS message available.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_NEWSTAT)
- if flgField & (1 << FpgaPhyMsg.SPI_FLG_CTRL):
- # Received control message
- if len(rxData) < CTRL_LEN:
- rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (CTRL_LEN - len(rxData))))
- # Write the control message to SHM.
- for i in range(CTRL_LEN):
- self.__shmRxCtrl[(ctrlWrOffs + i) & shmMask] = rxData[i]
- ctrlWrOffs = (ctrlWrOffs + CTRL_LEN) & shmMask
- # Update the receive count in SHM.
- self.__incShmStatus(self.STATUS_CTRL_RXCOUNT)
- # If there is data left, add it to tail data.
- tailData = rxData[CTRL_LEN : ]
- else:
- # Received data message
- if len(rxData) < RX_DATA_LEN:
- rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (RX_DATA_LEN - len(rxData))))
- # If this is a telegram start, clear the temp RX buffers.
- if flgField & (1 << FpgaPhyMsg.SPI_FLG_START):
- expectedRxLength = 0
- collectedRxLength = 0
- rxDataBuf = bytearray()
- # Get the raw PB data.
- rawDataLen = rxData[10]
- if rawDataLen <= 0 or rawDataLen > 8:
- # Invalid length.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_INVALLEN)
- continue
- rawData = rxData[2 : 2 + rawDataLen]
- rxDataBuf += rawData
- # If we don't know the PB telegram length, try to calculate it.
- if expectedRxLength <= 0:
- telegramLen = FpgaPhyMsg.calcLen(rxDataBuf)
- if (telegramLen == FpgaPhyMsg.LEN_ERROR or
- telegramLen == FpgaPhyMsg.LEN_UNKNOWN):
- # Could not determine telegram length.
- expectedRxLength = 0
- collectedRxLength = 0
- rxDataBuf = bytearray()
- self.__incShmStatus(self.STATUS_EVENTCOUNT_PBLENERR)
- continue
- if telegramLen == FpgaPhyMsg.LEN_NEEDMORE:
- # Need more telegram bytes.
- continue
- expectedRxLength = telegramLen
- # If we know the PB telegram length, check if we have enough data.
- if (expectedRxLength > 0 and
- len(rxDataBuf) >= expectedRxLength):
- if len(rxDataBuf) > expectedRxLength:
- # We got too much data.
- self.__incShmStatus(self.STATUS_EVENTCOUNT_INVALLEN)
- # Write the telegram to SHM.
- for i in range(expectedRxLength):
- self.__shmRxData[(dataWrOffs + i) & shmMask] = rxDataBuf[i]
- # Update receive telegram metadata in SHM.
- count = self.__shmStatus[self.STATUS_DATA_RXCOUNT]
- metaBegin = count * self.METASTRUCT_SIZE
- self.__shmRxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask] = dataWrOffs & 0xFF
- self.__shmRxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] = (dataWrOffs >> 8) & 0xFF
- self.__shmRxDataMeta[(metaBegin + self.META_LEN) & shmMask] = expectedRxLength & 0xFF
- self.__incShmStatus(self.STATUS_DATA_RXCOUNT)
- dataWrOffs = (dataWrOffs + expectedRxLength) & shmMask
- expectedRxLength = 0
- collectedRxLength = 0
- rxDataBuf = bytearray()
- # If there is data left, add it to tail data.
- tailData = rxData[RX_DATA_LEN : ]
- # I/O process
- def run(self):
- self.__shmStatus[self.STATUS_RUNNING] = 0
- errorCode = self.ERROR_NONE
- self.__shmStatus[self.STATUS_ERROR] = errorCode
- spi = None
- try:
- spi = spidev.SpiDev()
- spi.open(self.__spiDev, self.__spiChipSelect)
- spi.max_speed_hz = self.__spiSpeedHz
- self.__shmStatus[self.STATUS_RUNNING] = 1
- self.__ioProcMainLoop(spi)
- except PermissionError as e:
- print("FPGA-PHY error: %s" % str(e), file=sys.stderr)
- errorCode = self.ERROR_PERMISSION
- except OSError as e:
- print("FPGA-PHY error: %s" % str(e), file=sys.stderr)
- errorCode = self.ERROR_OSERROR
- finally:
- self.__shmStatus[self.STATUS_ERROR] = errorCode
- try:
- spi.close()
- except OSError as e:
- pass
- self.__shmStatus[self.STATUS_RUNNING] = 0
- return errorCode
- def shutdownProc(self):
- self.__shmStatus[self.STATUS_STOP] = 1
- if self.is_alive():
- self.join()
- def dataSend(self, txTelegramData):
- shmMask = self.__shmMask
- txLength = len(txTelegramData)
- txCount = self.__shmStatus[self.STATUS_DATA_TXCOUNT]
- metaBegin = txCount * self.METASTRUCT_SIZE
- dataWrOffs = self.__txDataWrOffs
- for i in range(txLength):
- self.__shmTxData[(dataWrOffs + i) & shmMask] = txTelegramData[i]
- self.__shmTxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask] = dataWrOffs & 0xFF
- self.__shmTxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] = (dataWrOffs >> 8) & 0xFF
- self.__shmTxDataMeta[(metaBegin + self.META_LEN) & shmMask] = txLength & 0xFF
- self.__shmStatus[self.STATUS_DATA_TXCOUNT] = (txCount + 1) & 0xFF
- self.__txDataWrOffs = (dataWrOffs + txLength) & shmMask
- def dataReceive(self):
- rxTelegrams = []
- shmMask = self.__shmMask
- newCount = self.__shmStatus[self.STATUS_DATA_RXCOUNT]
- rxCount = self.__rxDataCount
- while rxCount != newCount:
- metaBegin = rxCount * self.METASTRUCT_SIZE
- dataRdOffs = self.__shmRxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask]
- dataRdOffs |= self.__shmRxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] << 8
- dataRdLen = self.__shmRxDataMeta[(metaBegin + self.META_LEN) & shmMask]
- rxData = bytearray(dataRdLen)
- for i in range(dataRdLen):
- rxData[i] = self.__shmRxData[(dataRdOffs + i) & shmMask]
- rxTelegrams.append(rxData)
- rxCount = (rxCount + 1) & 0xFF
- self.__rxDataCount = rxCount
- return rxTelegrams
- def dataAvailable(self):
- return self.__shmStatus[self.STATUS_DATA_RXCOUNT] != self.__rxDataCount
- def controlSend(self, ctrlMsg):
- CTRL_LEN = ctrlMsg.CTRL_LEN
- shmMask = self.__shmMask
- txCount = self.__shmStatus[self.STATUS_CTRL_TXCOUNT]
- ctrlData = ctrlMsg.toBytes()
- ctrlWrOffs = self.__txCtrlWrOffs
- for i in range(CTRL_LEN):
- self.__shmTxCtrl[(ctrlWrOffs + i) & shmMask] = ctrlData[i]
- self.__shmStatus[self.STATUS_CTRL_TXCOUNT] = (txCount + 1) & 0xFF
- self.__txCtrlWrOffs = (ctrlWrOffs + CTRL_LEN) & shmMask
- def controlReceive(self):
- rxCtrlMsgs = []
- CTRL_LEN = FpgaPhyMsgCtrl.CTRL_LEN
- shmMask = self.__shmMask
- newCount = self.__shmStatus[self.STATUS_CTRL_RXCOUNT]
- rxCount = self.__rxCtrlCount
- ctrlRdOffs = self.__rxCtrlRdOffs
- while rxCount != newCount:
- rxCtrl = bytearray(CTRL_LEN)
- for i in range(CTRL_LEN):
- rxCtrl[i] = self.__shmRxCtrl[(ctrlRdOffs + i) & shmMask]
- rxCtrlMsgs.append(FpgaPhyMsgCtrl.fromBytes(rxCtrl))
- ctrlRdOffs = (ctrlRdOffs + CTRL_LEN) & shmMask
- rxCount = (rxCount + 1) & 0xFF
- self.__rxCtrlRdOffs = ctrlRdOffs
- self.__rxCtrlCount = rxCount
- return rxCtrlMsgs
- def controlAvailable(self):
- return self.__shmStatus[self.STATUS_CTRL_RXCOUNT] != self.__rxCtrlCount
- def getEventStatus(self):
- events = 0
- if self.__eventCountNewStat != self.__shmStatus[self.STATUS_EVENTCOUNT_NEWSTAT]:
- self.__eventCountNewStat = self.__shmStatus[self.STATUS_EVENTCOUNT_NEWSTAT]
- events |= 1 << self.EVENT_NEWSTAT
- if self.__eventCountReset != self.__shmStatus[self.STATUS_EVENTCOUNT_RESET]:
- self.__eventCountReset = self.__shmStatus[self.STATUS_EVENTCOUNT_RESET]
- events |= 1 << self.EVENT_RESET
- if self.__eventCountParErr != self.__shmStatus[self.STATUS_EVENTCOUNT_PARERR]:
- self.__eventCountParErr = self.__shmStatus[self.STATUS_EVENTCOUNT_PARERR]
- events |= 1 << self.EVENT_PARERR
- if self.__eventCountNoMagic != self.__shmStatus[self.STATUS_EVENTCOUNT_NOMAGIC]:
- self.__eventCountNoMagic = self.__shmStatus[self.STATUS_EVENTCOUNT_NOMAGIC]
- events |= 1 << self.EVENT_NOMAGIC
- if self.__eventCountInvalLen != self.__shmStatus[self.STATUS_EVENTCOUNT_INVALLEN]:
- self.__eventCountInvalLen = self.__shmStatus[self.STATUS_EVENTCOUNT_INVALLEN]
- events |= 1 << self.EVENT_INVALLEN
- if self.__eventCountPBLenErr != self.__shmStatus[self.STATUS_EVENTCOUNT_PBLENERR]:
- self.__eventCountPBLenErr = self.__shmStatus[self.STATUS_EVENTCOUNT_PBLENERR]
- events |= 1 << self.EVENT_PBLENERR
- return events
|