pw_emu: qemu: Improve the QMP handshake handling

Reuse the request method of QmpClient to do the initial QMP
handshake.

This also fixes a bug that causes false initialization errors when
notifications are generated during the handshake.

Bug: 315516286
Change-Id: Ia3ce77ca17138a5a728fa99301c125e4c77f4a82
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/184858
Reviewed-by: Jonathon Reinhart <jrreinhart@google.com>
Commit-Queue: Octavian Purdila <tavip@google.com>
diff --git a/pw_emu/py/pw_emu/qemu.py b/pw_emu/py/pw_emu/qemu.py
index 50112cd..6cf6ff0 100644
--- a/pw_emu/py/pw_emu/qemu.py
+++ b/pw_emu/py/pw_emu/qemu.py
@@ -50,12 +50,17 @@
     def __init__(self, stream: io.RawIOBase):
         self._stream = stream
 
-        json.loads(self._stream.readline())
-        cmd = json.dumps({'execute': 'qmp_capabilities'})
-        self._stream.write(cmd.encode('utf-8'))
-        resp = json.loads(self._stream.readline().decode('ascii'))
-        if not 'return' in resp:
-            raise QmpError(f'qmp init failed: {resp.get("error")}')
+        # Perform the QMP "capabilities negotiation" handshake.
+        # https://wiki.qemu.org/Documentation/QMP#Capabilities_Negotiation
+        #
+        # When the QMP connection is established, QEMU first sends a greeting
+        # message with its version and capabilities. Then the client sends
+        # 'qmp_capabilities' to exit capabilities negotiation mode. The result
+        # is an empty 'return'.
+        #
+        # self.request() will consume both the initial greeting and the
+        # subsequent 'return' response.
+        self.request('qmp_capabilities')
 
     def request(self, cmd: str, args: Optional[Dict[str, Any]] = None) -> Any:
         """Issue a command using the qmp interface.