1# SPDX-License-Identifier: Apache-2.0
2
3if("${ARCH}" STREQUAL "x86")
4  set_ifndef(QEMU_binary_suffix i386)
5elseif("${ARCH}" STREQUAL "mips")
6  if(CONFIG_BIG_ENDIAN)
7    set_ifndef(QEMU_binary_suffix mips)
8  else()
9    set_ifndef(QEMU_binary_suffix mipsel)
10  endif()
11elseif(DEFINED QEMU_ARCH)
12  set_ifndef(QEMU_binary_suffix ${QEMU_ARCH})
13else()
14  set_ifndef(QEMU_binary_suffix ${ARCH})
15endif()
16
17set(qemu_alternate_path $ENV{QEMU_BIN_PATH})
18if(qemu_alternate_path)
19find_program(
20  QEMU
21  PATHS ${qemu_alternate_path}
22  NO_DEFAULT_PATH
23  NAMES qemu-system-${QEMU_binary_suffix}
24  )
25else()
26find_program(
27  QEMU
28  qemu-system-${QEMU_binary_suffix}
29  )
30endif()
31
32# We need to set up uefi-run and OVMF environment
33# for testing UEFI method on qemu platforms
34if(CONFIG_QEMU_UEFI_BOOT)
35  find_program(UEFI NAMES uefi-run REQUIRED)
36  if(DEFINED ENV{OVMF_FD_PATH})
37    set(OVMF_FD_PATH $ENV{OVMF_FD_PATH})
38  else()
39    message(FATAL_ERROR "Couldn't find an valid OVMF_FD_PATH.")
40  endif()
41  list(APPEND UEFI -b ${OVMF_FD_PATH} -q ${QEMU})
42  set(QEMU ${UEFI})
43endif()
44
45set(qemu_targets
46  run_qemu
47  debugserver_qemu
48  )
49
50set(QEMU_FLAGS -pidfile)
51if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
52  list(APPEND QEMU_FLAGS qemu\${QEMU_INSTANCE}.pid)
53else()
54  list(APPEND QEMU_FLAGS qemu${QEMU_INSTANCE}.pid)
55endif()
56
57# If running with sysbuild, we need to ensure this variable is populated
58zephyr_get(QEMU_PIPE)
59# Set up chardev for console.
60if(QEMU_PTY)
61  # Redirect console to a pseudo-tty, used for running automated tests.
62  list(APPEND QEMU_FLAGS -chardev pty,id=con,mux=on)
63elseif(QEMU_PIPE)
64  # Redirect console to a pipe, used for running automated tests.
65  list(APPEND QEMU_FLAGS -chardev pipe,id=con,mux=on,path=${QEMU_PIPE})
66  # Create the pipe file before passing the path to QEMU.
67  foreach(target ${qemu_targets})
68    list(APPEND PRE_QEMU_COMMANDS_FOR_${target} COMMAND ${CMAKE_COMMAND} -E touch ${QEMU_PIPE})
69  endforeach()
70else()
71  # Redirect console to stdio, used for manual debugging.
72  list(APPEND QEMU_FLAGS -chardev stdio,id=con,mux=on)
73endif()
74
75# Connect main serial port to the console chardev.
76list(APPEND QEMU_FLAGS -serial chardev:con)
77
78# Connect semihosting console to the console chardev if configured.
79if(CONFIG_SEMIHOST)
80  list(APPEND QEMU_FLAGS
81    -semihosting-config enable=on,target=auto,chardev=con
82    )
83endif()
84
85# Connect monitor to the console chardev.
86list(APPEND QEMU_FLAGS -mon chardev=con,mode=readline)
87
88if(CONFIG_QEMU_ICOUNT)
89  if(CONFIG_QEMU_ICOUNT_SLEEP)
90    list(APPEND QEMU_FLAGS
91	  -icount shift=${CONFIG_QEMU_ICOUNT_SHIFT},align=off,sleep=on
92	  -rtc clock=vm)
93  else()
94    list(APPEND QEMU_FLAGS
95	  -icount shift=${CONFIG_QEMU_ICOUNT_SHIFT},align=off,sleep=off
96	  -rtc clock=vm)
97  endif()
98endif()
99
100# Add a BT serial device when building for bluetooth, unless the
101# application explicitly opts out with NO_QEMU_SERIAL_BT_SERVER.
102if(CONFIG_BT)
103  if(NOT CONFIG_BT_UART)
104      set(NO_QEMU_SERIAL_BT_SERVER 1)
105  endif()
106  if(NOT NO_QEMU_SERIAL_BT_SERVER)
107    list(APPEND QEMU_FLAGS -serial unix:/tmp/bt-server-bredr)
108  endif()
109endif()
110
111# If we are running a networking application in QEMU, then set proper
112# QEMU variables. This also allows two QEMUs to be hooked together and
113# pass data between them. The QEMU flags are not set for standalone
114# tests defined by CONFIG_NET_TEST. For PPP, the serial port file is
115# not available if we run unit tests which define CONFIG_NET_TEST.
116if(CONFIG_NETWORKING)
117  if(CONFIG_NET_QEMU_SLIP)
118    if((CONFIG_NET_SLIP_TAP) OR (CONFIG_IEEE802154_UPIPE))
119      set(QEMU_NET_STACK 1)
120    endif()
121  elseif((CONFIG_NET_QEMU_PPP) AND NOT (CONFIG_NET_TEST))
122      set(QEMU_NET_STACK 1)
123  endif()
124endif()
125
126# TO create independent pipes for each QEMU application set QEMU_PIPE_STACK
127if(QEMU_PIPE_STACK)
128  list(APPEND qemu_targets
129    node
130    )
131
132  if(NOT QEMU_PIPE_ID)
133    set(QEMU_PIPE_ID 1)
134  endif()
135
136  list(APPEND QEMU_FLAGS
137    -serial none
138    )
139
140  list(APPEND MORE_FLAGS_FOR_node
141        -serial pipe:/tmp/hub/ip-stack-node${QEMU_PIPE_ID}
142        -pidfile qemu-node${QEMU_PIPE_ID}.pid
143        )
144
145  set(PIPE_NODE_IN  /tmp/hub/ip-stack-node${QEMU_PIPE_ID}.in)
146  set(PIPE_NODE_OUT /tmp/hub/ip-stack-node${QEMU_PIPE_ID}.out)
147
148  set(pipes
149    ${PIPE_NODE_IN}
150    ${PIPE_NODE_OUT}
151    )
152
153  set(destroy_pipe_commands
154    COMMAND ${CMAKE_COMMAND} -E remove -f ${pipes}
155    )
156
157  set(create_pipe_commands
158    COMMAND ${CMAKE_COMMAND} -E make_directory /tmp/hub
159    COMMAND mkfifo ${PIPE_NODE_IN}
160    COMMAND mkfifo ${PIPE_NODE_OUT}
161    )
162
163  set(PRE_QEMU_COMMANDS_FOR_node
164    ${destroy_pipe_commands}
165    ${create_pipe_commands}
166    )
167
168elseif(QEMU_NET_STACK)
169  list(APPEND qemu_targets
170    client
171    server
172    )
173
174  foreach(target ${qemu_targets})
175    if((${target} STREQUAL client) OR (${target} STREQUAL server))
176      list(APPEND MORE_FLAGS_FOR_${target}
177        -serial pipe:/tmp/ip-stack-${target}
178        -pidfile qemu-${target}.pid
179        )
180    else()
181      # QEMU_INSTANCE is a command line argument to *make* (not cmake). By
182      # appending the instance name to the pid file we can easily run more
183      # instances of the same sample.
184
185      if(CONFIG_NET_QEMU_PPP)
186	if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
187	  set(ppp_path unix:/tmp/ppp\${QEMU_INSTANCE})
188	else()
189	  set(ppp_path unix:/tmp/ppp${QEMU_INSTANCE})
190	endif()
191
192	list(APPEND MORE_FLAGS_FOR_${target}
193          -serial ${ppp_path}
194          )
195      else()
196	if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
197          set(tmp_file unix:/tmp/slip.sock\${QEMU_INSTANCE})
198	else()
199          set(tmp_file unix:/tmp/slip.sock${QEMU_INSTANCE})
200	endif()
201
202	list(APPEND MORE_FLAGS_FOR_${target}
203          -serial ${tmp_file}
204          )
205      endif()
206
207    endif()
208  endforeach()
209
210
211  set(PIPE_SERVER_IN  /tmp/ip-stack-server.in)
212  set(PIPE_SERVER_OUT /tmp/ip-stack-server.out)
213  set(PIPE_CLIENT_IN  /tmp/ip-stack-client.in)
214  set(PIPE_CLIENT_OUT /tmp/ip-stack-client.out)
215
216  set(pipes
217    ${PIPE_SERVER_IN}
218    ${PIPE_SERVER_OUT}
219    ${PIPE_CLIENT_IN}
220    ${PIPE_CLIENT_OUT}
221    )
222
223  set(destroy_pipe_commands
224    COMMAND ${CMAKE_COMMAND} -E remove -f ${pipes}
225    )
226
227  # TODO: Port to Windows. Perhaps using python? Or removing the
228  # need for mkfifo and create_symlink somehow.
229  set(create_pipe_commands
230    COMMAND mkfifo ${PIPE_SERVER_IN}
231    COMMAND mkfifo ${PIPE_SERVER_OUT}
232    )
233  if(PCAP)
234    list(APPEND create_pipe_commands
235      COMMAND mkfifo ${PIPE_CLIENT_IN}
236      COMMAND mkfifo ${PIPE_CLIENT_OUT}
237      )
238  else()
239    list(APPEND create_pipe_commands
240      COMMAND ${CMAKE_COMMAND} -E create_symlink ${PIPE_SERVER_IN}  ${PIPE_CLIENT_OUT}
241      COMMAND ${CMAKE_COMMAND} -E create_symlink ${PIPE_SERVER_OUT} ${PIPE_CLIENT_IN}
242      )
243  endif()
244
245  set(PRE_QEMU_COMMANDS_FOR_server
246    ${destroy_pipe_commands}
247    ${create_pipe_commands}
248    )
249  if(PCAP)
250    # Start a monitor application to capture traffic
251    #
252    # Assumes;
253    # PCAP has been set to the file where traffic should be captured
254    # NET_TOOLS has been set to the net-tools repo path
255    # net-tools/monitor_15_4 has been built beforehand
256
257    set_ifndef(NET_TOOLS ${ZEPHYR_BASE}/../tools/net-tools) # Default if not set
258
259    list(APPEND PRE_QEMU_COMMANDS_FOR_server
260      #Disable Ctrl-C to ensure that users won't accidentally exit
261      #w/o killing the monitor.
262      COMMAND stty intr ^d
263
264      #This command is run in the background using '&'. This prevents
265      #chaining other commands with '&&'. The command is enclosed in '{}'
266      #to fix this.
267      COMMAND {
268        ${NET_TOOLS}/monitor_15_4
269        ${PCAP}
270        /tmp/ip-stack-server
271        /tmp/ip-stack-client
272        > /dev/null &
273      }
274      )
275    set(POST_QEMU_COMMANDS_FOR_server
276      # Re-enable Ctrl-C.
277      COMMAND stty intr ^c
278
279      # Kill the monitor_15_4 sub-process
280      COMMAND pkill -P $$$$
281      )
282  endif()
283endif(QEMU_PIPE_STACK)
284
285if(CONFIG_CAN AND NOT (CONFIG_NIOS2 OR CONFIG_SOC_LEON3))
286  # Add CAN bus 0
287  list(APPEND QEMU_FLAGS -object can-bus,id=canbus0)
288
289  if(NOT "${CONFIG_CAN_QEMU_IFACE_NAME}" STREQUAL "")
290    # Connect CAN bus 0 to host SocketCAN interface
291    list(APPEND QEMU_FLAGS
292      -object can-host-socketcan,id=canhost0,if=${CONFIG_CAN_QEMU_IFACE_NAME},canbus=canbus0)
293  endif()
294
295  if(CONFIG_CAN_KVASER_PCI)
296    # Emulate a single-channel Kvaser PCIcan card connected to CAN bus 0
297    list(APPEND QEMU_FLAGS -device kvaser_pci,canbus=canbus0)
298  endif()
299endif()
300
301if(CONFIG_X86_64 AND NOT CONFIG_QEMU_UEFI_BOOT)
302  # QEMU doesn't like 64-bit ELF files. Since we don't use any >4GB
303  # addresses, converting it to 32-bit is safe enough for emulation.
304  add_custom_target(qemu_image_target
305    COMMAND
306    ${CMAKE_OBJCOPY}
307    -O elf32-i386
308    $<TARGET_FILE:${logical_target_for_zephyr_elf}>
309    ${ZEPHYR_BINARY_DIR}/zephyr-qemu.elf
310    DEPENDS ${logical_target_for_zephyr_elf}
311    )
312
313  # Split the 'locore' and 'main' memory regions into separate executable
314  # images and specify the 'locore' as the boot kernel, in order to prevent
315  # the QEMU direct multiboot kernel loader from overwriting the BIOS and
316  # option ROM areas located in between the two memory regions.
317  # (for more details, refer to the issue zephyrproject-rtos/sdk-ng#168)
318  add_custom_target(qemu_locore_image_target
319    COMMAND
320    ${CMAKE_OBJCOPY}
321    -j .locore
322    ${ZEPHYR_BINARY_DIR}/zephyr-qemu.elf
323    ${ZEPHYR_BINARY_DIR}/zephyr-qemu-locore.elf
324    2>&1 | grep -iv \"empty loadable segment detected\" || true
325    DEPENDS qemu_image_target
326    )
327
328  add_custom_target(qemu_main_image_target
329    COMMAND
330    ${CMAKE_OBJCOPY}
331    -R .locore
332    ${ZEPHYR_BINARY_DIR}/zephyr-qemu.elf
333    ${ZEPHYR_BINARY_DIR}/zephyr-qemu-main.elf
334    2>&1 | grep -iv \"empty loadable segment detected\" || true
335    DEPENDS qemu_image_target
336    )
337
338  add_custom_target(
339    qemu_kernel_target
340    DEPENDS qemu_locore_image_target qemu_main_image_target
341    )
342
343  set(QEMU_KERNEL_FILE "${ZEPHYR_BINARY_DIR}/zephyr-qemu-locore.elf")
344
345  list(APPEND QEMU_EXTRA_FLAGS
346    "-device;loader,file=${ZEPHYR_BINARY_DIR}/zephyr-qemu-main.elf"
347    )
348endif()
349
350if(CONFIG_IVSHMEM)
351  if(CONFIG_IVSHMEM_DOORBELL)
352    list(APPEND QEMU_FLAGS
353      -device ivshmem-doorbell,vectors=${CONFIG_IVSHMEM_MSI_X_VECTORS},chardev=ivshmem
354      -chardev socket,path=/tmp/ivshmem_socket,id=ivshmem
355    )
356  else()
357    list(APPEND QEMU_FLAGS
358      -device ivshmem-plain,memdev=hostmem
359      -object memory-backend-file,size=${CONFIG_QEMU_IVSHMEM_PLAIN_MEM_SIZE}M,share,mem-path=/dev/shm/ivshmem,id=hostmem
360    )
361  endif()
362endif()
363
364if(CONFIG_NVME)
365  if(qemu_alternate_path)
366    find_program(
367      QEMU_IMG
368      PATHS ${qemu_alternate_path}
369      NO_DEFAULT_PATH
370      NAMES qemu-img
371    )
372  else()
373    find_program(
374      QEMU_IMG
375      qemu-img
376    )
377  endif()
378
379  list(APPEND QEMU_EXTRA_FLAGS
380    -drive file=${ZEPHYR_BINARY_DIR}/nvme_disk.img,if=none,id=nvm1
381    -device nvme,serial=deadbeef,drive=nvm1
382  )
383
384  add_custom_target(qemu_nvme_disk
385    COMMAND
386    ${QEMU_IMG}
387    create
388    ${ZEPHYR_BINARY_DIR}/nvme_disk.img
389    1M
390  )
391else()
392  add_custom_target(qemu_nvme_disk)
393endif()
394
395if(NOT QEMU_PIPE)
396  set(QEMU_PIPE_COMMENT "\nTo exit from QEMU enter: 'CTRL+a, x'\n")
397endif()
398
399# Don't just test CONFIG_SMP, there is at least one test of the lower
400# level multiprocessor API that wants an auxiliary CPU but doesn't
401# want SMP using it.
402if(NOT CONFIG_MP_MAX_NUM_CPUS MATCHES "1")
403  list(APPEND QEMU_SMP_FLAGS -smp cpus=${CONFIG_MP_MAX_NUM_CPUS})
404endif()
405
406# Use flags passed in from the environment
407set(env_qemu $ENV{QEMU_EXTRA_FLAGS})
408separate_arguments(env_qemu)
409list(APPEND QEMU_EXTRA_FLAGS ${env_qemu})
410
411# Also append QEMU flags from config
412if(NOT CONFIG_QEMU_EXTRA_FLAGS STREQUAL "")
413  set(config_qemu_flags ${CONFIG_QEMU_EXTRA_FLAGS})
414  separate_arguments(config_qemu_flags)
415  list(APPEND QEMU_EXTRA_FLAGS "${config_qemu_flags}")
416endif()
417
418list(APPEND MORE_FLAGS_FOR_debugserver_qemu -S)
419
420if(NOT CONFIG_QEMU_GDBSERVER_LISTEN_DEV STREQUAL "")
421  list(APPEND MORE_FLAGS_FOR_debugserver_qemu -gdb "${CONFIG_QEMU_GDBSERVER_LISTEN_DEV}")
422endif()
423
424# Architectures can define QEMU_KERNEL_FILE to use a specific output
425# file to pass to qemu (and a "qemu_kernel_target" target to generate
426# it), or set QEMU_KERNEL_OPTION if they want to replace the "-kernel
427# ..." option entirely.
428if(CONFIG_QEMU_UEFI_BOOT)
429  set(QEMU_UEFI_OPTION  ${PROJECT_BINARY_DIR}/${CONFIG_KERNEL_BIN_NAME}.efi)
430  list(APPEND QEMU_UEFI_OPTION --)
431elseif(DEFINED QEMU_KERNEL_FILE)
432  set(QEMU_KERNEL_OPTION "-kernel;${QEMU_KERNEL_FILE}")
433elseif(NOT DEFINED QEMU_KERNEL_OPTION)
434  set(QEMU_KERNEL_OPTION "-kernel;$<TARGET_FILE:${logical_target_for_zephyr_elf}>")
435elseif(DEFINED QEMU_KERNEL_OPTION)
436  string(CONFIGURE "${QEMU_KERNEL_OPTION}" QEMU_KERNEL_OPTION)
437endif()
438
439foreach(target ${qemu_targets})
440  add_custom_target(${target}
441    ${PRE_QEMU_COMMANDS}
442    ${PRE_QEMU_COMMANDS_FOR_${target}}
443    COMMAND
444    ${QEMU}
445    ${QEMU_UEFI_OPTION}
446    ${QEMU_FLAGS_${ARCH}}
447    ${QEMU_FLAGS}
448    ${QEMU_EXTRA_FLAGS}
449    ${MORE_FLAGS_FOR_${target}}
450    ${QEMU_SMP_FLAGS}
451    ${QEMU_KERNEL_OPTION}
452    ${POST_QEMU_COMMANDS_FOR_${target}}
453    DEPENDS ${logical_target_for_zephyr_elf}
454    WORKING_DIRECTORY ${APPLICATION_BINARY_DIR}
455    COMMENT "${QEMU_PIPE_COMMENT}[QEMU] CPU: ${QEMU_CPU_TYPE_${ARCH}}"
456    USES_TERMINAL
457    )
458  if(DEFINED QEMU_KERNEL_FILE)
459    add_dependencies(${target} qemu_nvme_disk qemu_kernel_target)
460  endif()
461endforeach()
462