1 /* 2 * Inter-VM Shared Memory Flat Device 3 * 4 * SPDX-FileCopyrightText: 2023 Linaro Ltd. 5 * SPDX-FileContributor: Gustavo Romero <gustavo.romero@linaro.org> 6 * SPDX-License-Identifier: GPL-2.0-or-later 7 * 8 */ 9 10 #include "qemu/osdep.h" 11 #include "qemu/units.h" 12 #include "qemu/error-report.h" 13 #include "qemu/module.h" 14 #include "qapi/error.h" 15 #include "hw/irq.h" 16 #include "hw/qdev-properties-system.h" 17 #include "hw/sysbus.h" 18 #include "chardev/char-fe.h" 19 #include "exec/address-spaces.h" 20 #include "trace.h" 21 22 #include "hw/misc/ivshmem-flat.h" 23 24 static int64_t ivshmem_flat_recv_msg(IvshmemFTState *s, int *pfd) 25 { 26 int64_t msg; 27 int n, ret; 28 29 n = 0; 30 do { 31 ret = qemu_chr_fe_read_all(&s->server_chr, (uint8_t *)&msg + n, 32 sizeof(msg) - n); 33 if (ret < 0) { 34 if (ret == -EINTR) { 35 continue; 36 } 37 exit(1); 38 } 39 n += ret; 40 } while (n < sizeof(msg)); 41 42 if (pfd) { 43 *pfd = qemu_chr_fe_get_msgfd(&s->server_chr); 44 } 45 return le64_to_cpu(msg); 46 } 47 48 static void ivshmem_flat_irq_handler(void *opaque) 49 { 50 VectorInfo *vi = opaque; 51 EventNotifier *e = &vi->event_notifier; 52 uint16_t vector_id; 53 const VectorInfo (*v)[64]; 54 55 assert(e->initialized); 56 57 vector_id = vi->id; 58 59 /* 60 * The vector info struct is passed to the handler via the 'opaque' pointer. 61 * This struct pointer allows the retrieval of the vector ID and its 62 * associated event notifier. However, for triggering an interrupt using 63 * qemu_set_irq, it's necessary to also have a pointer to the device state, 64 * i.e., a pointer to the IvshmemFTState struct. Since the vector info 65 * struct is contained within the IvshmemFTState struct, its pointer can be 66 * used to obtain the pointer to IvshmemFTState through simple pointer math. 67 */ 68 v = (void *)(vi - vector_id); /* v = &IvshmemPeer->vector[0] */ 69 IvshmemPeer *own_peer = container_of(v, IvshmemPeer, vector); 70 IvshmemFTState *s = container_of(own_peer, IvshmemFTState, own); 71 72 /* Clear event */ 73 if (!event_notifier_test_and_clear(e)) { 74 return; 75 } 76 77 trace_ivshmem_flat_irq_handler(vector_id); 78 79 /* 80 * Toggle device's output line, which is connected to interrupt controller, 81 * generating an interrupt request to the CPU. 82 */ 83 qemu_irq_pulse(s->irq); 84 } 85 86 static IvshmemPeer *ivshmem_flat_find_peer(IvshmemFTState *s, uint16_t peer_id) 87 { 88 IvshmemPeer *peer; 89 90 /* Own ID */ 91 if (s->own.id == peer_id) { 92 return &s->own; 93 } 94 95 /* Peer ID */ 96 QTAILQ_FOREACH(peer, &s->peer, next) { 97 if (peer->id == peer_id) { 98 return peer; 99 } 100 } 101 102 return NULL; 103 } 104 105 static IvshmemPeer *ivshmem_flat_add_peer(IvshmemFTState *s, uint16_t peer_id) 106 { 107 IvshmemPeer *new_peer; 108 109 new_peer = g_malloc0(sizeof(*new_peer)); 110 new_peer->id = peer_id; 111 new_peer->vector_counter = 0; 112 113 QTAILQ_INSERT_TAIL(&s->peer, new_peer, next); 114 115 trace_ivshmem_flat_new_peer(peer_id); 116 117 return new_peer; 118 } 119 120 static void ivshmem_flat_remove_peer(IvshmemFTState *s, uint16_t peer_id) 121 { 122 IvshmemPeer *peer; 123 124 peer = ivshmem_flat_find_peer(s, peer_id); 125 assert(peer); 126 127 QTAILQ_REMOVE(&s->peer, peer, next); 128 for (int n = 0; n < peer->vector_counter; n++) { 129 int efd; 130 efd = event_notifier_get_fd(&(peer->vector[n].event_notifier)); 131 close(efd); 132 } 133 134 g_free(peer); 135 } 136 137 static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer, 138 int vector_fd) 139 { 140 if (peer->vector_counter >= IVSHMEM_MAX_VECTOR_NUM) { 141 trace_ivshmem_flat_add_vector_failure(peer->vector_counter, 142 vector_fd, peer->id); 143 close(vector_fd); 144 145 return; 146 } 147 148 trace_ivshmem_flat_add_vector_success(peer->vector_counter, 149 vector_fd, peer->id); 150 151 /* 152 * Set vector ID and its associated eventfd notifier and add them to the 153 * peer. 154 */ 155 peer->vector[peer->vector_counter].id = peer->vector_counter; 156 g_unix_set_fd_nonblocking(vector_fd, true, NULL); 157 event_notifier_init_fd(&peer->vector[peer->vector_counter].event_notifier, 158 vector_fd); 159 160 /* 161 * If it's the device's own ID, register also the handler for the eventfd 162 * so the device can be notified by the other peers. 163 */ 164 if (peer == &s->own) { 165 qemu_set_fd_handler(vector_fd, ivshmem_flat_irq_handler, NULL, 166 &peer->vector); 167 } 168 169 peer->vector_counter++; 170 } 171 172 static void ivshmem_flat_process_msg(IvshmemFTState *s, uint64_t msg, int fd) 173 { 174 uint16_t peer_id; 175 IvshmemPeer *peer; 176 177 peer_id = msg & 0xFFFF; 178 peer = ivshmem_flat_find_peer(s, peer_id); 179 180 if (!peer) { 181 peer = ivshmem_flat_add_peer(s, peer_id); 182 } 183 184 if (fd >= 0) { 185 ivshmem_flat_add_vector(s, peer, fd); 186 } else { /* fd == -1, which is received when peers disconnect. */ 187 ivshmem_flat_remove_peer(s, peer_id); 188 } 189 } 190 191 static int ivshmem_flat_can_receive_data(void *opaque) 192 { 193 IvshmemFTState *s = opaque; 194 195 assert(s->msg_buffered_bytes < sizeof(s->msg_buf)); 196 return sizeof(s->msg_buf) - s->msg_buffered_bytes; 197 } 198 199 static void ivshmem_flat_read_msg(void *opaque, const uint8_t *buf, int size) 200 { 201 IvshmemFTState *s = opaque; 202 int fd; 203 int64_t msg; 204 205 assert(size >= 0 && s->msg_buffered_bytes + size <= sizeof(s->msg_buf)); 206 memcpy((unsigned char *)&s->msg_buf + s->msg_buffered_bytes, buf, size); 207 s->msg_buffered_bytes += size; 208 if (s->msg_buffered_bytes < sizeof(s->msg_buf)) { 209 return; 210 } 211 msg = le64_to_cpu(s->msg_buf); 212 s->msg_buffered_bytes = 0; 213 214 fd = qemu_chr_fe_get_msgfd(&s->server_chr); 215 216 ivshmem_flat_process_msg(s, msg, fd); 217 } 218 219 static uint64_t ivshmem_flat_iomem_read(void *opaque, 220 hwaddr offset, unsigned size) 221 { 222 IvshmemFTState *s = opaque; 223 uint32_t ret; 224 225 trace_ivshmem_flat_read_mmr(offset); 226 227 switch (offset) { 228 case INTMASK: 229 ret = 0; /* Ignore read since all bits are reserved in rev 1. */ 230 break; 231 case INTSTATUS: 232 ret = 0; /* Ignore read since all bits are reserved in rev 1. */ 233 break; 234 case IVPOSITION: 235 ret = s->own.id; 236 break; 237 case DOORBELL: 238 trace_ivshmem_flat_read_mmr_doorbell(); /* DOORBELL is write-only */ 239 ret = 0; 240 break; 241 default: 242 /* Should never reach out here due to iomem map range being exact */ 243 trace_ivshmem_flat_read_write_mmr_invalid(offset); 244 ret = 0; 245 } 246 247 return ret; 248 } 249 250 static int ivshmem_flat_interrupt_peer(IvshmemFTState *s, 251 uint16_t peer_id, uint16_t vector_id) 252 { 253 IvshmemPeer *peer; 254 255 peer = ivshmem_flat_find_peer(s, peer_id); 256 if (!peer) { 257 trace_ivshmem_flat_interrupt_invalid_peer(peer_id); 258 return 1; 259 } 260 261 event_notifier_set(&(peer->vector[vector_id].event_notifier)); 262 263 return 0; 264 } 265 266 static void ivshmem_flat_iomem_write(void *opaque, hwaddr offset, 267 uint64_t value, unsigned size) 268 { 269 IvshmemFTState *s = opaque; 270 uint16_t peer_id = (value >> 16) & 0xFFFF; 271 uint16_t vector_id = value & 0xFFFF; 272 273 trace_ivshmem_flat_write_mmr(offset); 274 275 switch (offset) { 276 case INTMASK: 277 break; 278 case INTSTATUS: 279 break; 280 case IVPOSITION: 281 break; 282 case DOORBELL: 283 trace_ivshmem_flat_interrupt_peer(peer_id, vector_id); 284 ivshmem_flat_interrupt_peer(s, peer_id, vector_id); 285 break; 286 default: 287 /* Should never reach out here due to iomem map range being exact. */ 288 trace_ivshmem_flat_read_write_mmr_invalid(offset); 289 break; 290 } 291 292 return; 293 } 294 295 static const MemoryRegionOps ivshmem_flat_ops = { 296 .read = ivshmem_flat_iomem_read, 297 .write = ivshmem_flat_iomem_write, 298 .endianness = DEVICE_LITTLE_ENDIAN, 299 .impl = { /* Read/write aligned at 32 bits. */ 300 .min_access_size = 4, 301 .max_access_size = 4, 302 }, 303 }; 304 305 static void ivshmem_flat_instance_init(Object *obj) 306 { 307 SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 308 IvshmemFTState *s = IVSHMEM_FLAT(obj); 309 310 /* 311 * Init mem region for 4 MMRs (ivshmem_registers), 312 * 32 bits each => 16 bytes (0x10). 313 */ 314 memory_region_init_io(&s->iomem, obj, &ivshmem_flat_ops, s, 315 "ivshmem-mmio", 0x10); 316 sysbus_init_mmio(sbd, &s->iomem); 317 318 /* 319 * Create one output IRQ that will be connect to the 320 * machine's interrupt controller. 321 */ 322 sysbus_init_irq(sbd, &s->irq); 323 324 QTAILQ_INIT(&s->peer); 325 } 326 327 static bool ivshmem_flat_connect_server(DeviceState *dev, Error **errp) 328 { 329 IvshmemFTState *s = IVSHMEM_FLAT(dev); 330 SysBusDevice *sbd = SYS_BUS_DEVICE(dev); 331 int64_t protocol_version, msg; 332 int shmem_fd; 333 uint16_t peer_id; 334 struct stat fdstat; 335 336 /* Check ivshmem server connection. */ 337 if (!qemu_chr_fe_backend_connected(&s->server_chr)) { 338 error_setg(errp, "ivshmem server socket not specified or incorret." 339 " Can't create device."); 340 return false; 341 } 342 343 /* 344 * Message sequence from server on new connection: 345 * _____________________________________ 346 * |STEP| uint64_t msg | int fd | 347 * ------------------------------------- 348 * 349 * 0 PROTOCOL -1 \ 350 * 1 OWN PEER ID -1 |-- Header/Greeting 351 * 2 -1 shmem fd / 352 * 353 * 3 PEER IDx Other peer's Vector 0 eventfd 354 * 4 PEER IDx Other peer's Vector 1 eventfd 355 * . . 356 * . . 357 * . . 358 * N PEER IDy Other peer's Vector 0 eventfd 359 * N+1 PEER IDy Other peer's Vector 1 eventfd 360 * . . 361 * . . 362 * . . 363 * 364 * ivshmem_flat_recv_msg() calls return 'msg' and 'fd'. 365 * 366 * See ./docs/specs/ivshmem-spec.txt for details on the protocol. 367 */ 368 369 /* Step 0 */ 370 protocol_version = ivshmem_flat_recv_msg(s, NULL); 371 372 /* Step 1 */ 373 msg = ivshmem_flat_recv_msg(s, NULL); 374 peer_id = 0xFFFF & msg; 375 s->own.id = peer_id; 376 s->own.vector_counter = 0; 377 378 trace_ivshmem_flat_proto_ver_own_id(protocol_version, s->own.id); 379 380 /* Step 2 */ 381 msg = ivshmem_flat_recv_msg(s, &shmem_fd); 382 /* Map shmem fd and MMRs into memory regions. */ 383 if (msg != -1 || shmem_fd < 0) { 384 error_setg(errp, "Could not receive valid shmem fd." 385 " Can't create device!"); 386 return false; 387 } 388 389 if (fstat(shmem_fd, &fdstat) != 0) { 390 error_setg(errp, "Could not determine shmem fd size." 391 " Can't create device!"); 392 return false; 393 } 394 trace_ivshmem_flat_shmem_size(shmem_fd, fdstat.st_size); 395 396 /* 397 * Shmem size provided by the ivshmem server must be equal to 398 * device's shmem size. 399 */ 400 if (fdstat.st_size != s->shmem_size) { 401 error_setg(errp, "Can't map shmem fd: shmem size different" 402 " from device size!"); 403 return false; 404 } 405 406 /* 407 * Beyond step 2 ivshmem_process_msg, called by ivshmem_flat_read_msg 408 * handler -- when data is available on the server socket -- will handle 409 * the additional messages that will be generated by the server as peers 410 * connect or disconnect. 411 */ 412 qemu_chr_fe_set_handlers(&s->server_chr, ivshmem_flat_can_receive_data, 413 ivshmem_flat_read_msg, NULL, NULL, s, NULL, true); 414 415 memory_region_init_ram_from_fd(&s->shmem, OBJECT(s), 416 "ivshmem-shmem", s->shmem_size, 417 RAM_SHARED, shmem_fd, 0, NULL); 418 sysbus_init_mmio(sbd, &s->shmem); 419 420 return true; 421 } 422 423 static void ivshmem_flat_realize(DeviceState *dev, Error **errp) 424 { 425 if (!ivshmem_flat_connect_server(dev, errp)) { 426 return; 427 } 428 } 429 430 static const Property ivshmem_flat_props[] = { 431 DEFINE_PROP_CHR("chardev", IvshmemFTState, server_chr), 432 DEFINE_PROP_UINT32("shmem-size", IvshmemFTState, shmem_size, 4 * MiB), 433 }; 434 435 static void ivshmem_flat_class_init(ObjectClass *klass, void *data) 436 { 437 DeviceClass *dc = DEVICE_CLASS(klass); 438 439 dc->hotpluggable = true; 440 dc->realize = ivshmem_flat_realize; 441 442 set_bit(DEVICE_CATEGORY_MISC, dc->categories); 443 device_class_set_props(dc, ivshmem_flat_props); 444 445 /* Reason: Must be wired up in code (sysbus MRs and IRQ) */ 446 dc->user_creatable = false; 447 } 448 449 static const TypeInfo ivshmem_flat_types[] = { 450 { 451 .name = TYPE_IVSHMEM_FLAT, 452 .parent = TYPE_SYS_BUS_DEVICE, 453 .instance_size = sizeof(IvshmemFTState), 454 .instance_init = ivshmem_flat_instance_init, 455 .class_init = ivshmem_flat_class_init, 456 }, 457 }; 458 459 DEFINE_TYPES(ivshmem_flat_types) 460