bulkio.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. import collections
  16. import logging
  17. import struct
  18. from ..exceptions import PebbleCommanderError
  19. class ResponseParseError(PebbleCommanderError):
  20. pass
  21. class EraseError(PebbleCommanderError):
  22. pass
  23. class OpenCommand(object):
  24. command_type = 1
  25. command_struct = struct.Struct('<BB')
  26. def __init__(self, domain, extra=None):
  27. self.domain = domain
  28. self.extra = extra
  29. @property
  30. def packet(self):
  31. cmd = self.command_struct.pack(self.command_type, self.domain)
  32. if self.extra:
  33. cmd += self.extra
  34. return cmd
  35. class CloseCommand(object):
  36. command_type = 2
  37. command_struct = struct.Struct('<BB')
  38. def __init__(self, fd):
  39. self.fd = fd
  40. @property
  41. def packet(self):
  42. return self.command_struct.pack(self.command_type, self.fd)
  43. class ReadCommand(object):
  44. command_type = 3
  45. command_struct = struct.Struct('<BBII')
  46. def __init__(self, fd, address, length):
  47. self.fd = fd
  48. self.address = address
  49. self.length = length
  50. @property
  51. def packet(self):
  52. return self.command_struct.pack(self.command_type, self.fd,
  53. self.address, self.length)
  54. class WriteCommand(object):
  55. command_type = 4
  56. command_struct = struct.Struct('<BBI')
  57. header_size = command_struct.size
  58. def __init__(self, fd, address, data):
  59. self.fd = fd
  60. self.address = address
  61. self.data = data
  62. @property
  63. def packet(self):
  64. return self.command_struct.pack(self.command_type, self.fd,
  65. self.address) + self.data
  66. class CRCCommand(object):
  67. command_type = 5
  68. command_struct = struct.Struct('<BBII')
  69. def __init__(self, fd, address, length):
  70. self.fd = fd
  71. self.address = address
  72. self.length = length
  73. @property
  74. def packet(self):
  75. return self.command_struct.pack(self.command_type, self.fd,
  76. self.address, self.length)
  77. class StatCommand(object):
  78. command_type = 6
  79. command_struct = struct.Struct('<BB')
  80. def __init__(self, fd):
  81. self.fd = fd
  82. @property
  83. def packet(self):
  84. return self.command_struct.pack(self.command_type, self.fd)
  85. class EraseCommand(object):
  86. command_type = 7
  87. command_struct = struct.Struct('<BB')
  88. def __init__(self, domain, extra=None):
  89. self.domain = domain
  90. self.extra = extra
  91. @property
  92. def packet(self):
  93. cmd = self.command_struct.pack(self.command_type, self.domain)
  94. if self.extra:
  95. cmd += self.extra
  96. return cmd
  97. class OpenResponse(object):
  98. response_type = 128
  99. response_format = '<xB'
  100. response_struct = struct.Struct(response_format)
  101. header_size = response_struct.size
  102. Response = collections.namedtuple('OpenResponse', 'fd')
  103. @classmethod
  104. def parse(cls, response):
  105. response_type = ord(response[0])
  106. if response_type != cls.response_type:
  107. raise ResponseParseError('Unexpected response type: %r' % response_type)
  108. return cls.Response._make(cls.response_struct.unpack(response))
  109. class CloseResponse(object):
  110. response_type = 129
  111. response_format = '<xB'
  112. response_struct = struct.Struct(response_format)
  113. header_size = response_struct.size
  114. Response = collections.namedtuple('CloseResponse', 'fd')
  115. @classmethod
  116. def parse(cls, response):
  117. response_type = ord(response[0])
  118. if response_type != cls.response_type:
  119. raise ResponseParseError('Unexpected response type: %r' % response_type)
  120. return cls.Response._make(cls.response_struct.unpack(response))
  121. class ReadResponse(object):
  122. response_type = 130
  123. response_format = '<xBI'
  124. response_struct = struct.Struct(response_format)
  125. header_size = response_struct.size
  126. Response = collections.namedtuple('ReadResponse', 'fd address data')
  127. @classmethod
  128. def parse(cls, response):
  129. if ord(response[0]) != cls.response_type:
  130. raise ResponseParseError('Unexpected response: %r' % response)
  131. header = response[:cls.header_size]
  132. body = response[cls.header_size:]
  133. fd, address, = cls.response_struct.unpack(header)
  134. return cls.Response(fd, address, body)
  135. class WriteResponse(object):
  136. response_type = 131
  137. response_format = '<xBII'
  138. response_struct = struct.Struct(response_format)
  139. header_size = response_struct.size
  140. Response = collections.namedtuple('WriteResponse', 'fd address length')
  141. @classmethod
  142. def parse(cls, response):
  143. response_type = ord(response[0])
  144. if response_type != cls.response_type:
  145. raise ResponseParseError('Unexpected response type: %r' % response_type)
  146. return cls.Response._make(cls.response_struct.unpack(response))
  147. class CRCResponse(object):
  148. response_type = 132
  149. response_format = '<xBIII'
  150. response_struct = struct.Struct(response_format)
  151. header_size = response_struct.size
  152. Response = collections.namedtuple('CRCResponse', 'fd address length crc')
  153. @classmethod
  154. def parse(cls, response):
  155. response_type = ord(response[0])
  156. if response_type != cls.response_type:
  157. raise ResponseParseError('Unexpected response type: %r' % response_type)
  158. return cls.Response._make(cls.response_struct.unpack(response))
  159. class StatResponse(object):
  160. response_type = 133
  161. def __init__(self, name, format, fields):
  162. self.name = name
  163. self.struct = struct.Struct('<xBB' + format)
  164. self.tuple = collections.namedtuple(name, 'fd flags ' + fields)
  165. def parse(self, response):
  166. response_type = ord(response[0])
  167. if response_type != self.response_type:
  168. raise ResponseParseError('Unexpected response type: %r' % response_type)
  169. return self.tuple._make(self.struct.unpack(response))
  170. def __repr__(self):
  171. return 'StatResponse({self.name!r}, {self.struct!r}, {self.tuple!r})'.format(self=self)
  172. class EraseResponse(object):
  173. response_type = 134
  174. response_format = '<xBb'
  175. response_struct = struct.Struct(response_format)
  176. header_size = response_struct.size
  177. Response = collections.namedtuple('EraseResponse', 'domain status')
  178. @classmethod
  179. def parse(cls, response):
  180. response_type = ord(response[0])
  181. if response_type != cls.response_type:
  182. raise ResponseParseError('Unexpected response type: %r' % response_type)
  183. return cls.Response._make(cls.response_struct.unpack(response))
  184. def enum(**enums):
  185. return type('Enum', (), enums)
  186. ReadDomains = enum(
  187. MEMORY=1,
  188. EXTERNAL_FLASH=2,
  189. FRAMEBUFFER=3,
  190. COREDUMP=4,
  191. PFS=5
  192. )
  193. class PULSEIO_Base(object):
  194. ERASE_FORMAT = None
  195. STAT_FORMAT = None
  196. DOMAIN = None
  197. def __init__(self, socket, *args, **kwargs):
  198. self.socket = socket
  199. self.pos = 0
  200. options = self._process_args(*args, **kwargs)
  201. resp = self._send_and_receive(OpenCommand, OpenResponse, self.DOMAIN, options)
  202. self.fd = resp.fd
  203. def __enter__(self):
  204. return self
  205. def __exit__(self, type, value, traceback):
  206. self.close()
  207. @staticmethod
  208. def _process_args(*args, **kwargs):
  209. return ""
  210. def _send_and_receive(self, cmd_type, resp_type, *args):
  211. cmd = cmd_type(*args)
  212. self.socket.send(cmd.packet)
  213. ret = self.socket.receive(block=True)
  214. return resp_type.parse(ret)
  215. def close(self):
  216. if self.fd is not None:
  217. resp = self._send_and_receive(CloseCommand, CloseResponse, self.fd)
  218. assert resp.fd == self.fd
  219. self.fd = None
  220. def seek_absolute(self, pos):
  221. if pos < 0:
  222. raise ValueError('Cannot seek to before start of file')
  223. self.pos = pos
  224. def seek_relative(self, num_bytes):
  225. if (self.pos + num_bytes) < 0:
  226. raise ValueError('Cannot seek to before start of file')
  227. self.pos += num_bytes
  228. @classmethod
  229. def erase(cls, socket, *args):
  230. if cls.ERASE_FORMAT == "raw":
  231. options = "".join(args)
  232. elif cls.ERASE_FORMAT:
  233. options = struct.pack("<" + cls.ERASE_FORMAT, *args)
  234. else:
  235. raise NotImplementedError("Erase is not supported for domain %d" % cls.DOMAIN)
  236. cmd = EraseCommand(cls.DOMAIN, options)
  237. socket.send(cmd.packet)
  238. status = 1
  239. while status > 0:
  240. ret = socket.receive(block=True)
  241. resp = EraseResponse.parse(ret)
  242. logging.debug("ERASE: domain %d status %d", resp.domain, resp.status)
  243. status = resp.status
  244. if status < 0:
  245. raise EraseError(status)
  246. def write(self, data):
  247. if self.fd is None:
  248. raise ValueError('Handle is not open')
  249. mss = self.socket.mtu - WriteCommand.header_size
  250. for offset in xrange(0, len(data), mss):
  251. segment = data[offset:offset+mss]
  252. resp = self._send_and_receive(WriteCommand, WriteResponse, self.fd, self.pos, segment)
  253. assert resp.fd == self.fd
  254. assert resp.address == self.pos
  255. self.pos += len(segment)
  256. def read(self, length):
  257. if self.fd is None:
  258. raise ValueError('Handle is not open')
  259. cmd = ReadCommand(self.fd, self.pos, length)
  260. self.socket.send(cmd.packet)
  261. data = bytearray()
  262. bytes_left = length
  263. while bytes_left > 0:
  264. packet = self.socket.receive(block=True)
  265. fd, chunk_offset, chunk = ReadResponse.parse(packet)
  266. assert fd == self.fd
  267. data.extend(chunk)
  268. bytes_left -= len(chunk)
  269. return data
  270. def crc(self, length):
  271. if self.fd is None:
  272. raise ValueError('Handle is not open')
  273. resp = self._send_and_receive(CRCCommand, CRCResponse, self.fd, self.pos, length)
  274. return resp.crc
  275. def stat(self):
  276. if self.fd is None:
  277. raise ValueError('Handle is not open')
  278. if not self.STAT_FORMAT:
  279. raise NotImplementedError("Stat is not supported for domain %d" % self.DOMAIN)
  280. return self._send_and_receive(StatCommand, self.STAT_FORMAT, self.fd)
  281. class PULSEIO_Memory(PULSEIO_Base):
  282. DOMAIN = ReadDomains.MEMORY
  283. # uint32 for address, uint32 for length
  284. ERASE_FORMAT = "II"
  285. class PULSEIO_ExternalFlash(PULSEIO_Base):
  286. DOMAIN = ReadDomains.EXTERNAL_FLASH
  287. # uint32 for address, uint32 for length
  288. ERASE_FORMAT = "II"
  289. class PULSEIO_Framebuffer(PULSEIO_Base):
  290. DOMAIN = ReadDomains.FRAMEBUFFER
  291. STAT_FORMAT = StatResponse('FramebufferAttributes', 'BBBI', 'width height bpp length')
  292. class PULSEIO_Coredump(PULSEIO_Base):
  293. DOMAIN = ReadDomains.COREDUMP
  294. STAT_FORMAT = StatResponse('CoredumpAttributes', 'BI', 'unread length')
  295. ERASE_FORMAT = "I"
  296. @staticmethod
  297. def _process_args(slot):
  298. return struct.pack("<I", slot)
  299. class PULSEIO_PFS(PULSEIO_Base):
  300. DOMAIN = ReadDomains.PFS
  301. STAT_FORMAT = StatResponse('PFSFileAttributes', 'I', 'length')
  302. ERASE_FORMAT = "raw"
  303. OP_FLAG_READ = 1 << 0
  304. OP_FLAG_WRITE = 1 << 1
  305. OP_FLAG_OVERWRITE = 1 << 2
  306. OP_FLAG_SKIP_HDR_CRC_CHECK = 1 << 3
  307. OP_FLAG_USE_PAGE_CACHE = 1 << 4
  308. @staticmethod
  309. def _process_args(filename, mode='r', flags=0xFE, initial_size=0):
  310. mode_num = PULSEIO_PFS.OP_FLAG_READ
  311. if 'w' in mode:
  312. mode_num |= PULSEIO_PFS.OP_FLAG_WRITE
  313. return struct.pack("<BBI", mode_num, flags, initial_size) + filename
  314. class BulkIO(object):
  315. PROTOCOL_NUMBER = 0x3e21
  316. DOMAIN_MAP = {
  317. 'pfs': PULSEIO_PFS,
  318. 'framebuffer': PULSEIO_Framebuffer
  319. }
  320. def __init__(self, link):
  321. self.socket = link.open_socket('reliable', self.PROTOCOL_NUMBER)
  322. def open(self, domain, *args, **kwargs):
  323. return self.DOMAIN_MAP[domain](self.socket, *args, **kwargs)
  324. def erase(self, domain, *args, **kwargs):
  325. return self.DOMAIN_MAP[domain].erase(self.socket, *args, **kwargs)
  326. def close(self):
  327. self.socket.close()