io.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Driver for FPGA based PROFIBUS PHY.
  4. #
  5. # Copyright (c) 2019 Michael Buesch <m@bues.ch>
  6. #
  7. # Licensed under the terms of the GNU General Public License version 2,
  8. # or (at your option) any later version.
  9. #
  10. from __future__ import division, absolute_import, print_function, unicode_literals
  11. from pyprofibus.compat import *
  12. from pyprofibus.phy_fpga_driver.messages import *
  13. import multiprocessing
  14. import mmap
  15. import spidev
  16. import time
  17. import sys
  18. __all__ = [
  19. "FpgaPhyProc",
  20. ]
  21. class FpgaPhyProc(multiprocessing.Process):
  22. """I/O process.
  23. """
  24. # Event IDs
  25. EVENT_NEWSTAT = 0
  26. EVENT_RESET = 1
  27. EVENT_PARERR = 2
  28. EVENT_NOMAGIC = 3
  29. EVENT_INVALLEN = 4
  30. EVENT_PBLENERR = 5
  31. # Offsets into __shmStatus
  32. STATUS_RUNNING = 0x0
  33. STATUS_STOP = 0x40
  34. STATUS_ERROR = 0x80
  35. STATUS_CTRL_TXCOUNT = 0xC0
  36. STATUS_CTRL_RXCOUNT = 0x100
  37. STATUS_DATA_TXCOUNT = 0x140
  38. STATUS_DATA_RXCOUNT = 0x180
  39. STATUS_EVENTCOUNT_BASE = 0x1C0
  40. STATUS_EVENTCOUNT_NEWSTAT = STATUS_EVENTCOUNT_BASE + EVENT_NEWSTAT
  41. STATUS_EVENTCOUNT_RESET = STATUS_EVENTCOUNT_BASE + EVENT_RESET
  42. STATUS_EVENTCOUNT_PARERR = STATUS_EVENTCOUNT_BASE + EVENT_PARERR
  43. STATUS_EVENTCOUNT_NOMAGIC = STATUS_EVENTCOUNT_BASE + EVENT_NOMAGIC
  44. STATUS_EVENTCOUNT_INVALLEN = STATUS_EVENTCOUNT_BASE + EVENT_INVALLEN
  45. STATUS_EVENTCOUNT_PBLENERR = STATUS_EVENTCOUNT_BASE + EVENT_PBLENERR
  46. # I/O process return codes.
  47. ERROR_NONE = 0
  48. ERROR_OSERROR = 1
  49. ERROR_PERMISSION = 2
  50. # Meta data offsets.
  51. META_OFFS_LO = 0
  52. META_OFFS_HI = 1
  53. META_LEN = 2
  54. METASTRUCT_SIZE = 3
  55. def __init__(self, spiDev, spiChipSelect, spiSpeedHz):
  56. super(FpgaPhyProc, self).__init__()
  57. self.__rxDataCount = 0
  58. self.__rxCtrlCount = 0
  59. self.__rxCtrlRdOffs = 0
  60. self.__txCtrlWrOffs = 0
  61. self.__txDataWrOffs = 0
  62. self.__eventCountNewStat = 0
  63. self.__eventCountReset = 0
  64. self.__eventCountParErr = 0
  65. self.__eventCountNoMagic = 0
  66. self.__eventCountInvalLen = 0
  67. self.__eventCountPBLenErr = 0
  68. self.__spiDev = spiDev
  69. self.__spiChipSelect = spiChipSelect
  70. self.__spiSpeedHz = spiSpeedHz
  71. def makeSHM(length):
  72. shm = mmap.mmap(-1, length)
  73. shm[0:length] = b"\x00" * length
  74. return shm
  75. self.__shmLengths = 4096
  76. self.__shmMask = self.__shmLengths - 1
  77. self.__shmTxData = makeSHM(self.__shmLengths)
  78. self.__shmTxDataMeta = makeSHM(self.__shmLengths)
  79. self.__shmRxData = makeSHM(self.__shmLengths)
  80. self.__shmRxDataMeta = makeSHM(self.__shmLengths)
  81. self.__shmTxCtrl = makeSHM(self.__shmLengths)
  82. self.__shmRxCtrl = makeSHM(self.__shmLengths)
  83. self.__shmStatus = makeSHM(self.__shmLengths)
  84. def start(self):
  85. super(FpgaPhyProc, self).start()
  86. success = False
  87. for i in range(500):
  88. if self.__shmStatus[self.STATUS_RUNNING]:
  89. success = True
  90. break
  91. if self.__shmStatus[self.STATUS_ERROR] != self.ERROR_NONE:
  92. break
  93. if not self.is_alive():
  94. break
  95. time.sleep(0.01)
  96. if not success:
  97. self.shutdownProc()
  98. return success
  99. def __incShmStatus(self, index):
  100. self.__shmStatus[index] = (self.__shmStatus[index] + 1) & 0xFF
  101. def __ioProcMainLoop(self, spi):
  102. ctrlWrOffs = 0
  103. ctrlRdOffs = 0
  104. dataWrOffs = 0
  105. txDataCount = 0
  106. txCtrlCount = 0
  107. expectedRxLength = 0
  108. collectedRxLength = 0
  109. rxDataBuf = bytearray()
  110. shmMask = self.__shmMask
  111. CTRL_LEN = FpgaPhyMsgCtrl.CTRL_LEN
  112. RX_DATA_LEN = 11
  113. MIN_XFER_LEN = RX_DATA_LEN
  114. tailData = b""
  115. while not self.__shmStatus[self.STATUS_STOP]:
  116. txData = b""
  117. # Get the TX control data, if any.
  118. if txCtrlCount != self.__shmStatus[self.STATUS_CTRL_TXCOUNT]:
  119. # Get the TX control message.
  120. txData = bytearray(CTRL_LEN)
  121. for i in range(CTRL_LEN):
  122. txData[i] = self.__shmTxCtrl[(ctrlRdOffs + i) & shmMask]
  123. ctrlRdOffs = (ctrlRdOffs + CTRL_LEN) & shmMask
  124. txCtrlCount = (txCtrlCount + 1) & 0xFF
  125. # Get the PB TX data, if any.
  126. elif txDataCount != self.__shmStatus[self.STATUS_DATA_TXCOUNT]:
  127. metaBegin = txDataCount * self.METASTRUCT_SIZE
  128. dataRdOffs = self.__shmTxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask]
  129. dataRdOffs |= self.__shmTxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] << 8
  130. dataRdLen = self.__shmTxDataMeta[(metaBegin + self.META_LEN) & shmMask]
  131. # Construct the TX data message.
  132. txData = bytearray(dataRdLen + 2)
  133. txData[0] = FpgaPhyMsg.SPI_MS_MAGIC
  134. txData[1] = 1 << FpgaPhyMsg.SPI_FLG_START
  135. txData[1] |= FpgaPhyMsg.parity(txData[1]) << FpgaPhyMsg.SPI_FLG_PARITY
  136. for i in range(dataRdLen):
  137. txData[i + 2] = self.__shmTxData[(dataRdOffs + i) & shmMask]
  138. txDataCount = (txDataCount + 1) & 0xFF
  139. # Pad the TX data, if required.
  140. if len(txData) < MIN_XFER_LEN:
  141. txData += FpgaPhyMsg.PADDING_BYTE * (MIN_XFER_LEN - len(txData))
  142. # Run the SPI transfer (transmit and receive).
  143. rxData = bytes(spi.xfer2(txData))
  144. # If we have tail data, prepend it to the received data.
  145. if tailData:
  146. rxData = tailData + rxData
  147. tailData = b""
  148. # Strip all leading padding bytes.
  149. rxData = rxData.lstrip(FpgaPhyMsg.PADDING_BYTE)
  150. if not rxData:
  151. continue
  152. # The first byte must be the magic byte.
  153. if rxData[0] != FpgaPhyMsg.SPI_SM_MAGIC:
  154. # Magic mismatch. Try to find the magic byte.
  155. self.__incShmStatus(self.STATUS_EVENTCOUNT_NOMAGIC)
  156. rxData = rxData[1:]
  157. while rxData and rxData[0] != FpgaPhyMsg.SPI_SM_MAGIC:
  158. rxData = rxData[1:]
  159. if not rxData:
  160. # Magic byte not found.
  161. continue
  162. # If the remaining data is not enough, get more bytes.
  163. if len(rxData) < 3:
  164. rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (3 - len(rxData))))
  165. # Get and check the received flags field.
  166. flgField = rxData[1]
  167. if FpgaPhyMsg.parity(flgField):
  168. # Parity mismatch.
  169. self.__incShmStatus(self.STATUS_EVENTCOUNT_PARERR)
  170. continue
  171. if flgField & (1 << FpgaPhyMsg.SPI_FLG_RESET):
  172. # FPGA reset detected.
  173. self.__incShmStatus(self.STATUS_EVENTCOUNT_RESET)
  174. if flgField & (1 << FpgaPhyMsg.SPI_FLG_NEWSTAT):
  175. # New STATUS message available.
  176. self.__incShmStatus(self.STATUS_EVENTCOUNT_NEWSTAT)
  177. if flgField & (1 << FpgaPhyMsg.SPI_FLG_CTRL):
  178. # Received control message
  179. if len(rxData) < CTRL_LEN:
  180. rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (CTRL_LEN - len(rxData))))
  181. # Write the control message to SHM.
  182. for i in range(CTRL_LEN):
  183. self.__shmRxCtrl[(ctrlWrOffs + i) & shmMask] = rxData[i]
  184. ctrlWrOffs = (ctrlWrOffs + CTRL_LEN) & shmMask
  185. # Update the receive count in SHM.
  186. self.__incShmStatus(self.STATUS_CTRL_RXCOUNT)
  187. # If there is data left, add it to tail data.
  188. tailData = rxData[CTRL_LEN : ]
  189. else:
  190. # Received data message
  191. if len(rxData) < RX_DATA_LEN:
  192. rxData += bytes(spi.xfer2(FpgaPhyMsg.PADDING_BYTE * (RX_DATA_LEN - len(rxData))))
  193. # If this is a telegram start, clear the temp RX buffers.
  194. if flgField & (1 << FpgaPhyMsg.SPI_FLG_START):
  195. expectedRxLength = 0
  196. collectedRxLength = 0
  197. rxDataBuf = bytearray()
  198. # Get the raw PB data.
  199. rawDataLen = rxData[10]
  200. if rawDataLen <= 0 or rawDataLen > 8:
  201. # Invalid length.
  202. self.__incShmStatus(self.STATUS_EVENTCOUNT_INVALLEN)
  203. continue
  204. rawData = rxData[2 : 2 + rawDataLen]
  205. rxDataBuf += rawData
  206. # If we don't know the PB telegram length, try to calculate it.
  207. if expectedRxLength <= 0:
  208. telegramLen = FpgaPhyMsg.calcLen(rxDataBuf)
  209. if (telegramLen == FpgaPhyMsg.LEN_ERROR or
  210. telegramLen == FpgaPhyMsg.LEN_UNKNOWN):
  211. # Could not determine telegram length.
  212. expectedRxLength = 0
  213. collectedRxLength = 0
  214. rxDataBuf = bytearray()
  215. self.__incShmStatus(self.STATUS_EVENTCOUNT_PBLENERR)
  216. continue
  217. if telegramLen == FpgaPhyMsg.LEN_NEEDMORE:
  218. # Need more telegram bytes.
  219. continue
  220. expectedRxLength = telegramLen
  221. # If we know the PB telegram length, check if we have enough data.
  222. if (expectedRxLength > 0 and
  223. len(rxDataBuf) >= expectedRxLength):
  224. if len(rxDataBuf) > expectedRxLength:
  225. # We got too much data.
  226. self.__incShmStatus(self.STATUS_EVENTCOUNT_INVALLEN)
  227. # Write the telegram to SHM.
  228. for i in range(expectedRxLength):
  229. self.__shmRxData[(dataWrOffs + i) & shmMask] = rxDataBuf[i]
  230. # Update receive telegram metadata in SHM.
  231. count = self.__shmStatus[self.STATUS_DATA_RXCOUNT]
  232. metaBegin = count * self.METASTRUCT_SIZE
  233. self.__shmRxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask] = dataWrOffs & 0xFF
  234. self.__shmRxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] = (dataWrOffs >> 8) & 0xFF
  235. self.__shmRxDataMeta[(metaBegin + self.META_LEN) & shmMask] = expectedRxLength & 0xFF
  236. self.__incShmStatus(self.STATUS_DATA_RXCOUNT)
  237. dataWrOffs = (dataWrOffs + expectedRxLength) & shmMask
  238. expectedRxLength = 0
  239. collectedRxLength = 0
  240. rxDataBuf = bytearray()
  241. # If there is data left, add it to tail data.
  242. tailData = rxData[RX_DATA_LEN : ]
  243. # I/O process
  244. def run(self):
  245. self.__shmStatus[self.STATUS_RUNNING] = 0
  246. errorCode = self.ERROR_NONE
  247. self.__shmStatus[self.STATUS_ERROR] = errorCode
  248. spi = None
  249. try:
  250. spi = spidev.SpiDev()
  251. spi.open(self.__spiDev, self.__spiChipSelect)
  252. spi.max_speed_hz = self.__spiSpeedHz
  253. self.__shmStatus[self.STATUS_RUNNING] = 1
  254. self.__ioProcMainLoop(spi)
  255. except PermissionError as e:
  256. print("FPGA-PHY error: %s" % str(e), file=sys.stderr)
  257. errorCode = self.ERROR_PERMISSION
  258. except OSError as e:
  259. print("FPGA-PHY error: %s" % str(e), file=sys.stderr)
  260. errorCode = self.ERROR_OSERROR
  261. finally:
  262. self.__shmStatus[self.STATUS_ERROR] = errorCode
  263. try:
  264. spi.close()
  265. except OSError as e:
  266. pass
  267. self.__shmStatus[self.STATUS_RUNNING] = 0
  268. return errorCode
  269. def shutdownProc(self):
  270. self.__shmStatus[self.STATUS_STOP] = 1
  271. if self.is_alive():
  272. self.join()
  273. def dataSend(self, txTelegramData):
  274. shmMask = self.__shmMask
  275. txLength = len(txTelegramData)
  276. txCount = self.__shmStatus[self.STATUS_DATA_TXCOUNT]
  277. metaBegin = txCount * self.METASTRUCT_SIZE
  278. dataWrOffs = self.__txDataWrOffs
  279. for i in range(txLength):
  280. self.__shmTxData[(dataWrOffs + i) & shmMask] = txTelegramData[i]
  281. self.__shmTxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask] = dataWrOffs & 0xFF
  282. self.__shmTxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] = (dataWrOffs >> 8) & 0xFF
  283. self.__shmTxDataMeta[(metaBegin + self.META_LEN) & shmMask] = txLength & 0xFF
  284. self.__shmStatus[self.STATUS_DATA_TXCOUNT] = (txCount + 1) & 0xFF
  285. self.__txDataWrOffs = (dataWrOffs + txLength) & shmMask
  286. def dataReceive(self):
  287. rxTelegrams = []
  288. shmMask = self.__shmMask
  289. newCount = self.__shmStatus[self.STATUS_DATA_RXCOUNT]
  290. rxCount = self.__rxDataCount
  291. while rxCount != newCount:
  292. metaBegin = rxCount * self.METASTRUCT_SIZE
  293. dataRdOffs = self.__shmRxDataMeta[(metaBegin + self.META_OFFS_LO) & shmMask]
  294. dataRdOffs |= self.__shmRxDataMeta[(metaBegin + self.META_OFFS_HI) & shmMask] << 8
  295. dataRdLen = self.__shmRxDataMeta[(metaBegin + self.META_LEN) & shmMask]
  296. rxData = bytearray(dataRdLen)
  297. for i in range(dataRdLen):
  298. rxData[i] = self.__shmRxData[(dataRdOffs + i) & shmMask]
  299. rxTelegrams.append(rxData)
  300. rxCount = (rxCount + 1) & 0xFF
  301. self.__rxDataCount = rxCount
  302. return rxTelegrams
  303. def dataAvailable(self):
  304. return self.__shmStatus[self.STATUS_DATA_RXCOUNT] != self.__rxDataCount
  305. def controlSend(self, ctrlMsg):
  306. CTRL_LEN = ctrlMsg.CTRL_LEN
  307. shmMask = self.__shmMask
  308. txCount = self.__shmStatus[self.STATUS_CTRL_TXCOUNT]
  309. ctrlData = ctrlMsg.toBytes()
  310. ctrlWrOffs = self.__txCtrlWrOffs
  311. for i in range(CTRL_LEN):
  312. self.__shmTxCtrl[(ctrlWrOffs + i) & shmMask] = ctrlData[i]
  313. self.__shmStatus[self.STATUS_CTRL_TXCOUNT] = (txCount + 1) & 0xFF
  314. self.__txCtrlWrOffs = (ctrlWrOffs + CTRL_LEN) & shmMask
  315. def controlReceive(self):
  316. rxCtrlMsgs = []
  317. CTRL_LEN = FpgaPhyMsgCtrl.CTRL_LEN
  318. shmMask = self.__shmMask
  319. newCount = self.__shmStatus[self.STATUS_CTRL_RXCOUNT]
  320. rxCount = self.__rxCtrlCount
  321. ctrlRdOffs = self.__rxCtrlRdOffs
  322. while rxCount != newCount:
  323. rxCtrl = bytearray(CTRL_LEN)
  324. for i in range(CTRL_LEN):
  325. rxCtrl[i] = self.__shmRxCtrl[(ctrlRdOffs + i) & shmMask]
  326. rxCtrlMsgs.append(FpgaPhyMsgCtrl.fromBytes(rxCtrl))
  327. ctrlRdOffs = (ctrlRdOffs + CTRL_LEN) & shmMask
  328. rxCount = (rxCount + 1) & 0xFF
  329. self.__rxCtrlRdOffs = ctrlRdOffs
  330. self.__rxCtrlCount = rxCount
  331. return rxCtrlMsgs
  332. def controlAvailable(self):
  333. return self.__shmStatus[self.STATUS_CTRL_RXCOUNT] != self.__rxCtrlCount
  334. def getEventStatus(self):
  335. events = 0
  336. if self.__eventCountNewStat != self.__shmStatus[self.STATUS_EVENTCOUNT_NEWSTAT]:
  337. self.__eventCountNewStat = self.__shmStatus[self.STATUS_EVENTCOUNT_NEWSTAT]
  338. events |= 1 << self.EVENT_NEWSTAT
  339. if self.__eventCountReset != self.__shmStatus[self.STATUS_EVENTCOUNT_RESET]:
  340. self.__eventCountReset = self.__shmStatus[self.STATUS_EVENTCOUNT_RESET]
  341. events |= 1 << self.EVENT_RESET
  342. if self.__eventCountParErr != self.__shmStatus[self.STATUS_EVENTCOUNT_PARERR]:
  343. self.__eventCountParErr = self.__shmStatus[self.STATUS_EVENTCOUNT_PARERR]
  344. events |= 1 << self.EVENT_PARERR
  345. if self.__eventCountNoMagic != self.__shmStatus[self.STATUS_EVENTCOUNT_NOMAGIC]:
  346. self.__eventCountNoMagic = self.__shmStatus[self.STATUS_EVENTCOUNT_NOMAGIC]
  347. events |= 1 << self.EVENT_NOMAGIC
  348. if self.__eventCountInvalLen != self.__shmStatus[self.STATUS_EVENTCOUNT_INVALLEN]:
  349. self.__eventCountInvalLen = self.__shmStatus[self.STATUS_EVENTCOUNT_INVALLEN]
  350. events |= 1 << self.EVENT_INVALLEN
  351. if self.__eventCountPBLenErr != self.__shmStatus[self.STATUS_EVENTCOUNT_PBLENERR]:
  352. self.__eventCountPBLenErr = self.__shmStatus[self.STATUS_EVENTCOUNT_PBLENERR]
  353. events |= 1 << self.EVENT_PBLENERR
  354. return events