Compare commits
126 Commits
ui-and-inp
...
SLE12-SP1-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3456706297 | ||
|
|
6bfc70462b | ||
|
|
77bd1c30f4 | ||
|
|
0d9bbf181e | ||
|
|
c7f5d47e08 | ||
|
|
aa7fb34246 | ||
|
|
4c3849714f | ||
|
|
8970c6a04e | ||
|
|
0bcc356da2 | ||
|
|
5cc51586d1 | ||
|
|
7637d9fbfe | ||
|
|
589b223ca5 | ||
|
|
225bfdb2ed | ||
|
|
fc1b871631 | ||
|
|
a78f57a82b | ||
|
|
457b3ba055 | ||
|
|
bff3afb57d | ||
|
|
eb70eb33cb | ||
|
|
8526e2d444 | ||
|
|
99bc193099 | ||
|
|
5e454fe423 | ||
|
|
41eb27692f | ||
|
|
c251fa606e | ||
|
|
c923a7775c | ||
|
|
09d66eb5f5 | ||
|
|
b313f6d23d | ||
|
|
319e0e347f | ||
|
|
daede1f0f2 | ||
|
|
789adc278e | ||
|
|
c4c86364b3 | ||
|
|
b31a018f58 | ||
|
|
588bf41e1f | ||
|
|
babe800bcc | ||
|
|
17e0a09e25 | ||
|
|
ed87ee47e0 | ||
|
|
621ddf509e | ||
|
|
ac10d55b82 | ||
|
|
8e9b8b2204 | ||
|
|
2ac80007da | ||
|
|
4168180338 | ||
|
|
683051c844 | ||
|
|
4a7c93f23e | ||
|
|
66e090df95 | ||
|
|
acc6309904 | ||
|
|
9bb28247d7 | ||
|
|
1a8b5a066a | ||
|
|
5673fb20cd | ||
|
|
f8898ec278 | ||
|
|
efa9daceb1 | ||
|
|
ed19da1195 | ||
|
|
b8ddc9be4c | ||
|
|
0228bc7ae0 | ||
|
|
a5a901fc4c | ||
|
|
ca05fad76e | ||
|
|
6855fbd119 | ||
|
|
02792b8b1d | ||
|
|
f72dd33b9a | ||
|
|
cc2fb7f00d | ||
|
|
bd2ce7536f | ||
|
|
f2b44e9116 | ||
|
|
b5af05aeb7 | ||
|
|
dfa83a6bae | ||
|
|
35a616edef | ||
|
|
35c30d3efd | ||
|
|
f4c861fd68 | ||
|
|
b7a197c39e | ||
|
|
85611098ff | ||
|
|
ce4f451bbb | ||
|
|
6722c126f3 | ||
|
|
8dd45dcd83 | ||
|
|
e750591c8a | ||
|
|
f9c0ae2723 | ||
|
|
c8bd74d1d5 | ||
|
|
d1557697fd | ||
|
|
86d6fe4cb0 | ||
|
|
9634e45e0b | ||
|
|
0dc545e977 | ||
|
|
358f0ee234 | ||
|
|
961c74a841 | ||
|
|
98fe91ed66 | ||
|
|
46addaa0b5 | ||
|
|
5a4568717c | ||
|
|
87740cecc3 | ||
|
|
8df2a9acd2 | ||
|
|
c5c71e87aa | ||
|
|
2060efae47 | ||
|
|
8d64975c98 | ||
|
|
9b4420ad62 | ||
|
|
99c3468d8f | ||
|
|
1c17e8c7d3 | ||
|
|
ffd060d51f | ||
|
|
e4fb4bea37 | ||
|
|
edc0a65326 | ||
|
|
c62f6c8f67 | ||
|
|
3d8b7aed60 | ||
|
|
27ed14c4d7 | ||
|
|
6a45a1b8e4 | ||
|
|
6cacd2651a | ||
|
|
e8248a5af1 | ||
|
|
81cb0a5657 | ||
|
|
6130c46232 | ||
|
|
49ef542e41 | ||
|
|
c270245a53 | ||
|
|
9272707a1f | ||
|
|
c759f1a078 | ||
|
|
714b54401c | ||
|
|
e7e08380c3 | ||
|
|
c631ee6520 | ||
|
|
b153c8d3f3 | ||
|
|
f45048225a | ||
|
|
ae0fa48f51 | ||
|
|
bb3a1da4d4 | ||
|
|
b48a391cff | ||
|
|
cc883fe42d | ||
|
|
4072585ecf | ||
|
|
959fad0ff1 | ||
|
|
a4bb522ee5 | ||
|
|
cf6c213981 | ||
|
|
cf3297868c | ||
|
|
ad9c167fd2 | ||
|
|
d8e231fce2 | ||
|
|
53cd79c117 | ||
|
|
3dd15f3e58 | ||
|
|
4c59860506 | ||
|
|
b575af0730 | ||
|
|
d3b59789e8 |
@@ -1,2 +0,0 @@
|
|||||||
((c-mode . ((c-file-style . "stroustrup")
|
|
||||||
(indent-tabs-mode . nil))))
|
|
||||||
8
.gdbinit
8
.gdbinit
@@ -1,8 +0,0 @@
|
|||||||
# GDB may have ./.gdbinit loading disabled by default. In that case you can
|
|
||||||
# follow the instructions it prints. They boil down to adding the following to
|
|
||||||
# your home directory's ~/.gdbinit file:
|
|
||||||
#
|
|
||||||
# add-auto-load-safe-path /path/to/qemu/.gdbinit
|
|
||||||
|
|
||||||
# Load QEMU-specific sub-commands and settings
|
|
||||||
source scripts/qemu-gdb.py
|
|
||||||
59
.gitignore
vendored
59
.gitignore
vendored
@@ -5,22 +5,24 @@
|
|||||||
/config-target.*
|
/config-target.*
|
||||||
/config.status
|
/config.status
|
||||||
/config-temp
|
/config-temp
|
||||||
/trace-events-all
|
/trace/generated-tracers.h
|
||||||
|
/trace/generated-tracers.c
|
||||||
|
/trace/generated-tracers-dtrace.h
|
||||||
|
/trace/generated-tracers.dtrace
|
||||||
/trace/generated-events.h
|
/trace/generated-events.h
|
||||||
/trace/generated-events.c
|
/trace/generated-events.c
|
||||||
/trace/generated-helpers-wrappers.h
|
/trace/generated-helpers-wrappers.h
|
||||||
/trace/generated-helpers.h
|
/trace/generated-helpers.h
|
||||||
/trace/generated-helpers.c
|
/trace/generated-helpers.c
|
||||||
/trace/generated-tcg-tracers.h
|
/trace/generated-tcg-tracers.h
|
||||||
/ui/shader/texture-blit-frag.h
|
/trace/generated-ust-provider.h
|
||||||
/ui/shader/texture-blit-vert.h
|
/trace/generated-ust.c
|
||||||
|
/libcacard/trace/generated-tracers.c
|
||||||
*-timestamp
|
*-timestamp
|
||||||
/*-softmmu
|
/*-softmmu
|
||||||
/*-darwin-user
|
/*-darwin-user
|
||||||
/*-linux-user
|
/*-linux-user
|
||||||
/*-bsd-user
|
/*-bsd-user
|
||||||
/ivshmem-client
|
|
||||||
/ivshmem-server
|
|
||||||
/libdis*
|
/libdis*
|
||||||
/libuser
|
/libuser
|
||||||
/linux-headers/asm
|
/linux-headers/asm
|
||||||
@@ -30,11 +32,11 @@
|
|||||||
/qapi-visit.[ch]
|
/qapi-visit.[ch]
|
||||||
/qapi-event.[ch]
|
/qapi-event.[ch]
|
||||||
/qmp-commands.h
|
/qmp-commands.h
|
||||||
/qmp-introspect.[ch]
|
|
||||||
/qmp-marshal.c
|
/qmp-marshal.c
|
||||||
/qemu-doc.html
|
/qemu-doc.html
|
||||||
|
/qemu-tech.html
|
||||||
/qemu-doc.info
|
/qemu-doc.info
|
||||||
/qemu-doc.txt
|
/qemu-tech.info
|
||||||
/qemu-img
|
/qemu-img
|
||||||
/qemu-nbd
|
/qemu-nbd
|
||||||
/qemu-options.def
|
/qemu-options.def
|
||||||
@@ -45,19 +47,15 @@
|
|||||||
/qemu-ga
|
/qemu-ga
|
||||||
/qemu-bridge-helper
|
/qemu-bridge-helper
|
||||||
/qemu-monitor.texi
|
/qemu-monitor.texi
|
||||||
/qemu-monitor-info.texi
|
/qmp-commands.txt
|
||||||
/qemu-version.h
|
|
||||||
/qemu-version.h.tmp
|
|
||||||
/module_block.h
|
|
||||||
/vscclient
|
/vscclient
|
||||||
/vhost-user-scsi
|
|
||||||
/fsdev/virtfs-proxy-helper
|
/fsdev/virtfs-proxy-helper
|
||||||
*.[1-9]
|
*.[1-9]
|
||||||
*.a
|
*.a
|
||||||
*.aux
|
*.aux
|
||||||
*.cp
|
*.cp
|
||||||
|
*.dvi
|
||||||
*.exe
|
*.exe
|
||||||
*.msi
|
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.mo
|
*.mo
|
||||||
@@ -77,6 +75,10 @@
|
|||||||
*.d
|
*.d
|
||||||
!/scripts/qemu-guest-agent/fsfreeze-hook.d
|
!/scripts/qemu-guest-agent/fsfreeze-hook.d
|
||||||
*.o
|
*.o
|
||||||
|
*.lo
|
||||||
|
*.la
|
||||||
|
*.pc
|
||||||
|
.libs
|
||||||
.sdk
|
.sdk
|
||||||
*.gcda
|
*.gcda
|
||||||
*.gcno
|
*.gcno
|
||||||
@@ -86,10 +88,6 @@
|
|||||||
/pc-bios/optionrom/linuxboot.bin
|
/pc-bios/optionrom/linuxboot.bin
|
||||||
/pc-bios/optionrom/linuxboot.raw
|
/pc-bios/optionrom/linuxboot.raw
|
||||||
/pc-bios/optionrom/linuxboot.img
|
/pc-bios/optionrom/linuxboot.img
|
||||||
/pc-bios/optionrom/linuxboot_dma.asm
|
|
||||||
/pc-bios/optionrom/linuxboot_dma.bin
|
|
||||||
/pc-bios/optionrom/linuxboot_dma.raw
|
|
||||||
/pc-bios/optionrom/linuxboot_dma.img
|
|
||||||
/pc-bios/optionrom/multiboot.asm
|
/pc-bios/optionrom/multiboot.asm
|
||||||
/pc-bios/optionrom/multiboot.bin
|
/pc-bios/optionrom/multiboot.bin
|
||||||
/pc-bios/optionrom/multiboot.raw
|
/pc-bios/optionrom/multiboot.raw
|
||||||
@@ -100,35 +98,8 @@
|
|||||||
/pc-bios/optionrom/kvmvapic.img
|
/pc-bios/optionrom/kvmvapic.img
|
||||||
/pc-bios/s390-ccw/s390-ccw.elf
|
/pc-bios/s390-ccw/s390-ccw.elf
|
||||||
/pc-bios/s390-ccw/s390-ccw.img
|
/pc-bios/s390-ccw/s390-ccw.img
|
||||||
/docs/interop/qemu-ga-qapi.texi
|
|
||||||
/docs/interop/qemu-ga-ref.html
|
|
||||||
/docs/interop/qemu-ga-ref.info*
|
|
||||||
/docs/interop/qemu-ga-ref.txt
|
|
||||||
/docs/interop/qemu-qmp-qapi.texi
|
|
||||||
/docs/interop/qemu-qmp-ref.html
|
|
||||||
/docs/interop/qemu-qmp-ref.info*
|
|
||||||
/docs/interop/qemu-qmp-ref.txt
|
|
||||||
/docs/version.texi
|
|
||||||
*.tps
|
|
||||||
.stgit-*
|
.stgit-*
|
||||||
cscope.*
|
cscope.*
|
||||||
tags
|
tags
|
||||||
TAGS
|
TAGS
|
||||||
docker-src.*
|
|
||||||
*~
|
*~
|
||||||
trace.h
|
|
||||||
trace.c
|
|
||||||
trace-ust.h
|
|
||||||
trace-ust.h
|
|
||||||
trace-dtrace.h
|
|
||||||
trace-dtrace.dtrace
|
|
||||||
trace-root.h
|
|
||||||
trace-root.c
|
|
||||||
trace-ust-root.h
|
|
||||||
trace-ust-root.h
|
|
||||||
trace-ust-all.h
|
|
||||||
trace-ust-all.c
|
|
||||||
trace-dtrace-root.h
|
|
||||||
trace-dtrace-root.dtrace
|
|
||||||
trace-ust-all.h
|
|
||||||
trace-ust-all.c
|
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -31,9 +31,3 @@
|
|||||||
[submodule "roms/u-boot"]
|
[submodule "roms/u-boot"]
|
||||||
path = roms/u-boot
|
path = roms/u-boot
|
||||||
url = git://git.qemu-project.org/u-boot.git
|
url = git://git.qemu-project.org/u-boot.git
|
||||||
[submodule "roms/skiboot"]
|
|
||||||
path = roms/skiboot
|
|
||||||
url = git://git.qemu.org/skiboot.git
|
|
||||||
[submodule "roms/QemuMacDrivers"]
|
|
||||||
path = roms/QemuMacDrivers
|
|
||||||
url = git://git.qemu.org/QemuMacDrivers.git
|
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
language: c
|
|
||||||
git:
|
|
||||||
submodules: false
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- LC_ALL=C
|
|
||||||
matrix:
|
|
||||||
- IMAGE=debian-armhf-cross
|
|
||||||
TARGET_LIST=arm-softmmu,arm-linux-user,armeb-linux-user
|
|
||||||
- IMAGE=debian-arm64-cross
|
|
||||||
TARGET_LIST=aarch64-softmmu,aarch64-linux-user
|
|
||||||
- IMAGE=debian-s390x-cross
|
|
||||||
TARGET_LIST=s390x-softmmu,s390x-linux-user
|
|
||||||
# mips64el-softmmu disabled due to libfdt problem
|
|
||||||
- IMAGE=debian-mipsel-cross
|
|
||||||
TARGET_LIST=mipsel-softmmu,mipsel-linux-user,mips64el-linux-user
|
|
||||||
build:
|
|
||||||
pre_ci:
|
|
||||||
- make docker-image-${IMAGE} V=1
|
|
||||||
pre_ci_boot:
|
|
||||||
image_name: qemu
|
|
||||||
image_tag: ${IMAGE}
|
|
||||||
pull: false
|
|
||||||
options: "-e HOME=/root"
|
|
||||||
ci:
|
|
||||||
- unset CC
|
|
||||||
# some targets require newer up to date packages, for example TARGET_LIST matching
|
|
||||||
# aarch64*-softmmu|arm*-softmmu|ppc*-softmmu|microblaze*-softmmu|mips64el-softmmu)
|
|
||||||
# see the configure script:
|
|
||||||
# error_exit "DTC (libfdt) version >= 1.4.2 not present. Your options:"
|
|
||||||
# " (1) Preferred: Install the DTC (libfdt) devel package"
|
|
||||||
# " (2) Fetch the DTC submodule, using:"
|
|
||||||
# " git submodule update --init dtc"
|
|
||||||
- dpkg --compare-versions `dpkg-query --showformat='${Version}' --show libfdt-dev` ge 1.4.2 || git submodule update --init dtc
|
|
||||||
- ./configure ${QEMU_CONFIGURE_OPTS} --target-list=${TARGET_LIST}
|
|
||||||
- make -j$(($(getconf _NPROCESSORS_ONLN) + 1))
|
|
||||||
237
.travis.yml
237
.travis.yml
@@ -1,194 +1,103 @@
|
|||||||
sudo: false
|
|
||||||
language: c
|
language: c
|
||||||
python:
|
python:
|
||||||
- "2.4"
|
- "2.4"
|
||||||
compiler:
|
compiler:
|
||||||
- gcc
|
- gcc
|
||||||
cache: ccache
|
- clang
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
# Build dependencies
|
|
||||||
- libaio-dev
|
|
||||||
- libattr1-dev
|
|
||||||
- libbrlapi-dev
|
|
||||||
- libcap-ng-dev
|
|
||||||
- libgnutls-dev
|
|
||||||
- libgtk-3-dev
|
|
||||||
- libiscsi-dev
|
|
||||||
- liblttng-ust-dev
|
|
||||||
- libnfs-dev
|
|
||||||
- libncurses5-dev
|
|
||||||
- libnss3-dev
|
|
||||||
- libpixman-1-dev
|
|
||||||
- libpng12-dev
|
|
||||||
- librados-dev
|
|
||||||
- libsdl1.2-dev
|
|
||||||
- libseccomp-dev
|
|
||||||
- libspice-protocol-dev
|
|
||||||
- libspice-server-dev
|
|
||||||
- libssh2-1-dev
|
|
||||||
- liburcu-dev
|
|
||||||
- libusb-1.0-0-dev
|
|
||||||
- libvte-2.90-dev
|
|
||||||
- sparse
|
|
||||||
- uuid-dev
|
|
||||||
|
|
||||||
# The channel name "irc.oftc.net#qemu" is encrypted against qemu/qemu
|
|
||||||
# to prevent IRC notifications from forks. This was created using:
|
|
||||||
# $ travis encrypt -r "qemu/qemu" "irc.oftc.net#qemu"
|
|
||||||
notifications:
|
notifications:
|
||||||
irc:
|
irc:
|
||||||
channels:
|
channels:
|
||||||
- secure: "F7GDRgjuOo5IUyRLqSkmDL7kvdU4UcH3Lm/W2db2JnDHTGCqgEdaYEYKciyCLZ57vOTsTsOgesN8iUT7hNHBd1KWKjZe9KDTZWppWRYVwAwQMzVeSOsbbU4tRoJ6Pp+3qhH1Z0eGYR9ZgKYAoTumDFgSAYRp4IscKS8jkoedOqM="
|
- "irc.oftc.net#qemu"
|
||||||
on_success: change
|
on_success: change
|
||||||
on_failure: always
|
on_failure: always
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- TEST_CMD="make check"
|
- TEST_CMD=""
|
||||||
|
- EXTRA_CONFIG=""
|
||||||
|
# Development packages, EXTRA_PKGS saved for additional builds
|
||||||
|
- CORE_PKGS="libusb-1.0-0-dev libiscsi-dev librados-dev libncurses5-dev"
|
||||||
|
- NET_PKGS="libseccomp-dev libgnutls-dev libssh2-1-dev libspice-server-dev libspice-protocol-dev libnss3-dev"
|
||||||
|
- GUI_PKGS="libgtk-3-dev libvte-2.90-dev libsdl1.2-dev libpng12-dev libpixman-1-dev"
|
||||||
|
- EXTRA_PKGS=""
|
||||||
matrix:
|
matrix:
|
||||||
- CONFIG=""
|
# Group major targets together with their linux-user counterparts
|
||||||
- CONFIG="--enable-debug --enable-debug-tcg --enable-trace-backends=log"
|
- TARGETS=alpha-softmmu,alpha-linux-user
|
||||||
- CONFIG="--disable-linux-aio --disable-cap-ng --disable-attr --disable-brlapi --disable-uuid --disable-libusb"
|
- TARGETS=arm-softmmu,arm-linux-user,armeb-linux-user,aarch64-softmmu,aarch64-linux-user
|
||||||
- CONFIG="--enable-modules"
|
- TARGETS=cris-softmmu,cris-linux-user
|
||||||
- CONFIG="--with-coroutine=ucontext"
|
- TARGETS=i386-softmmu,i386-linux-user,x86_64-softmmu,x86_64-linux-user
|
||||||
- CONFIG="--with-coroutine=sigaltstack"
|
- TARGETS=m68k-softmmu,m68k-linux-user
|
||||||
|
- TARGETS=microblaze-softmmu,microblazeel-softmmu,microblaze-linux-user,microblazeel-linux-user
|
||||||
|
- TARGETS=mips-softmmu,mips64-softmmu,mips64el-softmmu,mipsel-softmmu
|
||||||
|
- TARGETS=mips-linux-user,mips64-linux-user,mips64el-linux-user,mipsel-linux-user,mipsn32-linux-user,mipsn32el-linux-user
|
||||||
|
- TARGETS=or32-softmmu,or32-linux-user
|
||||||
|
- TARGETS=ppc-softmmu,ppc64-softmmu,ppcemb-softmmu,ppc-linux-user,ppc64-linux-user,ppc64abi32-linux-user,ppc64le-linux-user
|
||||||
|
- TARGETS=s390x-softmmu,s390x-linux-user
|
||||||
|
- TARGETS=sh4-softmmu,sh4eb-softmmu,sh4-linux-user sh4eb-linux-user
|
||||||
|
- TARGETS=sparc-softmmu,sparc64-softmmu,sparc-linux-user,sparc32plus-linux-user,sparc64-linux-user
|
||||||
|
- TARGETS=unicore32-softmmu,unicore32-linux-user
|
||||||
|
# Group remaining softmmu only targets into one build
|
||||||
|
- TARGETS=lm32-softmmu,moxie-softmmu,tricore-softmmu,xtensa-softmmu,xtensaeb-softmmu
|
||||||
git:
|
git:
|
||||||
# we want to do this ourselves
|
# we want to do this ourselves
|
||||||
submodules: false
|
submodules: false
|
||||||
before_install:
|
before_install:
|
||||||
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi
|
|
||||||
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install libffi gettext glib pixman ; fi
|
|
||||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
|
- sudo apt-get update -qq
|
||||||
|
- sudo apt-get install -qq ${CORE_PKGS} ${NET_PKGS} ${GUI_PKGS} ${EXTRA_PKGS}
|
||||||
before_script:
|
before_script:
|
||||||
- ./configure ${CONFIG}
|
- ./configure --target-list=${TARGETS} --enable-debug-tcg ${EXTRA_CONFIG}
|
||||||
script:
|
script:
|
||||||
- make -j3 && ${TEST_CMD}
|
- make -j2 && ${TEST_CMD}
|
||||||
matrix:
|
matrix:
|
||||||
|
# We manually include a number of additional build for non-standard bits
|
||||||
include:
|
include:
|
||||||
# Test with CLang for compile portability
|
# Make check target (we only do this once)
|
||||||
- env: CONFIG=""
|
- env:
|
||||||
compiler: clang
|
- TARGETS=alpha-softmmu,arm-softmmu,aarch64-softmmu,cris-softmmu,
|
||||||
# gprof/gcov are GCC features
|
i386-softmmu,x86_64-softmmu,m68k-softmmu,microblaze-softmmu,
|
||||||
- env: CONFIG="--enable-gprof --enable-gcov --disable-pie"
|
microblazeel-softmmu,mips-softmmu,mips64-softmmu,
|
||||||
|
mips64el-softmmu,mipsel-softmmu,or32-softmmu,ppc-softmmu,
|
||||||
|
ppc64-softmmu,ppcemb-softmmu,s390x-softmmu,sh4-softmmu,
|
||||||
|
sh4eb-softmmu,sparc-softmmu,sparc64-softmmu,
|
||||||
|
unicore32-softmmu,unicore32-linux-user,
|
||||||
|
lm32-softmmu,moxie-softmmu,tricore-softmmu,xtensa-softmmu,
|
||||||
|
xtensaeb-softmmu
|
||||||
|
TEST_CMD="make check"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
# We manually include builds which we disable "make check" for
|
# Debug related options
|
||||||
- env: CONFIG="--enable-debug --enable-tcg-interpreter"
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
TEST_CMD=""
|
EXTRA_CONFIG="--enable-debug"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
- env: CONFIG="--enable-trace-backends=simple"
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
TEST_CMD=""
|
EXTRA_CONFIG="--enable-debug --enable-tcg-interpreter"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
- env: CONFIG="--enable-trace-backends=ftrace"
|
# All the extra -dev packages
|
||||||
TEST_CMD=""
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
|
EXTRA_PKGS="libaio-dev libcap-ng-dev libattr1-dev libbrlapi-dev uuid-dev libusb-1.0.0-dev"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
- env: CONFIG="--enable-trace-backends=ust"
|
# Currently configure doesn't force --disable-pie
|
||||||
TEST_CMD=""
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
|
EXTRA_CONFIG="--enable-gprof --enable-gcov --disable-pie"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
- env: CONFIG=""
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
os: osx
|
EXTRA_PKGS="sparse"
|
||||||
compiler: clang
|
EXTRA_CONFIG="--enable-sparse"
|
||||||
# Plain Trusty System Build
|
|
||||||
- env: CONFIG="--disable-linux-user"
|
|
||||||
sudo: required
|
|
||||||
addons:
|
|
||||||
dist: trusty
|
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
before_install:
|
# All the trace backends (apart from dtrace)
|
||||||
- sudo apt-get update -qq
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
- sudo apt-get build-dep -qq qemu
|
EXTRA_CONFIG="--enable-trace-backends=stderr"
|
||||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
compiler: gcc
|
||||||
- git submodule update --init --recursive
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
# Plain Trusty Linux User Build
|
EXTRA_CONFIG="--enable-trace-backends=simple"
|
||||||
- env: CONFIG="--disable-system"
|
compiler: gcc
|
||||||
sudo: required
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
addons:
|
EXTRA_CONFIG="--enable-trace-backends=ftrace"
|
||||||
dist: trusty
|
compiler: gcc
|
||||||
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
|
EXTRA_PKGS="liblttng-ust-dev liburcu-dev"
|
||||||
|
EXTRA_CONFIG="--enable-trace-backends=ust"
|
||||||
|
compiler: gcc
|
||||||
|
- env: TARGETS=i386-softmmu,x86_64-softmmu
|
||||||
|
EXTRA_CONFIG="--enable-modules"
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
before_install:
|
|
||||||
- sudo apt-get update -qq
|
|
||||||
- sudo apt-get build-dep -qq qemu
|
|
||||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
# Trusty System build with latest stable clang
|
|
||||||
- sudo: required
|
|
||||||
addons:
|
|
||||||
dist: trusty
|
|
||||||
language: generic
|
|
||||||
compiler: none
|
|
||||||
env:
|
|
||||||
- COMPILER_NAME=clang CXX=clang++-3.9 CC=clang-3.9
|
|
||||||
- CONFIG="--disable-linux-user --cc=clang-3.9 --cxx=clang++-3.9"
|
|
||||||
before_install:
|
|
||||||
- wget -nv -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -
|
|
||||||
- sudo apt-add-repository -y 'deb http://llvm.org/apt/trusty llvm-toolchain-trusty-3.9 main'
|
|
||||||
- sudo apt-get update -qq
|
|
||||||
- sudo apt-get install -qq -y clang-3.9
|
|
||||||
- sudo apt-get build-dep -qq qemu
|
|
||||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
before_script:
|
|
||||||
- ./configure ${CONFIG} || cat config.log
|
|
||||||
# Trusty Linux User build with latest stable clang
|
|
||||||
- sudo: required
|
|
||||||
addons:
|
|
||||||
dist: trusty
|
|
||||||
language: generic
|
|
||||||
compiler: none
|
|
||||||
env:
|
|
||||||
- COMPILER_NAME=clang CXX=clang++-3.9 CC=clang-3.9
|
|
||||||
- CONFIG="--disable-system --cc=clang-3.9 --cxx=clang++-3.9"
|
|
||||||
before_install:
|
|
||||||
- wget -nv -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -
|
|
||||||
- sudo apt-add-repository -y 'deb http://llvm.org/apt/trusty llvm-toolchain-trusty-3.9 main'
|
|
||||||
- sudo apt-get update -qq
|
|
||||||
- sudo apt-get install -qq -y clang-3.9
|
|
||||||
- sudo apt-get build-dep -qq qemu
|
|
||||||
- wget -O - http://people.linaro.org/~alex.bennee/qemu-submodule-git-seed.tar.xz | tar -xvJ
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
before_script:
|
|
||||||
- ./configure ${CONFIG} || cat config.log
|
|
||||||
# Using newer GCC with sanitizers
|
|
||||||
- addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
# PPAs for newer toolchains
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
# Extra toolchains
|
|
||||||
- gcc-5
|
|
||||||
- g++-5
|
|
||||||
# Build dependencies
|
|
||||||
- libaio-dev
|
|
||||||
- libattr1-dev
|
|
||||||
- libbrlapi-dev
|
|
||||||
- libcap-ng-dev
|
|
||||||
- libgnutls-dev
|
|
||||||
- libgtk-3-dev
|
|
||||||
- libiscsi-dev
|
|
||||||
- liblttng-ust-dev
|
|
||||||
- libnfs-dev
|
|
||||||
- libncurses5-dev
|
|
||||||
- libnss3-dev
|
|
||||||
- libpixman-1-dev
|
|
||||||
- libpng12-dev
|
|
||||||
- librados-dev
|
|
||||||
- libsdl1.2-dev
|
|
||||||
- libseccomp-dev
|
|
||||||
- libspice-protocol-dev
|
|
||||||
- libspice-server-dev
|
|
||||||
- libssh2-1-dev
|
|
||||||
- liburcu-dev
|
|
||||||
- libusb-1.0-0-dev
|
|
||||||
- libvte-2.90-dev
|
|
||||||
- sparse
|
|
||||||
- uuid-dev
|
|
||||||
language: generic
|
|
||||||
compiler: none
|
|
||||||
env:
|
|
||||||
- COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
|
|
||||||
- CONFIG="--cc=gcc-5 --cxx=g++-5 --disable-pie --disable-linux-user"
|
|
||||||
- TEST_CMD=""
|
|
||||||
before_script:
|
|
||||||
- ./configure ${CONFIG} --extra-cflags="-g3 -O0 -fsanitize=thread -fuse-ld=gold" || cat config.log
|
|
||||||
|
|||||||
30
CODING_STYLE
30
CODING_STYLE
@@ -9,7 +9,7 @@ patches before submitting.
|
|||||||
Of course, the most important aspect in any coding style is whitespace.
|
Of course, the most important aspect in any coding style is whitespace.
|
||||||
Crusty old coders who have trouble spotting the glasses on their noses
|
Crusty old coders who have trouble spotting the glasses on their noses
|
||||||
can tell the difference between a tab and eight spaces from a distance
|
can tell the difference between a tab and eight spaces from a distance
|
||||||
of approximately fifteen parsecs. Many a flamewar has been fought and
|
of approximately fifteen parsecs. Many a flamewar have been fought and
|
||||||
lost on this issue.
|
lost on this issue.
|
||||||
|
|
||||||
QEMU indents are four spaces. Tabs are never used, except in Makefiles
|
QEMU indents are four spaces. Tabs are never used, except in Makefiles
|
||||||
@@ -31,11 +31,7 @@ Do not leave whitespace dangling off the ends of lines.
|
|||||||
|
|
||||||
2. Line width
|
2. Line width
|
||||||
|
|
||||||
Lines should be 80 characters; try not to make them longer.
|
Lines are 80 characters; not longer.
|
||||||
|
|
||||||
Sometimes it is hard to do, especially when dealing with QEMU subsystems
|
|
||||||
that use long function or symbol names. Even in that case, do not make
|
|
||||||
lines much longer than 80 characters.
|
|
||||||
|
|
||||||
Rationale:
|
Rationale:
|
||||||
- Some people like to tile their 24" screens with a 6x4 matrix of 80x24
|
- Some people like to tile their 24" screens with a 6x4 matrix of 80x24
|
||||||
@@ -43,8 +39,6 @@ Rationale:
|
|||||||
let them keep doing it.
|
let them keep doing it.
|
||||||
- Code and especially patches is much more readable if limited to a sane
|
- Code and especially patches is much more readable if limited to a sane
|
||||||
line length. Eighty is traditional.
|
line length. Eighty is traditional.
|
||||||
- The four-space indentation makes the most common excuse ("But look
|
|
||||||
at all that white space on the left!") moot.
|
|
||||||
- It is the QEMU coding style.
|
- It is the QEMU coding style.
|
||||||
|
|
||||||
3. Naming
|
3. Naming
|
||||||
@@ -93,15 +87,10 @@ Furthermore, it is the QEMU coding style.
|
|||||||
|
|
||||||
5. Declarations
|
5. Declarations
|
||||||
|
|
||||||
Mixed declarations (interleaving statements and declarations within
|
Mixed declarations (interleaving statements and declarations within blocks)
|
||||||
blocks) are generally not allowed; declarations should be at the beginning
|
are not allowed; declarations should be at the beginning of blocks. In other
|
||||||
of blocks.
|
words, the code should not generate warnings if using GCC's
|
||||||
|
-Wdeclaration-after-statement option.
|
||||||
Every now and then, an exception is made for declarations inside a
|
|
||||||
#ifdef or #ifndef block: if the code looks nicer, such declarations can
|
|
||||||
be placed at the top of the block even if there are statements above.
|
|
||||||
On the other hand, however, it's often best to move that #ifdef/#ifndef
|
|
||||||
block to a separate function altogether.
|
|
||||||
|
|
||||||
6. Conditional statements
|
6. Conditional statements
|
||||||
|
|
||||||
@@ -116,10 +105,3 @@ if (a == 1) {
|
|||||||
Rationale: Yoda conditions (as in 'if (1 == a)') are awkward to read.
|
Rationale: Yoda conditions (as in 'if (1 == a)') are awkward to read.
|
||||||
Besides, good compilers already warn users when '==' is mis-typed as '=',
|
Besides, good compilers already warn users when '==' is mis-typed as '=',
|
||||||
even when the constant is on the right.
|
even when the constant is on the right.
|
||||||
|
|
||||||
7. Comment style
|
|
||||||
|
|
||||||
We use traditional C-style /* */ comments and avoid // comments.
|
|
||||||
|
|
||||||
Rationale: The // form is valid in C99, so this is purely a matter of
|
|
||||||
consistency of style. The checkpatch script will warn you about this.
|
|
||||||
|
|||||||
77
HACKING
77
HACKING
@@ -1,28 +1,10 @@
|
|||||||
1. Preprocessor
|
1. Preprocessor
|
||||||
|
|
||||||
1.1. Variadic macros
|
|
||||||
|
|
||||||
For variadic macros, stick with this C99-like syntax:
|
For variadic macros, stick with this C99-like syntax:
|
||||||
|
|
||||||
#define DPRINTF(fmt, ...) \
|
#define DPRINTF(fmt, ...) \
|
||||||
do { printf("IRQ: " fmt, ## __VA_ARGS__); } while (0)
|
do { printf("IRQ: " fmt, ## __VA_ARGS__); } while (0)
|
||||||
|
|
||||||
1.2. Include directives
|
|
||||||
|
|
||||||
Order include directives as follows:
|
|
||||||
|
|
||||||
#include "qemu/osdep.h" /* Always first... */
|
|
||||||
#include <...> /* then system headers... */
|
|
||||||
#include "..." /* and finally QEMU headers. */
|
|
||||||
|
|
||||||
The "qemu/osdep.h" header contains preprocessor macros that affect the behavior
|
|
||||||
of core system headers like <stdint.h>. It must be the first include so that
|
|
||||||
core system headers included by external libraries get the preprocessor macros
|
|
||||||
that QEMU depends on.
|
|
||||||
|
|
||||||
Do not include "qemu/osdep.h" from header files since the .c file will have
|
|
||||||
already included it.
|
|
||||||
|
|
||||||
2. C types
|
2. C types
|
||||||
|
|
||||||
It should be common sense to use the right type, but we have collected
|
It should be common sense to use the right type, but we have collected
|
||||||
@@ -175,62 +157,3 @@ painful. These are:
|
|||||||
* you may assume that integers are 2s complement representation
|
* you may assume that integers are 2s complement representation
|
||||||
* you may assume that right shift of a signed integer duplicates
|
* you may assume that right shift of a signed integer duplicates
|
||||||
the sign bit (ie it is an arithmetic shift, not a logical shift)
|
the sign bit (ie it is an arithmetic shift, not a logical shift)
|
||||||
|
|
||||||
In addition, QEMU assumes that the compiler does not use the latitude
|
|
||||||
given in C99 and C11 to treat aspects of signed '<<' as undefined, as
|
|
||||||
documented in the GNU Compiler Collection manual starting at version 4.0.
|
|
||||||
|
|
||||||
7. Error handling and reporting
|
|
||||||
|
|
||||||
7.1 Reporting errors to the human user
|
|
||||||
|
|
||||||
Do not use printf(), fprintf() or monitor_printf(). Instead, use
|
|
||||||
error_report() or error_vreport() from error-report.h. This ensures the
|
|
||||||
error is reported in the right place (current monitor or stderr), and in
|
|
||||||
a uniform format.
|
|
||||||
|
|
||||||
Use error_printf() & friends to print additional information.
|
|
||||||
|
|
||||||
error_report() prints the current location. In certain common cases
|
|
||||||
like command line parsing, the current location is tracked
|
|
||||||
automatically. To manipulate it manually, use the loc_*() from
|
|
||||||
error-report.h.
|
|
||||||
|
|
||||||
7.2 Propagating errors
|
|
||||||
|
|
||||||
An error can't always be reported to the user right where it's detected,
|
|
||||||
but often needs to be propagated up the call chain to a place that can
|
|
||||||
handle it. This can be done in various ways.
|
|
||||||
|
|
||||||
The most flexible one is Error objects. See error.h for usage
|
|
||||||
information.
|
|
||||||
|
|
||||||
Use the simplest suitable method to communicate success / failure to
|
|
||||||
callers. Stick to common methods: non-negative on success / -1 on
|
|
||||||
error, non-negative / -errno, non-null / null, or Error objects.
|
|
||||||
|
|
||||||
Example: when a function returns a non-null pointer on success, and it
|
|
||||||
can fail only in one way (as far as the caller is concerned), returning
|
|
||||||
null on failure is just fine, and certainly simpler and a lot easier on
|
|
||||||
the eyes than propagating an Error object through an Error ** parameter.
|
|
||||||
|
|
||||||
Example: when a function's callers need to report details on failure
|
|
||||||
only the function really knows, use Error **, and set suitable errors.
|
|
||||||
|
|
||||||
Do not report an error to the user when you're also returning an error
|
|
||||||
for somebody else to handle. Leave the reporting to the place that
|
|
||||||
consumes the error returned.
|
|
||||||
|
|
||||||
7.3 Handling errors
|
|
||||||
|
|
||||||
Calling exit() is fine when handling configuration errors during
|
|
||||||
startup. It's problematic during normal operation. In particular,
|
|
||||||
monitor commands should never exit().
|
|
||||||
|
|
||||||
Do not call exit() or abort() to handle an error that can be triggered
|
|
||||||
by the guest (e.g., some unimplemented corner case in guest code
|
|
||||||
translation or device emulation). Guests should not be able to
|
|
||||||
terminate QEMU.
|
|
||||||
|
|
||||||
Note that &error_fatal is just another way to exit(1), and &error_abort
|
|
||||||
is just another way to abort().
|
|
||||||
|
|||||||
1072
MAINTAINERS
1072
MAINTAINERS
File diff suppressed because it is too large
Load Diff
585
Makefile
585
Makefile
@@ -3,11 +3,6 @@
|
|||||||
# Always point to the root of the build tree (needs GNU make).
|
# Always point to the root of the build tree (needs GNU make).
|
||||||
BUILD_DIR=$(CURDIR)
|
BUILD_DIR=$(CURDIR)
|
||||||
|
|
||||||
# Before including a proper config-host.mak, assume we are in the source tree
|
|
||||||
SRC_PATH=.
|
|
||||||
|
|
||||||
UNCHECKED_GOALS := %clean TAGS cscope ctags docker docker-%
|
|
||||||
|
|
||||||
# All following code might depend on configuration variables
|
# All following code might depend on configuration variables
|
||||||
ifneq ($(wildcard config-host.mak),)
|
ifneq ($(wildcard config-host.mak),)
|
||||||
# Put the all: rule here so that config-host.mak can contain dependencies.
|
# Put the all: rule here so that config-host.mak can contain dependencies.
|
||||||
@@ -26,12 +21,12 @@ endif
|
|||||||
|
|
||||||
CONFIG_SOFTMMU := $(if $(filter %-softmmu,$(TARGET_DIRS)),y)
|
CONFIG_SOFTMMU := $(if $(filter %-softmmu,$(TARGET_DIRS)),y)
|
||||||
CONFIG_USER_ONLY := $(if $(filter %-user,$(TARGET_DIRS)),y)
|
CONFIG_USER_ONLY := $(if $(filter %-user,$(TARGET_DIRS)),y)
|
||||||
CONFIG_XEN := $(CONFIG_XEN_BACKEND)
|
|
||||||
CONFIG_ALL=y
|
CONFIG_ALL=y
|
||||||
-include config-all-devices.mak
|
-include config-all-devices.mak
|
||||||
-include config-all-disas.mak
|
-include config-all-disas.mak
|
||||||
|
|
||||||
config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios
|
include $(SRC_PATH)/rules.mak
|
||||||
|
config-host.mak: $(SRC_PATH)/configure
|
||||||
@echo $@ is out-of-date, running configure
|
@echo $@ is out-of-date, running configure
|
||||||
@# TODO: The next lines include code which supports a smooth
|
@# TODO: The next lines include code which supports a smooth
|
||||||
@# transition from old configurations without config.status.
|
@# transition from old configurations without config.status.
|
||||||
@@ -43,161 +38,43 @@ config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
config-host.mak:
|
config-host.mak:
|
||||||
ifneq ($(filter-out $(UNCHECKED_GOALS),$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
||||||
@echo "Please call configure before running make!"
|
@echo "Please call configure before running make!"
|
||||||
@exit 1
|
@exit 1
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
include $(SRC_PATH)/rules.mak
|
GENERATED_HEADERS = config-host.h qemu-options.def
|
||||||
|
GENERATED_HEADERS += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
|
||||||
|
GENERATED_SOURCES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
|
||||||
|
|
||||||
GENERATED_FILES = qemu-version.h config-host.h qemu-options.def
|
GENERATED_HEADERS += trace/generated-events.h
|
||||||
GENERATED_FILES += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
|
GENERATED_SOURCES += trace/generated-events.c
|
||||||
GENERATED_FILES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
|
|
||||||
GENERATED_FILES += qmp-introspect.h
|
|
||||||
GENERATED_FILES += qmp-introspect.c
|
|
||||||
|
|
||||||
GENERATED_FILES += trace/generated-tcg-tracers.h
|
GENERATED_HEADERS += trace/generated-tracers.h
|
||||||
|
ifeq ($(findstring dtrace,$(TRACE_BACKENDS)),dtrace)
|
||||||
GENERATED_FILES += trace/generated-helpers-wrappers.h
|
GENERATED_HEADERS += trace/generated-tracers-dtrace.h
|
||||||
GENERATED_FILES += trace/generated-helpers.h
|
|
||||||
GENERATED_FILES += trace/generated-helpers.c
|
|
||||||
|
|
||||||
ifdef CONFIG_TRACE_UST
|
|
||||||
GENERATED_FILES += trace-ust-all.h
|
|
||||||
GENERATED_FILES += trace-ust-all.c
|
|
||||||
endif
|
endif
|
||||||
|
GENERATED_SOURCES += trace/generated-tracers.c
|
||||||
|
|
||||||
GENERATED_FILES += module_block.h
|
GENERATED_HEADERS += trace/generated-tcg-tracers.h
|
||||||
|
|
||||||
TRACE_HEADERS = trace-root.h $(trace-events-subdirs:%=%/trace.h)
|
GENERATED_HEADERS += trace/generated-helpers-wrappers.h
|
||||||
TRACE_SOURCES = trace-root.c $(trace-events-subdirs:%=%/trace.c)
|
GENERATED_HEADERS += trace/generated-helpers.h
|
||||||
TRACE_DTRACE =
|
GENERATED_SOURCES += trace/generated-helpers.c
|
||||||
ifdef CONFIG_TRACE_DTRACE
|
|
||||||
TRACE_HEADERS += trace-dtrace-root.h $(trace-events-subdirs:%=%/trace-dtrace.h)
|
ifeq ($(findstring ust,$(TRACE_BACKENDS)),ust)
|
||||||
TRACE_DTRACE += trace-dtrace-root.dtrace $(trace-events-subdirs:%=%/trace-dtrace.dtrace)
|
GENERATED_HEADERS += trace/generated-ust-provider.h
|
||||||
|
GENERATED_SOURCES += trace/generated-ust.c
|
||||||
endif
|
endif
|
||||||
ifdef CONFIG_TRACE_UST
|
|
||||||
TRACE_HEADERS += trace-ust-root.h $(trace-events-subdirs:%=%/trace-ust.h)
|
|
||||||
endif
|
|
||||||
|
|
||||||
GENERATED_FILES += $(TRACE_HEADERS)
|
|
||||||
GENERATED_FILES += $(TRACE_SOURCES)
|
|
||||||
GENERATED_FILES += $(BUILD_DIR)/trace-events-all
|
|
||||||
|
|
||||||
trace-group-name = $(shell dirname $1 | sed -e 's/[^a-zA-Z0-9]/_/g')
|
|
||||||
|
|
||||||
tracetool-y = $(SRC_PATH)/scripts/tracetool.py
|
|
||||||
tracetool-y += $(shell find $(SRC_PATH)/scripts/tracetool -name "*.py")
|
|
||||||
|
|
||||||
%/trace.h: %/trace.h-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
%/trace.h-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=$(call trace-group-name,$@) \
|
|
||||||
--format=h \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
%/trace.c: %/trace.c-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
%/trace.c-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=$(call trace-group-name,$@) \
|
|
||||||
--format=c \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
%/trace-ust.h: %/trace-ust.h-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
%/trace-ust.h-timestamp: $(SRC_PATH)/%/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=$(call trace-group-name,$@) \
|
|
||||||
--format=ust-events-h \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
%/trace-dtrace.dtrace: %/trace-dtrace.dtrace-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
%/trace-dtrace.dtrace-timestamp: $(SRC_PATH)/%/trace-events $(BUILD_DIR)/config-host.mak $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=$(call trace-group-name,$@) \
|
|
||||||
--format=d \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
%/trace-dtrace.h: %/trace-dtrace.dtrace $(tracetool-y)
|
|
||||||
$(call quiet-command,dtrace -o $@ -h -s $<, "GEN","$@")
|
|
||||||
|
|
||||||
%/trace-dtrace.o: %/trace-dtrace.dtrace $(tracetool-y)
|
|
||||||
|
|
||||||
|
|
||||||
trace-root.h: trace-root.h-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-root.h-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=root \
|
|
||||||
--format=h \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-root.c: trace-root.c-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-root.c-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=root \
|
|
||||||
--format=c \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-ust-root.h: trace-ust-root.h-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-ust-root.h-timestamp: $(SRC_PATH)/trace-events $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=root \
|
|
||||||
--format=ust-events-h \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-ust-all.h: trace-ust-all.h-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-ust-all.h-timestamp: $(trace-events-files) $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=all \
|
|
||||||
--format=ust-events-h \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$(trace-events-files) > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-ust-all.c: trace-ust-all.c-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-ust-all.c-timestamp: $(trace-events-files) $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=all \
|
|
||||||
--format=ust-events-c \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$(trace-events-files) > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-dtrace-root.dtrace: trace-dtrace-root.dtrace-timestamp
|
|
||||||
@cmp $< $@ >/dev/null 2>&1 || cp $< $@
|
|
||||||
trace-dtrace-root.dtrace-timestamp: $(SRC_PATH)/trace-events $(BUILD_DIR)/config-host.mak $(tracetool-y)
|
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
|
||||||
--group=root \
|
|
||||||
--format=d \
|
|
||||||
--backends=$(TRACE_BACKENDS) \
|
|
||||||
$< > $@,"GEN","$(@:%-timestamp=%)")
|
|
||||||
|
|
||||||
trace-dtrace-root.h: trace-dtrace-root.dtrace
|
|
||||||
$(call quiet-command,dtrace -o $@ -h -s $<, "GEN","$@")
|
|
||||||
|
|
||||||
trace-dtrace-root.o: trace-dtrace-root.dtrace
|
|
||||||
|
|
||||||
# Don't try to regenerate Makefile or configure
|
# Don't try to regenerate Makefile or configure
|
||||||
# We don't generate any of them
|
# We don't generate any of them
|
||||||
Makefile: ;
|
Makefile: ;
|
||||||
configure: ;
|
configure: ;
|
||||||
|
|
||||||
.PHONY: all clean cscope distclean html info install install-doc \
|
.PHONY: all clean cscope distclean dvi html info install install-doc \
|
||||||
pdf txt recurse-all speed test dist msi FORCE
|
pdf recurse-all speed test dist
|
||||||
|
|
||||||
$(call set-vpath, $(SRC_PATH))
|
$(call set-vpath, $(SRC_PATH))
|
||||||
|
|
||||||
@@ -206,9 +83,10 @@ LIBS+=-lz $(LIBS_TOOLS)
|
|||||||
HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
|
HELPERS-$(CONFIG_LINUX) = qemu-bridge-helper$(EXESUF)
|
||||||
|
|
||||||
ifdef BUILD_DOCS
|
ifdef BUILD_DOCS
|
||||||
DOCS=qemu-doc.html qemu-doc.txt qemu.1 qemu-img.1 qemu-nbd.8 qemu-ga.8
|
DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 qmp-commands.txt
|
||||||
DOCS+=docs/interop/qemu-qmp-ref.html docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7
|
ifdef CONFIG_LINUX
|
||||||
DOCS+=docs/interop/qemu-ga-ref.html docs/interop/qemu-ga-ref.txt docs/interop/qemu-ga-ref.7
|
DOCS+=kvm_stat.1
|
||||||
|
endif
|
||||||
ifdef CONFIG_VIRTFS
|
ifdef CONFIG_VIRTFS
|
||||||
DOCS+=fsdev/virtfs-proxy-helper.1
|
DOCS+=fsdev/virtfs-proxy-helper.1
|
||||||
endif
|
endif
|
||||||
@@ -222,20 +100,20 @@ SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %-config-devices.mak.d, $(TARGET_DIRS))
|
|||||||
|
|
||||||
ifeq ($(SUBDIR_DEVICES_MAK),)
|
ifeq ($(SUBDIR_DEVICES_MAK),)
|
||||||
config-all-devices.mak:
|
config-all-devices.mak:
|
||||||
$(call quiet-command,echo '# no devices' > $@,"GEN","$@")
|
$(call quiet-command,echo '# no devices' > $@," GEN $@")
|
||||||
else
|
else
|
||||||
config-all-devices.mak: $(SUBDIR_DEVICES_MAK)
|
config-all-devices.mak: $(SUBDIR_DEVICES_MAK)
|
||||||
$(call quiet-command, sed -n \
|
$(call quiet-command, sed -n \
|
||||||
's|^\([^=]*\)=\(.*\)$$|\1:=$$(findstring y,$$(\1)\2)|p' \
|
's|^\([^=]*\)=\(.*\)$$|\1:=$$(findstring y,$$(\1)\2)|p' \
|
||||||
$(SUBDIR_DEVICES_MAK) | sort -u > $@, \
|
$(SUBDIR_DEVICES_MAK) | sort -u > $@, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
endif
|
endif
|
||||||
|
|
||||||
-include $(SUBDIR_DEVICES_MAK_DEP)
|
-include $(SUBDIR_DEVICES_MAK_DEP)
|
||||||
|
|
||||||
%/config-devices.mak: default-configs/%.mak $(SRC_PATH)/scripts/make_device_config.sh
|
%/config-devices.mak: default-configs/%.mak
|
||||||
$(call quiet-command, \
|
$(call quiet-command, \
|
||||||
$(SHELL) $(SRC_PATH)/scripts/make_device_config.sh $< $*-config-devices.mak.d $@ > $@.tmp,"GEN","$@.tmp")
|
$(SHELL) $(SRC_PATH)/scripts/make_device_config.sh $< $*-config-devices.mak.d $@ > $@.tmp, " GEN $@.tmp")
|
||||||
$(call quiet-command, if test -f $@; then \
|
$(call quiet-command, if test -f $@; then \
|
||||||
if cmp -s $@.old $@; then \
|
if cmp -s $@.old $@; then \
|
||||||
mv $@.tmp $@; \
|
mv $@.tmp $@; \
|
||||||
@@ -252,7 +130,7 @@ endif
|
|||||||
else \
|
else \
|
||||||
mv $@.tmp $@; \
|
mv $@.tmp $@; \
|
||||||
cp -p $@ $@.old; \
|
cp -p $@ $@.old; \
|
||||||
fi,"GEN","$@");
|
fi, " GEN $@");
|
||||||
|
|
||||||
defconfig:
|
defconfig:
|
||||||
rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK)
|
rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK)
|
||||||
@@ -263,65 +141,32 @@ endif
|
|||||||
|
|
||||||
dummy := $(call unnest-vars,, \
|
dummy := $(call unnest-vars,, \
|
||||||
stub-obj-y \
|
stub-obj-y \
|
||||||
chardev-obj-y \
|
|
||||||
util-obj-y \
|
util-obj-y \
|
||||||
qga-obj-y \
|
qga-obj-y \
|
||||||
ivshmem-client-obj-y \
|
|
||||||
ivshmem-server-obj-y \
|
|
||||||
libvhost-user-obj-y \
|
|
||||||
vhost-user-scsi-obj-y \
|
|
||||||
qga-vss-dll-obj-y \
|
qga-vss-dll-obj-y \
|
||||||
block-obj-y \
|
block-obj-y \
|
||||||
block-obj-m \
|
block-obj-m \
|
||||||
crypto-obj-y \
|
|
||||||
crypto-aes-obj-y \
|
|
||||||
qom-obj-y \
|
|
||||||
io-obj-y \
|
|
||||||
common-obj-y \
|
common-obj-y \
|
||||||
common-obj-m \
|
common-obj-m)
|
||||||
trace-obj-y)
|
|
||||||
|
|
||||||
ifneq ($(wildcard config-host.mak),)
|
ifneq ($(wildcard config-host.mak),)
|
||||||
include $(SRC_PATH)/tests/Makefile.include
|
include $(SRC_PATH)/tests/Makefile
|
||||||
|
endif
|
||||||
|
ifeq ($(CONFIG_SMARTCARD_NSS),y)
|
||||||
|
include $(SRC_PATH)/libcacard/Makefile
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
|
all: $(DOCS) $(TOOLS) $(HELPERS-y) recurse-all modules
|
||||||
|
|
||||||
qemu-version.h: FORCE
|
|
||||||
$(call quiet-command, \
|
|
||||||
(cd $(SRC_PATH); \
|
|
||||||
printf '#define QEMU_PKGVERSION '; \
|
|
||||||
if test -n "$(PKGVERSION)"; then \
|
|
||||||
printf '"$(PKGVERSION)"\n'; \
|
|
||||||
else \
|
|
||||||
if test -d .git; then \
|
|
||||||
printf '" ('; \
|
|
||||||
git describe --match 'v*' 2>/dev/null | tr -d '\n'; \
|
|
||||||
if ! git diff-index --quiet HEAD &>/dev/null; then \
|
|
||||||
printf -- '-dirty'; \
|
|
||||||
fi; \
|
|
||||||
printf ')"\n'; \
|
|
||||||
else \
|
|
||||||
printf '""\n'; \
|
|
||||||
fi; \
|
|
||||||
fi) > $@.tmp)
|
|
||||||
$(call quiet-command, if ! cmp -s $@ $@.tmp; then \
|
|
||||||
mv $@.tmp $@; \
|
|
||||||
else \
|
|
||||||
rm $@.tmp; \
|
|
||||||
fi)
|
|
||||||
|
|
||||||
config-host.h: config-host.h-timestamp
|
config-host.h: config-host.h-timestamp
|
||||||
config-host.h-timestamp: config-host.mak
|
config-host.h-timestamp: config-host.mak
|
||||||
qemu-options.def: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
|
qemu-options.def: $(SRC_PATH)/qemu-options.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@")
|
||||||
|
|
||||||
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
|
||||||
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
|
||||||
|
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
$(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
|
|
||||||
$(SOFTMMU_SUBDIR_RULES): $(io-obj-y)
|
|
||||||
$(SOFTMMU_SUBDIR_RULES): config-all-devices.mak
|
$(SOFTMMU_SUBDIR_RULES): config-all-devices.mak
|
||||||
|
|
||||||
subdir-%:
|
subdir-%:
|
||||||
@@ -346,46 +191,47 @@ subdir-dtc:dtc/libfdt dtc/tests
|
|||||||
dtc/%:
|
dtc/%:
|
||||||
mkdir -p $@
|
mkdir -p $@
|
||||||
|
|
||||||
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y) $(chardev-obj-y) \
|
$(SUBDIR_RULES): libqemuutil.a libqemustub.a $(common-obj-y)
|
||||||
$(qom-obj-y) $(crypto-aes-obj-$(CONFIG_USER_ONLY))
|
|
||||||
|
|
||||||
ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS))
|
ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS))
|
||||||
# Only keep -O and -g cflags
|
|
||||||
romsubdir-%:
|
romsubdir-%:
|
||||||
$(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C pc-bios/$* V="$(V)" TARGET_DIR="$*/" CFLAGS="$(filter -O% -g%,$(CFLAGS))",)
|
$(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C pc-bios/$* V="$(V)" TARGET_DIR="$*/",)
|
||||||
|
|
||||||
ALL_SUBDIRS=$(TARGET_DIRS) $(patsubst %,pc-bios/%, $(ROMS))
|
ALL_SUBDIRS=$(TARGET_DIRS) $(patsubst %,pc-bios/%, $(ROMS))
|
||||||
|
|
||||||
recurse-all: $(SUBDIR_RULES) $(ROMSUBDIR_RULES)
|
recurse-all: $(SUBDIR_RULES) $(ROMSUBDIR_RULES)
|
||||||
|
|
||||||
$(BUILD_DIR)/version.o: $(SRC_PATH)/version.rc config-host.h
|
$(BUILD_DIR)/version.o: $(SRC_PATH)/version.rc config-host.h | $(BUILD_DIR)/version.lo
|
||||||
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<,"RC","version.o")
|
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.o")
|
||||||
|
$(BUILD_DIR)/version.lo: $(SRC_PATH)/version.rc config-host.h
|
||||||
|
$(call quiet-command,$(WINDRES) -I$(BUILD_DIR) -o $@ $<," RC version.lo")
|
||||||
|
|
||||||
Makefile: $(version-obj-y)
|
Makefile: $(version-obj-y) $(version-lobj-y)
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Build libraries
|
# Build libraries
|
||||||
|
|
||||||
libqemustub.a: $(stub-obj-y)
|
libqemustub.a: $(stub-obj-y)
|
||||||
libqemuutil.a: $(util-obj-y) $(trace-obj-y)
|
libqemuutil.a: $(util-obj-y)
|
||||||
|
|
||||||
|
block-modules = $(foreach o,$(block-obj-m),"$(basename $(subst /,-,$o))",) NULL
|
||||||
|
util/module.o-cflags = -D'CONFIG_BLOCK_MODULES=$(block-modules)'
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
COMMON_LDADDS = libqemuutil.a libqemustub.a
|
|
||||||
|
|
||||||
qemu-img.o: qemu-img-cmds.h
|
qemu-img.o: qemu-img-cmds.h
|
||||||
|
|
||||||
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
|
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) libqemuutil.a libqemustub.a
|
||||||
|
|
||||||
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
|
qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o
|
||||||
|
|
||||||
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
|
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/virtio-9p-marshal.o libqemuutil.a libqemustub.a
|
||||||
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
|
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
|
||||||
|
|
||||||
qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
|
qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@")
|
||||||
|
|
||||||
qemu-ga$(EXESUF): LIBS = $(LIBS_QGA)
|
qemu-ga$(EXESUF): LIBS = $(LIBS_QGA)
|
||||||
qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
|
qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
|
||||||
@@ -397,112 +243,70 @@ qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
|
|||||||
qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
|
qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
|
||||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
||||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
$(gen-out-type) -o qga/qapi-generated -p "qga-" -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
|
qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
|
||||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
||||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
$(gen-out-type) -o qga/qapi-generated -p "qga-" -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
|
qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
|
||||||
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
||||||
$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
|
$(gen-out-type) -o qga/qapi-generated -p "qga-" -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
|
|
||||||
qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
|
qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
|
||||||
$(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
|
$(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
|
||||||
$(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json \
|
$(SRC_PATH)/qapi/event.json
|
||||||
$(SRC_PATH)/qapi/crypto.json $(SRC_PATH)/qapi/rocker.json \
|
|
||||||
$(SRC_PATH)/qapi/trace.json
|
|
||||||
|
|
||||||
qapi-types.c qapi-types.h :\
|
qapi-types.c qapi-types.h :\
|
||||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
|
||||||
$(gen-out-type) -o "." -b $<, \
|
$(gen-out-type) -o "." -b -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qapi-visit.c qapi-visit.h :\
|
qapi-visit.c qapi-visit.h :\
|
||||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
|
||||||
$(gen-out-type) -o "." -b $<, \
|
$(gen-out-type) -o "." -b -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qapi-event.c qapi-event.h :\
|
qapi-event.c qapi-event.h :\
|
||||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
|
$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
|
||||||
$(gen-out-type) -o "." $<, \
|
$(gen-out-type) -o "." -b -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qmp-commands.h qmp-marshal.c :\
|
qmp-commands.h qmp-marshal.c :\
|
||||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
|
||||||
$(gen-out-type) -o "." $<, \
|
$(gen-out-type) -o "." -m -i $<, \
|
||||||
"GEN","$@")
|
" GEN $@")
|
||||||
qmp-introspect.h qmp-introspect.c :\
|
|
||||||
$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
|
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
|
|
||||||
$(gen-out-type) -o "." $<, \
|
|
||||||
"GEN","$@")
|
|
||||||
|
|
||||||
QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
|
QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
|
||||||
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN)
|
$(qga-obj-y) qemu-ga.o: $(QGALIB_GEN)
|
||||||
|
|
||||||
qemu-ga$(EXESUF): $(qga-obj-y) $(COMMON_LDADDS)
|
qemu-ga$(EXESUF): $(qga-obj-y) libqemuutil.a libqemustub.a
|
||||||
$(call LINK, $^)
|
$(call LINK, $^)
|
||||||
|
|
||||||
ifdef QEMU_GA_MSI_ENABLED
|
|
||||||
QEMU_GA_MSI=qemu-ga-$(ARCH).msi
|
|
||||||
|
|
||||||
msi: $(QEMU_GA_MSI)
|
|
||||||
|
|
||||||
$(QEMU_GA_MSI): qemu-ga.exe $(QGA_VSS_PROVIDER)
|
|
||||||
|
|
||||||
$(QEMU_GA_MSI): config-host.mak
|
|
||||||
|
|
||||||
$(QEMU_GA_MSI): $(SRC_PATH)/qga/installer/qemu-ga.wxs
|
|
||||||
$(call quiet-command,QEMU_GA_VERSION="$(QEMU_GA_VERSION)" QEMU_GA_MANUFACTURER="$(QEMU_GA_MANUFACTURER)" QEMU_GA_DISTRO="$(QEMU_GA_DISTRO)" BUILD_DIR="$(BUILD_DIR)" \
|
|
||||||
wixl -o $@ $(QEMU_GA_MSI_ARCH) $(QEMU_GA_MSI_WITH_VSS) $(QEMU_GA_MSI_MINGW_DLL_PATH) $<,"WIXL","$@")
|
|
||||||
else
|
|
||||||
msi:
|
|
||||||
@echo "MSI build not configured or dependency resolution failed (reconfigure with --enable-guest-agent-msi option)"
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq ($(EXESUF),)
|
|
||||||
.PHONY: qemu-ga
|
|
||||||
qemu-ga: qemu-ga$(EXESUF) $(QGA_VSS_PROVIDER) $(QEMU_GA_MSI)
|
|
||||||
endif
|
|
||||||
|
|
||||||
ivshmem-client$(EXESUF): $(ivshmem-client-obj-y) $(COMMON_LDADDS)
|
|
||||||
$(call LINK, $^)
|
|
||||||
ivshmem-server$(EXESUF): $(ivshmem-server-obj-y) $(COMMON_LDADDS)
|
|
||||||
$(call LINK, $^)
|
|
||||||
vhost-user-scsi$(EXESUF): $(vhost-user-scsi-obj-y)
|
|
||||||
$(call LINK, $^)
|
|
||||||
|
|
||||||
module_block.h: $(SRC_PATH)/scripts/modules/module_block.py config-host.mak
|
|
||||||
$(call quiet-command,$(PYTHON) $< $@ \
|
|
||||||
$(addprefix $(SRC_PATH)/,$(patsubst %.mo,%.c,$(block-obj-m))), \
|
|
||||||
"GEN","$@")
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
# avoid old build problems by removing potentially incorrect old files
|
# avoid old build problems by removing potentially incorrect old files
|
||||||
rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
|
rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h
|
||||||
rm -f qemu-options.def
|
rm -f qemu-options.def
|
||||||
rm -f *.msi
|
find . \( -name '*.l[oa]' -o -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} +
|
||||||
find . \( -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} +
|
|
||||||
rm -f $(filter-out %.tlb,$(TOOLS)) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~
|
rm -f $(filter-out %.tlb,$(TOOLS)) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~
|
||||||
rm -f fsdev/*.pod
|
rm -f fsdev/*.pod
|
||||||
|
rm -rf .libs */.libs
|
||||||
rm -f qemu-img-cmds.h
|
rm -f qemu-img-cmds.h
|
||||||
rm -f ui/shader/*-vert.h ui/shader/*-frag.h
|
@# May not be present in GENERATED_HEADERS
|
||||||
@# May not be present in GENERATED_FILES
|
|
||||||
rm -f trace/generated-tracers-dtrace.dtrace*
|
rm -f trace/generated-tracers-dtrace.dtrace*
|
||||||
rm -f trace/generated-tracers-dtrace.h*
|
rm -f trace/generated-tracers-dtrace.h*
|
||||||
rm -f $(foreach f,$(GENERATED_FILES),$(f) $(f)-timestamp)
|
rm -f $(foreach f,$(GENERATED_HEADERS),$(f) $(f)-timestamp)
|
||||||
|
rm -f $(foreach f,$(GENERATED_SOURCES),$(f) $(f)-timestamp)
|
||||||
rm -rf qapi-generated
|
rm -rf qapi-generated
|
||||||
rm -rf qga/qapi-generated
|
rm -rf qga/qapi-generated
|
||||||
for d in $(ALL_SUBDIRS); do \
|
for d in $(ALL_SUBDIRS); do \
|
||||||
if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \
|
if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \
|
||||||
rm -f $$d/qemu-options.def; \
|
rm -f $$d/qemu-options.def; \
|
||||||
done
|
done
|
||||||
rm -f $(SUBDIR_DEVICES_MAK) config-all-devices.mak
|
|
||||||
|
|
||||||
VERSION ?= $(shell cat VERSION)
|
VERSION ?= $(shell cat VERSION)
|
||||||
|
|
||||||
@@ -512,22 +316,17 @@ qemu-%.tar.bz2:
|
|||||||
$(SRC_PATH)/scripts/make-release "$(SRC_PATH)" "$(patsubst qemu-%.tar.bz2,%,$@)"
|
$(SRC_PATH)/scripts/make-release "$(SRC_PATH)" "$(patsubst qemu-%.tar.bz2,%,$@)"
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi qemu-monitor-info.texi
|
rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi
|
||||||
rm -f config-all-devices.mak config-all-disas.mak config.status
|
rm -f config-all-devices.mak config-all-disas.mak config.status
|
||||||
rm -f po/*.mo tests/qemu-iotests/common.env
|
rm -f po/*.mo tests/qemu-iotests/common.env
|
||||||
rm -f roms/seabios/config.mak roms/vgabios/config.mak
|
rm -f roms/seabios/config.mak roms/vgabios/config.mak
|
||||||
rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps
|
rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.cps qemu-doc.dvi
|
||||||
rm -f qemu-doc.fn qemu-doc.fns qemu-doc.info qemu-doc.ky qemu-doc.kys
|
rm -f qemu-doc.fn qemu-doc.fns qemu-doc.info qemu-doc.ky qemu-doc.kys
|
||||||
rm -f qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp
|
rm -f qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp
|
||||||
rm -f qemu-doc.vr qemu-doc.txt
|
rm -f qemu-doc.vr
|
||||||
rm -f config.log
|
rm -f config.log
|
||||||
rm -f linux-headers/asm
|
rm -f linux-headers/asm
|
||||||
rm -f docs/version.texi
|
rm -f qemu-tech.info qemu-tech.aux qemu-tech.cp qemu-tech.dvi qemu-tech.fn qemu-tech.info qemu-tech.ky qemu-tech.log qemu-tech.pdf qemu-tech.pg qemu-tech.toc qemu-tech.tp qemu-tech.vr
|
||||||
rm -f docs/interop/qemu-ga-qapi.texi docs/interop/qemu-qmp-qapi.texi
|
|
||||||
rm -f docs/interop/qemu-qmp-ref.7 docs/interop/qemu-ga-ref.7
|
|
||||||
rm -f docs/interop/qemu-qmp-ref.txt docs/interop/qemu-ga-ref.txt
|
|
||||||
rm -f docs/interop/qemu-qmp-ref.pdf docs/interop/qemu-ga-ref.pdf
|
|
||||||
rm -f docs/interop/qemu-qmp-ref.html docs/interop/qemu-ga-ref.html
|
|
||||||
for d in $(TARGET_DIRS); do \
|
for d in $(TARGET_DIRS); do \
|
||||||
rm -rf $$d || exit 1 ; \
|
rm -rf $$d || exit 1 ; \
|
||||||
done
|
done
|
||||||
@@ -542,48 +341,37 @@ bepo cz
|
|||||||
|
|
||||||
ifdef INSTALL_BLOBS
|
ifdef INSTALL_BLOBS
|
||||||
BLOBS=bios.bin bios-256k.bin sgabios.bin vgabios.bin vgabios-cirrus.bin \
|
BLOBS=bios.bin bios-256k.bin sgabios.bin vgabios.bin vgabios-cirrus.bin \
|
||||||
vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin vgabios-virtio.bin \
|
vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin \
|
||||||
acpi-dsdt.aml \
|
acpi-dsdt.aml q35-acpi-dsdt.aml \
|
||||||
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc QEMU,tcx.bin QEMU,cgthree.bin \
|
ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc QEMU,tcx.bin QEMU,cgthree.bin \
|
||||||
pxe-e1000.rom pxe-eepro100.rom pxe-ne2k_pci.rom \
|
pxe-e1000.rom pxe-eepro100.rom pxe-ne2k_pci.rom \
|
||||||
pxe-pcnet.rom pxe-rtl8139.rom pxe-virtio.rom \
|
pxe-pcnet.rom pxe-rtl8139.rom pxe-virtio.rom \
|
||||||
efi-e1000.rom efi-eepro100.rom efi-ne2k_pci.rom \
|
efi-e1000.rom efi-eepro100.rom efi-ne2k_pci.rom \
|
||||||
efi-pcnet.rom efi-rtl8139.rom efi-virtio.rom \
|
efi-pcnet.rom efi-rtl8139.rom efi-virtio.rom \
|
||||||
efi-e1000e.rom efi-vmxnet3.rom \
|
|
||||||
qemu-icon.bmp qemu_logo_no_text.svg \
|
qemu-icon.bmp qemu_logo_no_text.svg \
|
||||||
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
|
bamboo.dtb petalogix-s3adsp1800.dtb petalogix-ml605.dtb \
|
||||||
multiboot.bin linuxboot.bin linuxboot_dma.bin kvmvapic.bin \
|
multiboot.bin linuxboot.bin kvmvapic.bin \
|
||||||
|
s390-zipl.rom \
|
||||||
s390-ccw.img \
|
s390-ccw.img \
|
||||||
spapr-rtas.bin slof.bin skiboot.lid \
|
spapr-rtas.bin slof.bin \
|
||||||
palcode-clipper \
|
palcode-clipper \
|
||||||
u-boot.e500 \
|
u-boot.e500
|
||||||
qemu_vga.ndrv
|
|
||||||
else
|
else
|
||||||
BLOBS=
|
BLOBS=
|
||||||
endif
|
endif
|
||||||
|
|
||||||
install-doc: $(DOCS)
|
install-doc: $(DOCS)
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_docdir)"
|
||||||
$(INSTALL_DATA) qemu-doc.html "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DATA) qemu-doc.html qemu-tech.html "$(DESTDIR)$(qemu_docdir)"
|
||||||
$(INSTALL_DATA) qemu-doc.txt "$(DESTDIR)$(qemu_docdir)"
|
$(INSTALL_DATA) qmp-commands.txt "$(DESTDIR)$(qemu_docdir)"
|
||||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.html "$(DESTDIR)$(qemu_docdir)"
|
|
||||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.txt "$(DESTDIR)$(qemu_docdir)"
|
|
||||||
ifdef CONFIG_POSIX
|
ifdef CONFIG_POSIX
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
||||||
$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DATA) qemu.1 "$(DESTDIR)$(mandir)/man1"
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man7"
|
|
||||||
$(INSTALL_DATA) docs/interop/qemu-qmp-ref.7 "$(DESTDIR)$(mandir)/man7"
|
|
||||||
ifneq ($(TOOLS),)
|
ifneq ($(TOOLS),)
|
||||||
$(INSTALL_DATA) qemu-img.1 "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DATA) qemu-img.1 "$(DESTDIR)$(mandir)/man1"
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
|
||||||
$(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8"
|
$(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8"
|
||||||
endif
|
endif
|
||||||
ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
|
||||||
$(INSTALL_DATA) qemu-ga.8 "$(DESTDIR)$(mandir)/man8"
|
|
||||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.html "$(DESTDIR)$(qemu_docdir)"
|
|
||||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.txt "$(DESTDIR)$(qemu_docdir)"
|
|
||||||
$(INSTALL_DATA) docs/interop/qemu-ga-ref.7 "$(DESTDIR)$(mandir)/man7"
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
ifdef CONFIG_VIRTFS
|
ifdef CONFIG_VIRTFS
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
|
||||||
@@ -600,10 +388,16 @@ ifneq (,$(findstring qemu-ga,$(TOOLS)))
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
install-confdir:
|
||||||
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_confdir)"
|
||||||
|
|
||||||
install: all $(if $(BUILD_DOCS),install-doc) install-datadir install-localstatedir
|
install-sysconfig: install-datadir install-confdir
|
||||||
|
$(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(qemu_confdir)"
|
||||||
|
|
||||||
|
install: all $(if $(BUILD_DOCS),install-doc) install-sysconfig \
|
||||||
|
install-datadir install-localstatedir
|
||||||
ifneq ($(TOOLS),)
|
ifneq ($(TOOLS),)
|
||||||
$(call install-prog,$(subst qemu-ga,qemu-ga$(EXESUF),$(TOOLS)),$(DESTDIR)$(bindir))
|
$(call install-prog,$(TOOLS),$(DESTDIR)$(bindir))
|
||||||
endif
|
endif
|
||||||
ifneq ($(CONFIG_MODULES),)
|
ifneq ($(CONFIG_MODULES),)
|
||||||
$(INSTALL_DIR) "$(DESTDIR)$(qemu_moddir)"
|
$(INSTALL_DIR) "$(DESTDIR)$(qemu_moddir)"
|
||||||
@@ -628,7 +422,7 @@ endif
|
|||||||
set -e; for x in $(KEYMAPS); do \
|
set -e; for x in $(KEYMAPS); do \
|
||||||
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/keymaps/$$x "$(DESTDIR)$(qemu_datadir)/keymaps"; \
|
$(INSTALL_DATA) $(SRC_PATH)/pc-bios/keymaps/$$x "$(DESTDIR)$(qemu_datadir)/keymaps"; \
|
||||||
done
|
done
|
||||||
$(INSTALL_DATA) $(BUILD_DIR)/trace-events-all "$(DESTDIR)$(qemu_datadir)/trace-events-all"
|
$(INSTALL_DATA) $(SRC_PATH)/trace-events "$(DESTDIR)$(qemu_datadir)/trace-events"
|
||||||
for d in $(TARGET_DIRS); do \
|
for d in $(TARGET_DIRS); do \
|
||||||
$(MAKE) $(SUBDIR_MAKEFLAGS) TARGET_DIR=$$d/ -C $$d $@ || exit 1 ; \
|
$(MAKE) $(SUBDIR_MAKEFLAGS) TARGET_DIR=$$d/ -C $$d $@ || exit 1 ; \
|
||||||
done
|
done
|
||||||
@@ -637,108 +431,83 @@ endif
|
|||||||
test speed: all
|
test speed: all
|
||||||
$(MAKE) -C tests/tcg $@
|
$(MAKE) -C tests/tcg $@
|
||||||
|
|
||||||
.PHONY: ctags
|
|
||||||
ctags:
|
|
||||||
rm -f tags
|
|
||||||
find "$(SRC_PATH)" -name '*.[hc]' -exec ctags --append {} +
|
|
||||||
|
|
||||||
.PHONY: TAGS
|
.PHONY: TAGS
|
||||||
TAGS:
|
TAGS:
|
||||||
rm -f TAGS
|
rm -f $@
|
||||||
find "$(SRC_PATH)" -name '*.[hc]' -exec etags --append {} +
|
find "$(SRC_PATH)" -name '*.[hc]' -exec etags --append {} +
|
||||||
|
|
||||||
cscope:
|
cscope:
|
||||||
rm -f "$(SRC_PATH)"/cscope.*
|
rm -f ./cscope.*
|
||||||
find "$(SRC_PATH)/" -name "*.[chsS]" -print | sed 's,^\./,,' > "$(SRC_PATH)/cscope.files"
|
find "$(SRC_PATH)" -name "*.[chsS]" -print | sed 's,^\./,,' > ./cscope.files
|
||||||
cscope -b -i"$(SRC_PATH)/cscope.files"
|
cscope -b
|
||||||
|
|
||||||
# opengl shader programs
|
|
||||||
ui/shader/%-vert.h: $(SRC_PATH)/ui/shader/%.vert $(SRC_PATH)/scripts/shaderinclude.pl
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(call quiet-command,\
|
|
||||||
perl $(SRC_PATH)/scripts/shaderinclude.pl $< > $@,\
|
|
||||||
"VERT","$@")
|
|
||||||
|
|
||||||
ui/shader/%-frag.h: $(SRC_PATH)/ui/shader/%.frag $(SRC_PATH)/scripts/shaderinclude.pl
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(call quiet-command,\
|
|
||||||
perl $(SRC_PATH)/scripts/shaderinclude.pl $< > $@,\
|
|
||||||
"FRAG","$@")
|
|
||||||
|
|
||||||
ui/console-gl.o: $(SRC_PATH)/ui/console-gl.c \
|
|
||||||
ui/shader/texture-blit-vert.h ui/shader/texture-blit-frag.h
|
|
||||||
|
|
||||||
# documentation
|
# documentation
|
||||||
MAKEINFO=makeinfo
|
MAKEINFO=makeinfo
|
||||||
MAKEINFOINCLUDES= -I docs -I $(<D) -I $(@D)
|
MAKEINFOFLAGS=--no-headers --no-split --number-sections
|
||||||
MAKEINFOFLAGS=--no-split --number-sections $(MAKEINFOINCLUDES)
|
TEXIFLAG=$(if $(V),,--quiet)
|
||||||
TEXI2PODFLAGS=$(MAKEINFOINCLUDES) "-DVERSION=$(VERSION)"
|
%.dvi: %.texi
|
||||||
TEXI2PDFFLAGS=$(if $(V),,--quiet) -I $(SRC_PATH) $(MAKEINFOINCLUDES)
|
$(call quiet-command,texi2dvi $(TEXIFLAG) -I . $<," GEN $@")
|
||||||
|
|
||||||
docs/version.texi: $(SRC_PATH)/VERSION
|
%.html: %.texi
|
||||||
$(call quiet-command,echo "@set VERSION $(VERSION)" > $@,"GEN","$@")
|
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --html $< -o $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
%.html: %.texi docs/version.texi
|
%.info: %.texi
|
||||||
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
|
$(call quiet-command,$(MAKEINFO) $< -o $@," GEN $@")
|
||||||
--html $< -o $@,"GEN","$@")
|
|
||||||
|
|
||||||
%.info: %.texi docs/version.texi
|
%.pdf: %.texi
|
||||||
$(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) $< -o $@,"GEN","$@")
|
$(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<," GEN $@")
|
||||||
|
|
||||||
%.txt: %.texi docs/version.texi
|
qemu-options.texi: $(SRC_PATH)/qemu-options.hx
|
||||||
$(call quiet-command,LC_ALL=C $(MAKEINFO) $(MAKEINFOFLAGS) --no-headers \
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||||
--plaintext $< -o $@,"GEN","$@")
|
|
||||||
|
|
||||||
%.pdf: %.texi docs/version.texi
|
qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx
|
||||||
$(call quiet-command,texi2pdf $(TEXI2PDFFLAGS) $< -o $@,"GEN","$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||||
|
|
||||||
qemu-options.texi: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
|
qmp-commands.txt: $(SRC_PATH)/qmp-commands.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -q < $< > $@," GEN $@")
|
||||||
|
|
||||||
qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@")
|
||||||
|
|
||||||
qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxtool
|
qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
$(call quiet-command, \
|
||||||
|
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu.pod && \
|
||||||
|
$(POD2MAN) --section=1 --center=" " --release=" " qemu.pod > $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
|
qemu-img.1: qemu-img.texi qemu-img-cmds.texi
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
|
$(call quiet-command, \
|
||||||
|
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-img.pod && \
|
||||||
|
$(POD2MAN) --section=1 --center=" " --release=" " qemu-img.pod > $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
|
|
||||||
|
|
||||||
docs/interop/qemu-qmp-qapi.texi: $(qapi-modules)
|
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
|
|
||||||
|
|
||||||
docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json
|
|
||||||
$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
|
|
||||||
|
|
||||||
qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
|
|
||||||
qemu.1: qemu-option-trace.texi
|
|
||||||
qemu-img.1: qemu-img.texi qemu-option-trace.texi qemu-img-cmds.texi
|
|
||||||
fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
|
fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
|
||||||
qemu-nbd.8: qemu-nbd.texi qemu-option-trace.texi
|
$(call quiet-command, \
|
||||||
qemu-ga.8: qemu-ga.texi
|
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< fsdev/virtfs-proxy-helper.pod && \
|
||||||
|
$(POD2MAN) --section=1 --center=" " --release=" " fsdev/virtfs-proxy-helper.pod > $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
html: qemu-doc.html docs/interop/qemu-qmp-ref.html docs/interop/qemu-ga-ref.html
|
qemu-nbd.8: qemu-nbd.texi
|
||||||
info: qemu-doc.info docs/interop/qemu-qmp-ref.info docs/interop/qemu-ga-ref.info
|
$(call quiet-command, \
|
||||||
pdf: qemu-doc.pdf docs/interop/qemu-qmp-ref.pdf docs/interop/qemu-ga-ref.pdf
|
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \
|
||||||
txt: qemu-doc.txt docs/interop/qemu-qmp-ref.txt docs/interop/qemu-ga-ref.txt
|
$(POD2MAN) --section=8 --center=" " --release=" " qemu-nbd.pod > $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
qemu-doc.html qemu-doc.info qemu-doc.pdf qemu-doc.txt: \
|
kvm_stat.1: scripts/kvm/kvm_stat.texi
|
||||||
qemu-img.texi qemu-nbd.texi qemu-options.texi qemu-option-trace.texi \
|
$(call quiet-command, \
|
||||||
qemu-monitor.texi qemu-img-cmds.texi qemu-ga.texi \
|
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< kvm_stat.pod && \
|
||||||
qemu-monitor-info.texi
|
$(POD2MAN) --section=1 --center=" " --release=" " kvm_stat.pod > $@, \
|
||||||
|
" GEN $@")
|
||||||
|
|
||||||
docs/interop/qemu-ga-ref.dvi docs/interop/qemu-ga-ref.html \
|
dvi: qemu-doc.dvi qemu-tech.dvi
|
||||||
docs/interop/qemu-ga-ref.info docs/interop/qemu-ga-ref.pdf \
|
html: qemu-doc.html qemu-tech.html
|
||||||
docs/interop/qemu-ga-ref.txt docs/interop/qemu-ga-ref.7: \
|
info: qemu-doc.info qemu-tech.info
|
||||||
docs/interop/qemu-ga-ref.texi docs/interop/qemu-ga-qapi.texi
|
pdf: qemu-doc.pdf qemu-tech.pdf
|
||||||
|
|
||||||
docs/interop/qemu-qmp-ref.dvi docs/interop/qemu-qmp-ref.html \
|
|
||||||
docs/interop/qemu-qmp-ref.info docs/interop/qemu-qmp-ref.pdf \
|
|
||||||
docs/interop/qemu-qmp-ref.txt docs/interop/qemu-qmp-ref.7: \
|
|
||||||
docs/interop/qemu-qmp-ref.texi docs/interop/qemu-qmp-qapi.texi
|
|
||||||
|
|
||||||
|
qemu-doc.dvi qemu-doc.html qemu-doc.info qemu-doc.pdf: \
|
||||||
|
qemu-img.texi qemu-nbd.texi qemu-options.texi \
|
||||||
|
qemu-monitor.texi qemu-img-cmds.texi
|
||||||
|
|
||||||
ifdef CONFIG_WIN32
|
ifdef CONFIG_WIN32
|
||||||
|
|
||||||
@@ -788,7 +557,6 @@ endif # SIGNCODE
|
|||||||
$(if $(DLL_PATH),-DDLLDIR="$(DLL_PATH)") \
|
$(if $(DLL_PATH),-DDLLDIR="$(DLL_PATH)") \
|
||||||
-DSRCDIR="$(SRC_PATH)" \
|
-DSRCDIR="$(SRC_PATH)" \
|
||||||
-DOUTFILE="$(INSTALLER)" \
|
-DOUTFILE="$(INSTALLER)" \
|
||||||
-DDISPLAYVERSION="$(VERSION)" \
|
|
||||||
$(SRC_PATH)/qemu.nsi
|
$(SRC_PATH)/qemu.nsi
|
||||||
rm -r ${INSTDIR}
|
rm -r ${INSTDIR}
|
||||||
ifdef SIGNCODE
|
ifdef SIGNCODE
|
||||||
@@ -798,55 +566,10 @@ endif # CONFIG_WIN
|
|||||||
|
|
||||||
# Add a dependency on the generated files, so that they are always
|
# Add a dependency on the generated files, so that they are always
|
||||||
# rebuilt before other object files
|
# rebuilt before other object files
|
||||||
ifneq ($(wildcard config-host.mak),)
|
ifneq ($(filter-out %clean,$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
||||||
ifneq ($(filter-out $(UNCHECKED_GOALS),$(MAKECMDGOALS)),$(if $(MAKECMDGOALS),,fail))
|
Makefile: $(GENERATED_HEADERS)
|
||||||
Makefile: $(GENERATED_FILES)
|
|
||||||
endif
|
endif
|
||||||
endif
|
|
||||||
|
|
||||||
.SECONDARY: $(TRACE_HEADERS) $(TRACE_HEADERS:%=%-timestamp) \
|
|
||||||
$(TRACE_SOURCES) $(TRACE_SOURCES:%=%-timestamp) \
|
|
||||||
$(TRACE_DTRACE) $(TRACE_DTRACE:%=%-timestamp)
|
|
||||||
|
|
||||||
# Include automatically generated dependency files
|
# Include automatically generated dependency files
|
||||||
# Dependencies in Makefile.objs files come from our recursive subdir rules
|
# Dependencies in Makefile.objs files come from our recursive subdir rules
|
||||||
-include $(wildcard *.d tests/*.d)
|
-include $(wildcard *.d tests/*.d)
|
||||||
|
|
||||||
include $(SRC_PATH)/tests/docker/Makefile.include
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
|
||||||
@echo 'Generic targets:'
|
|
||||||
@echo ' all - Build all'
|
|
||||||
@echo ' dir/file.o - Build specified target only'
|
|
||||||
@echo ' install - Install QEMU, documentation and tools'
|
|
||||||
@echo ' ctags/TAGS - Generate tags file for editors'
|
|
||||||
@echo ' cscope - Generate cscope index'
|
|
||||||
@echo ''
|
|
||||||
@$(if $(TARGET_DIRS), \
|
|
||||||
echo 'Architecture specific targets:'; \
|
|
||||||
$(foreach t, $(TARGET_DIRS), \
|
|
||||||
printf " %-30s - Build for %s\\n" $(patsubst %,subdir-%,$(t)) $(t);) \
|
|
||||||
echo '')
|
|
||||||
@echo 'Cleaning targets:'
|
|
||||||
@echo ' clean - Remove most generated files but keep the config'
|
|
||||||
@echo ' distclean - Remove all generated files'
|
|
||||||
@echo ' dist - Build a distributable tarball'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Test targets:'
|
|
||||||
@echo ' check - Run all tests (check-help for details)'
|
|
||||||
@echo ' docker - Help about targets running tests inside Docker containers'
|
|
||||||
@echo ''
|
|
||||||
@echo 'Documentation targets:'
|
|
||||||
@echo ' html info pdf txt'
|
|
||||||
@echo ' - Build documentation in specified format'
|
|
||||||
@echo ''
|
|
||||||
ifdef CONFIG_WIN32
|
|
||||||
@echo 'Windows targets:'
|
|
||||||
@echo ' installer - Build NSIS-based installer for QEMU'
|
|
||||||
ifdef QEMU_GA_MSI_ENABLED
|
|
||||||
@echo ' msi - Build MSI-based installer for qemu-ga'
|
|
||||||
endif
|
|
||||||
@echo ''
|
|
||||||
endif
|
|
||||||
@echo ' make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build'
|
|
||||||
|
|||||||
130
Makefile.objs
130
Makefile.objs
@@ -1,37 +1,37 @@
|
|||||||
#######################################################################
|
#######################################################################
|
||||||
# Common libraries for tools and emulators
|
# Common libraries for tools and emulators
|
||||||
stub-obj-y = stubs/ crypto/
|
stub-obj-y = stubs/
|
||||||
util-obj-y = util/ qobject/ qapi/
|
util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
|
||||||
util-obj-y += qmp-introspect.o qapi-types.o qapi-visit.o qapi-event.o
|
|
||||||
|
|
||||||
chardev-obj-y = chardev/
|
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# block-obj-y is code used by both qemu system emulation and qemu-img
|
# block-obj-y is code used by both qemu system emulation and qemu-img
|
||||||
|
|
||||||
block-obj-y += nbd/
|
block-obj-y = async.o thread-pool.o
|
||||||
block-obj-y += block.o blockjob.o
|
block-obj-y += nbd.o block.o blockjob.o
|
||||||
|
block-obj-y += main-loop.o iohandler.o qemu-timer.o
|
||||||
|
block-obj-$(CONFIG_POSIX) += aio-posix.o
|
||||||
|
block-obj-$(CONFIG_WIN32) += aio-win32.o
|
||||||
block-obj-y += block/
|
block-obj-y += block/
|
||||||
block-obj-y += qemu-io-cmds.o
|
block-obj-y += qemu-io-cmds.o
|
||||||
block-obj-$(CONFIG_REPLICATION) += replication.o
|
|
||||||
|
block-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
|
||||||
|
block-obj-y += qemu-coroutine-sleep.o
|
||||||
|
block-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
|
||||||
|
|
||||||
block-obj-m = block/
|
block-obj-m = block/
|
||||||
|
|
||||||
#######################################################################
|
|
||||||
# crypto-obj-y is code used by both qemu system emulation and qemu-img
|
|
||||||
|
|
||||||
crypto-obj-y = crypto/
|
######################################################################
|
||||||
crypto-aes-obj-y = crypto/
|
# smartcard
|
||||||
|
|
||||||
#######################################################################
|
libcacard-y += libcacard/cac.o libcacard/event.o
|
||||||
# qom-obj-y is code used by both qemu system emulation and qemu-img
|
libcacard-y += libcacard/vcard.o libcacard/vreader.o
|
||||||
|
libcacard-y += libcacard/vcard_emul_nss.o
|
||||||
qom-obj-y = qom/
|
libcacard-y += libcacard/vcard_emul_type.o
|
||||||
|
libcacard-y += libcacard/card_7816.o
|
||||||
#######################################################################
|
libcacard-y += libcacard/vcardt.o
|
||||||
# io-obj-y is code used by both qemu system emulation and qemu-img
|
libcacard/vcard_emul_nss.o-cflags := $(NSS_CFLAGS)
|
||||||
|
libcacard/vcard_emul_nss.o-libs := $(NSS_LIBS)
|
||||||
io-obj-y = io/
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Target independent part of system emulation. The long term path is to
|
# Target independent part of system emulation. The long term path is to
|
||||||
@@ -49,11 +49,15 @@ common-obj-$(CONFIG_POSIX) += os-posix.o
|
|||||||
common-obj-$(CONFIG_LINUX) += fsdev/
|
common-obj-$(CONFIG_LINUX) += fsdev/
|
||||||
|
|
||||||
common-obj-y += migration/
|
common-obj-y += migration/
|
||||||
|
common-obj-y += qemu-char.o #aio.o
|
||||||
|
common-obj-y += page_cache.o
|
||||||
|
common-obj-y += qjson.o
|
||||||
|
|
||||||
|
common-obj-$(CONFIG_SPICE) += spice-qemu-char.o
|
||||||
|
|
||||||
common-obj-y += audio/
|
common-obj-y += audio/
|
||||||
common-obj-y += hw/
|
common-obj-y += hw/
|
||||||
|
common-obj-y += accel.o
|
||||||
common-obj-y += replay/
|
|
||||||
|
|
||||||
common-obj-y += ui/
|
common-obj-y += ui/
|
||||||
common-obj-y += bt-host.o bt-vhci.o
|
common-obj-y += bt-host.o bt-vhci.o
|
||||||
@@ -67,23 +71,22 @@ common-obj-y += tpm.o
|
|||||||
common-obj-$(CONFIG_SLIRP) += slirp/
|
common-obj-$(CONFIG_SLIRP) += slirp/
|
||||||
|
|
||||||
common-obj-y += backends/
|
common-obj-y += backends/
|
||||||
common-obj-y += chardev/
|
|
||||||
|
|
||||||
common-obj-$(CONFIG_SECCOMP) += qemu-seccomp.o
|
common-obj-$(CONFIG_SECCOMP) += qemu-seccomp.o
|
||||||
|
|
||||||
common-obj-$(CONFIG_FDT) += device_tree.o
|
common-obj-$(CONFIG_SMARTCARD_NSS) += $(libcacard-y)
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# qapi
|
# qapi
|
||||||
|
|
||||||
common-obj-y += qmp-marshal.o
|
common-obj-y += qmp-marshal.o
|
||||||
common-obj-y += qmp-introspect.o
|
|
||||||
common-obj-y += qmp.o hmp.o
|
common-obj-y += qmp.o hmp.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
# Target-independent parts used in system and user emulation
|
# Target-independent parts used in system and user emulation
|
||||||
common-obj-y += cpus-common.o
|
common-obj-y += qemu-log.o
|
||||||
|
common-obj-y += tcg-runtime.o
|
||||||
common-obj-y += hw/
|
common-obj-y += hw/
|
||||||
common-obj-y += qom/
|
common-obj-y += qom/
|
||||||
common-obj-y += disas/
|
common-obj-y += disas/
|
||||||
@@ -91,6 +94,7 @@ common-obj-y += disas/
|
|||||||
######################################################################
|
######################################################################
|
||||||
# Resource file for Windows executables
|
# Resource file for Windows executables
|
||||||
version-obj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.o
|
version-obj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.o
|
||||||
|
version-lobj-$(CONFIG_WIN32) += $(BUILD_DIR)/version.lo
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# tracing
|
# tracing
|
||||||
@@ -104,75 +108,3 @@ target-obj-y += trace/
|
|||||||
# by libqemuutil.a. These should be moved to a separate .json schema.
|
# by libqemuutil.a. These should be moved to a separate .json schema.
|
||||||
qga-obj-y = qga/
|
qga-obj-y = qga/
|
||||||
qga-vss-dll-obj-y = qga/
|
qga-vss-dll-obj-y = qga/
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# contrib
|
|
||||||
ivshmem-client-obj-y = contrib/ivshmem-client/
|
|
||||||
ivshmem-server-obj-y = contrib/ivshmem-server/
|
|
||||||
libvhost-user-obj-y = contrib/libvhost-user/
|
|
||||||
vhost-user-scsi.o-cflags := $(LIBISCSI_CFLAGS)
|
|
||||||
vhost-user-scsi.o-libs := $(LIBISCSI_LIBS)
|
|
||||||
vhost-user-scsi-obj-y = contrib/vhost-user-scsi/
|
|
||||||
vhost-user-scsi-obj-y += contrib/libvhost-user/libvhost-user.o
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
trace-events-subdirs =
|
|
||||||
trace-events-subdirs += util
|
|
||||||
trace-events-subdirs += crypto
|
|
||||||
trace-events-subdirs += io
|
|
||||||
trace-events-subdirs += migration
|
|
||||||
trace-events-subdirs += block
|
|
||||||
trace-events-subdirs += backends
|
|
||||||
trace-events-subdirs += chardev
|
|
||||||
trace-events-subdirs += hw/block
|
|
||||||
trace-events-subdirs += hw/block/dataplane
|
|
||||||
trace-events-subdirs += hw/char
|
|
||||||
trace-events-subdirs += hw/intc
|
|
||||||
trace-events-subdirs += hw/net
|
|
||||||
trace-events-subdirs += hw/virtio
|
|
||||||
trace-events-subdirs += hw/audio
|
|
||||||
trace-events-subdirs += hw/misc
|
|
||||||
trace-events-subdirs += hw/usb
|
|
||||||
trace-events-subdirs += hw/scsi
|
|
||||||
trace-events-subdirs += hw/nvram
|
|
||||||
trace-events-subdirs += hw/display
|
|
||||||
trace-events-subdirs += hw/input
|
|
||||||
trace-events-subdirs += hw/timer
|
|
||||||
trace-events-subdirs += hw/dma
|
|
||||||
trace-events-subdirs += hw/sparc
|
|
||||||
trace-events-subdirs += hw/sd
|
|
||||||
trace-events-subdirs += hw/isa
|
|
||||||
trace-events-subdirs += hw/mem
|
|
||||||
trace-events-subdirs += hw/i386
|
|
||||||
trace-events-subdirs += hw/i386/xen
|
|
||||||
trace-events-subdirs += hw/9pfs
|
|
||||||
trace-events-subdirs += hw/ppc
|
|
||||||
trace-events-subdirs += hw/pci
|
|
||||||
trace-events-subdirs += hw/s390x
|
|
||||||
trace-events-subdirs += hw/vfio
|
|
||||||
trace-events-subdirs += hw/acpi
|
|
||||||
trace-events-subdirs += hw/arm
|
|
||||||
trace-events-subdirs += hw/alpha
|
|
||||||
trace-events-subdirs += hw/xen
|
|
||||||
trace-events-subdirs += ui
|
|
||||||
trace-events-subdirs += audio
|
|
||||||
trace-events-subdirs += net
|
|
||||||
trace-events-subdirs += target/arm
|
|
||||||
trace-events-subdirs += target/i386
|
|
||||||
trace-events-subdirs += target/mips
|
|
||||||
trace-events-subdirs += target/sparc
|
|
||||||
trace-events-subdirs += target/s390x
|
|
||||||
trace-events-subdirs += target/ppc
|
|
||||||
trace-events-subdirs += qom
|
|
||||||
trace-events-subdirs += linux-user
|
|
||||||
trace-events-subdirs += qapi
|
|
||||||
trace-events-subdirs += accel/tcg
|
|
||||||
trace-events-subdirs += accel/kvm
|
|
||||||
|
|
||||||
trace-events-files = $(SRC_PATH)/trace-events $(trace-events-subdirs:%=$(SRC_PATH)/%/trace-events)
|
|
||||||
|
|
||||||
trace-obj-y = trace-root.o
|
|
||||||
trace-obj-y += $(trace-events-subdirs:%=%/trace.o)
|
|
||||||
trace-obj-$(CONFIG_TRACE_UST) += trace-ust-all.o
|
|
||||||
trace-obj-$(CONFIG_TRACE_DTRACE) += trace-dtrace-root.o
|
|
||||||
trace-obj-$(CONFIG_TRACE_DTRACE) += $(trace-events-subdirs:%=%/trace-dtrace.o)
|
|
||||||
|
|||||||
108
Makefile.target
108
Makefile.target
@@ -1,17 +1,15 @@
|
|||||||
# -*- Mode: makefile -*-
|
# -*- Mode: makefile -*-
|
||||||
|
|
||||||
BUILD_DIR?=$(CURDIR)/..
|
|
||||||
|
|
||||||
include ../config-host.mak
|
include ../config-host.mak
|
||||||
include config-target.mak
|
include config-target.mak
|
||||||
include config-devices.mak
|
include config-devices.mak
|
||||||
include $(SRC_PATH)/rules.mak
|
include $(SRC_PATH)/rules.mak
|
||||||
|
|
||||||
$(call set-vpath, $(SRC_PATH):$(BUILD_DIR))
|
$(call set-vpath, $(SRC_PATH))
|
||||||
ifdef CONFIG_LINUX
|
ifdef CONFIG_LINUX
|
||||||
QEMU_CFLAGS += -I../linux-headers
|
QEMU_CFLAGS += -I../linux-headers
|
||||||
endif
|
endif
|
||||||
QEMU_CFLAGS += -I.. -I$(SRC_PATH)/target/$(TARGET_BASE_ARCH) -DNEED_CPU_H
|
QEMU_CFLAGS += -I.. -I$(SRC_PATH)/target-$(TARGET_BASE_ARCH) -DNEED_CPU_H
|
||||||
|
|
||||||
QEMU_CFLAGS+=-I$(SRC_PATH)/include
|
QEMU_CFLAGS+=-I$(SRC_PATH)/include
|
||||||
|
|
||||||
@@ -26,7 +24,7 @@ ifneq (,$(findstring -mwindows,$(libs_softmmu)))
|
|||||||
# Terminate program name with a 'w' because the linker builds a windows executable.
|
# Terminate program name with a 'w' because the linker builds a windows executable.
|
||||||
QEMU_PROGW=qemu-system-$(TARGET_NAME)w$(EXESUF)
|
QEMU_PROGW=qemu-system-$(TARGET_NAME)w$(EXESUF)
|
||||||
$(QEMU_PROG): $(QEMU_PROGW)
|
$(QEMU_PROG): $(QEMU_PROGW)
|
||||||
$(call quiet-command,$(OBJCOPY) --subsystem console $(QEMU_PROGW) $(QEMU_PROG),"GEN","$(TARGET_DIR)$(QEMU_PROG)")
|
$(call quiet-command,$(OBJCOPY) --subsystem console $(QEMU_PROGW) $(QEMU_PROG)," GEN $(TARGET_DIR)$(QEMU_PROG)")
|
||||||
QEMU_PROG_BUILD = $(QEMU_PROGW)
|
QEMU_PROG_BUILD = $(QEMU_PROGW)
|
||||||
else
|
else
|
||||||
QEMU_PROG_BUILD = $(QEMU_PROG)
|
QEMU_PROG_BUILD = $(QEMU_PROG)
|
||||||
@@ -36,6 +34,10 @@ endif
|
|||||||
PROGS=$(QEMU_PROG) $(QEMU_PROGW)
|
PROGS=$(QEMU_PROG) $(QEMU_PROGW)
|
||||||
STPFILES=
|
STPFILES=
|
||||||
|
|
||||||
|
ifdef CONFIG_LINUX_USER
|
||||||
|
PROGS+=$(QEMU_PROG)-binfmt
|
||||||
|
endif
|
||||||
|
|
||||||
config-target.h: config-target.h-timestamp
|
config-target.h: config-target.h-timestamp
|
||||||
config-target.h-timestamp: config-target.mak
|
config-target.h-timestamp: config-target.mak
|
||||||
|
|
||||||
@@ -48,38 +50,34 @@ else
|
|||||||
TARGET_TYPE=system
|
TARGET_TYPE=system
|
||||||
endif
|
endif
|
||||||
|
|
||||||
$(QEMU_PROG).stp-installed: $(BUILD_DIR)/trace-events-all
|
$(QEMU_PROG).stp-installed: $(SRC_PATH)/trace-events
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
$(call quiet-command,$(TRACETOOL) \
|
||||||
--group=all \
|
|
||||||
--format=stap \
|
--format=stap \
|
||||||
--backends=$(TRACE_BACKENDS) \
|
--backends=$(TRACE_BACKENDS) \
|
||||||
--binary=$(bindir)/$(QEMU_PROG) \
|
--binary=$(bindir)/$(QEMU_PROG) \
|
||||||
--target-name=$(TARGET_NAME) \
|
--target-name=$(TARGET_NAME) \
|
||||||
--target-type=$(TARGET_TYPE) \
|
--target-type=$(TARGET_TYPE) \
|
||||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp-installed")
|
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp-installed")
|
||||||
|
|
||||||
$(QEMU_PROG).stp: $(BUILD_DIR)/trace-events-all
|
$(QEMU_PROG).stp: $(SRC_PATH)/trace-events
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
$(call quiet-command,$(TRACETOOL) \
|
||||||
--group=all \
|
|
||||||
--format=stap \
|
--format=stap \
|
||||||
--backends=$(TRACE_BACKENDS) \
|
--backends=$(TRACE_BACKENDS) \
|
||||||
--binary=$(realpath .)/$(QEMU_PROG) \
|
--binary=$(realpath .)/$(QEMU_PROG) \
|
||||||
--target-name=$(TARGET_NAME) \
|
--target-name=$(TARGET_NAME) \
|
||||||
--target-type=$(TARGET_TYPE) \
|
--target-type=$(TARGET_TYPE) \
|
||||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG).stp")
|
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG).stp")
|
||||||
|
|
||||||
$(QEMU_PROG)-simpletrace.stp: $(BUILD_DIR)/trace-events-all
|
$(QEMU_PROG)-simpletrace.stp: $(SRC_PATH)/trace-events
|
||||||
$(call quiet-command,$(TRACETOOL) \
|
$(call quiet-command,$(TRACETOOL) \
|
||||||
--group=all \
|
|
||||||
--format=simpletrace-stap \
|
--format=simpletrace-stap \
|
||||||
--backends=$(TRACE_BACKENDS) \
|
--backends=$(TRACE_BACKENDS) \
|
||||||
--probe-prefix=qemu.$(TARGET_TYPE).$(TARGET_NAME) \
|
--probe-prefix=qemu.$(TARGET_TYPE).$(TARGET_NAME) \
|
||||||
$< > $@,"GEN","$(TARGET_DIR)$(QEMU_PROG)-simpletrace.stp")
|
< $< > $@," GEN $(TARGET_DIR)$(QEMU_PROG)-simpletrace.stp")
|
||||||
|
|
||||||
else
|
else
|
||||||
stap:
|
stap:
|
||||||
endif
|
endif
|
||||||
.PHONY: stap
|
|
||||||
|
|
||||||
all: $(PROGS) stap
|
all: $(PROGS) stap
|
||||||
|
|
||||||
@@ -88,17 +86,15 @@ all: $(PROGS) stap
|
|||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
# cpu emulator library
|
# cpu emulator library
|
||||||
obj-y += exec.o
|
obj-y = exec.o translate-all.o cpu-exec.o
|
||||||
obj-y += accel/
|
|
||||||
obj-y += tcg/tcg.o tcg/tcg-op.o tcg/optimize.o
|
obj-y += tcg/tcg.o tcg/tcg-op.o tcg/optimize.o
|
||||||
obj-y += tcg/tcg-common.o tcg/tcg-runtime.o
|
obj-$(CONFIG_TCG_INTERPRETER) += tci.o
|
||||||
obj-$(CONFIG_TCG_INTERPRETER) += tcg/tci.o
|
|
||||||
obj-$(CONFIG_TCG_INTERPRETER) += disas/tci.o
|
obj-$(CONFIG_TCG_INTERPRETER) += disas/tci.o
|
||||||
obj-y += fpu/softfloat.o
|
obj-y += fpu/softfloat.o
|
||||||
obj-y += target/$(TARGET_BASE_ARCH)/
|
obj-y += target-$(TARGET_BASE_ARCH)/
|
||||||
obj-y += disas.o
|
obj-y += disas.o
|
||||||
obj-$(call notempty,$(TARGET_XML_FILES)) += gdbstub-xml.o
|
obj-$(call notempty,$(TARGET_XML_FILES)) += gdbstub-xml.o
|
||||||
obj-$(call lnot,$(CONFIG_HAX)) += hax-stub.o
|
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
|
||||||
|
|
||||||
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decContext.o
|
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decContext.o
|
||||||
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decNumber.o
|
obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/decNumber.o
|
||||||
@@ -111,12 +107,12 @@ obj-$(CONFIG_LIBDECNUMBER) += libdecnumber/dpd/decimal128.o
|
|||||||
|
|
||||||
ifdef CONFIG_LINUX_USER
|
ifdef CONFIG_LINUX_USER
|
||||||
|
|
||||||
QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) \
|
QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) -I$(SRC_PATH)/linux-user
|
||||||
-I$(SRC_PATH)/linux-user/host/$(ARCH) \
|
|
||||||
-I$(SRC_PATH)/linux-user
|
|
||||||
|
|
||||||
obj-y += linux-user/
|
obj-y += linux-user/
|
||||||
obj-y += gdbstub.o thunk.o user-exec.o user-exec-stub.o
|
obj-y += gdbstub.o thunk.o user-exec.o
|
||||||
|
|
||||||
|
obj-binfmt-y += linux-user/
|
||||||
|
|
||||||
endif #CONFIG_LINUX_USER
|
endif #CONFIG_LINUX_USER
|
||||||
|
|
||||||
@@ -129,7 +125,7 @@ QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ABI_DIR) \
|
|||||||
-I$(SRC_PATH)/bsd-user/$(HOST_VARIANT_DIR)
|
-I$(SRC_PATH)/bsd-user/$(HOST_VARIANT_DIR)
|
||||||
|
|
||||||
obj-y += bsd-user/
|
obj-y += bsd-user/
|
||||||
obj-y += gdbstub.o user-exec.o user-exec-stub.o
|
obj-y += gdbstub.o user-exec.o
|
||||||
|
|
||||||
endif #CONFIG_BSD_USER
|
endif #CONFIG_BSD_USER
|
||||||
|
|
||||||
@@ -139,11 +135,18 @@ ifdef CONFIG_SOFTMMU
|
|||||||
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o
|
obj-y += arch_init.o cpus.o monitor.o gdbstub.o balloon.o ioport.o numa.o
|
||||||
obj-y += qtest.o bootdevice.o
|
obj-y += qtest.o bootdevice.o
|
||||||
obj-y += hw/
|
obj-y += hw/
|
||||||
obj-y += memory.o
|
obj-$(CONFIG_FDT) += device_tree.o
|
||||||
|
obj-$(CONFIG_KVM) += kvm-all.o
|
||||||
|
obj-y += memory.o savevm.o cputlb.o
|
||||||
obj-y += memory_mapping.o
|
obj-y += memory_mapping.o
|
||||||
obj-y += dump.o
|
obj-y += dump.o
|
||||||
obj-y += migration/ram.o
|
LIBS+=$(libs_softmmu)
|
||||||
LIBS := $(libs_softmmu) $(LIBS)
|
|
||||||
|
# xen support
|
||||||
|
obj-$(CONFIG_XEN) += xen-common.o
|
||||||
|
obj-$(CONFIG_XEN_I386) += xen-hvm.o xen-mapcache.o
|
||||||
|
obj-$(call lnot,$(CONFIG_XEN)) += xen-common-stub.o
|
||||||
|
obj-$(call lnot,$(CONFIG_XEN_I386)) += xen-hvm-stub.o
|
||||||
|
|
||||||
# Hardware support
|
# Hardware support
|
||||||
ifeq ($(TARGET_NAME), sparc64)
|
ifeq ($(TARGET_NAME), sparc64)
|
||||||
@@ -152,67 +155,58 @@ else
|
|||||||
obj-y += hw/$(TARGET_BASE_ARCH)/
|
obj-y += hw/$(TARGET_BASE_ARCH)/
|
||||||
endif
|
endif
|
||||||
|
|
||||||
GENERATED_FILES += hmp-commands.h hmp-commands-info.h
|
GENERATED_HEADERS += hmp-commands.h qmp-commands-old.h
|
||||||
|
|
||||||
endif # CONFIG_SOFTMMU
|
endif # CONFIG_SOFTMMU
|
||||||
|
|
||||||
# Workaround for http://gcc.gnu.org/PR55489, see configure.
|
# Workaround for http://gcc.gnu.org/PR55489, see configure.
|
||||||
%/translate.o: QEMU_CFLAGS += $(TRANSLATE_OPT_CFLAGS)
|
%/translate.o: QEMU_CFLAGS += $(TRANSLATE_OPT_CFLAGS)
|
||||||
|
|
||||||
|
ifdef CONFIG_LINUX_USER
|
||||||
|
dummy := $(call unnest-vars,,obj-y obj-binfmt-y)
|
||||||
|
else
|
||||||
dummy := $(call unnest-vars,,obj-y)
|
dummy := $(call unnest-vars,,obj-y)
|
||||||
|
endif
|
||||||
all-obj-y := $(obj-y)
|
all-obj-y := $(obj-y)
|
||||||
|
|
||||||
target-obj-y :=
|
target-obj-y :=
|
||||||
block-obj-y :=
|
block-obj-y :=
|
||||||
common-obj-y :=
|
common-obj-y :=
|
||||||
chardev-obj-y :=
|
|
||||||
include $(SRC_PATH)/Makefile.objs
|
include $(SRC_PATH)/Makefile.objs
|
||||||
dummy := $(call unnest-vars,,target-obj-y)
|
dummy := $(call unnest-vars,,target-obj-y)
|
||||||
target-obj-y-save := $(target-obj-y)
|
target-obj-y-save := $(target-obj-y)
|
||||||
dummy := $(call unnest-vars,.., \
|
dummy := $(call unnest-vars,.., \
|
||||||
block-obj-y \
|
block-obj-y \
|
||||||
block-obj-m \
|
block-obj-m \
|
||||||
chardev-obj-y \
|
|
||||||
crypto-obj-y \
|
|
||||||
crypto-aes-obj-y \
|
|
||||||
qom-obj-y \
|
|
||||||
io-obj-y \
|
|
||||||
common-obj-y \
|
common-obj-y \
|
||||||
common-obj-m)
|
common-obj-m)
|
||||||
target-obj-y := $(target-obj-y-save)
|
target-obj-y := $(target-obj-y-save)
|
||||||
all-obj-y += $(common-obj-y)
|
all-obj-y += $(common-obj-y)
|
||||||
all-obj-y += $(target-obj-y)
|
all-obj-y += $(target-obj-y)
|
||||||
all-obj-y += $(qom-obj-y)
|
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y)
|
||||||
all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
|
|
||||||
all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
|
|
||||||
all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
|
|
||||||
all-obj-$(CONFIG_SOFTMMU) += $(io-obj-y)
|
|
||||||
|
|
||||||
$(QEMU_PROG_BUILD): config-devices.mak
|
$(QEMU_PROG_BUILD): config-devices.mak
|
||||||
|
|
||||||
COMMON_LDADDS = ../libqemuutil.a ../libqemustub.a
|
|
||||||
|
|
||||||
# build either PROG or PROGW
|
# build either PROG or PROGW
|
||||||
$(QEMU_PROG_BUILD): $(all-obj-y) $(COMMON_LDADDS)
|
$(QEMU_PROG_BUILD): $(all-obj-y) ../libqemuutil.a ../libqemustub.a
|
||||||
$(call LINK, $(filter-out %.mak, $^))
|
$(call LINK, $(filter-out %.mak, $^))
|
||||||
ifdef CONFIG_DARWIN
|
|
||||||
$(call quiet-command,Rez -append $(SRC_PATH)/pc-bios/qemu.rsrc -o $@,"REZ","$(TARGET_DIR)$@")
|
$(QEMU_PROG)-binfmt: $(obj-binfmt-y)
|
||||||
$(call quiet-command,SetFile -a C $@,"SETFILE","$(TARGET_DIR)$@")
|
$(call LINK,$^)
|
||||||
endif
|
|
||||||
|
|
||||||
gdbstub-xml.c: $(TARGET_XML_FILES) $(SRC_PATH)/scripts/feature_to_c.sh
|
gdbstub-xml.c: $(TARGET_XML_FILES) $(SRC_PATH)/scripts/feature_to_c.sh
|
||||||
$(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES),"GEN","$(TARGET_DIR)$@")
|
$(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES)," GEN $(TARGET_DIR)$@")
|
||||||
|
|
||||||
hmp-commands.h: $(SRC_PATH)/hmp-commands.hx $(SRC_PATH)/scripts/hxtool
|
hmp-commands.h: $(SRC_PATH)/hmp-commands.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@")
|
||||||
|
|
||||||
hmp-commands-info.h: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxtool
|
qmp-commands-old.h: $(SRC_PATH)/qmp-commands.hx
|
||||||
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$(TARGET_DIR)$@")
|
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@")
|
||||||
|
|
||||||
clean: clean-target
|
clean:
|
||||||
rm -f *.a *~ $(PROGS)
|
rm -f *.a *~ $(PROGS)
|
||||||
rm -f $(shell find . -name '*.[od]')
|
rm -f $(shell find . -name '*.[od]')
|
||||||
rm -f hmp-commands.h gdbstub-xml.c
|
rm -f hmp-commands.h qmp-commands-old.h gdbstub-xml.c
|
||||||
ifdef CONFIG_TRACE_SYSTEMTAP
|
ifdef CONFIG_TRACE_SYSTEMTAP
|
||||||
rm -f *.stp
|
rm -f *.stp
|
||||||
endif
|
endif
|
||||||
@@ -227,5 +221,5 @@ ifdef CONFIG_TRACE_SYSTEMTAP
|
|||||||
$(INSTALL_DATA) $(QEMU_PROG)-simpletrace.stp "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG)-simpletrace.stp"
|
$(INSTALL_DATA) $(QEMU_PROG)-simpletrace.stp "$(DESTDIR)$(qemu_datadir)/../systemtap/tapset/$(QEMU_PROG)-simpletrace.stp"
|
||||||
endif
|
endif
|
||||||
|
|
||||||
GENERATED_FILES += config-target.h
|
GENERATED_HEADERS += config-target.h
|
||||||
Makefile: $(GENERATED_FILES)
|
Makefile: $(GENERATED_HEADERS)
|
||||||
|
|||||||
107
README
107
README
@@ -1,106 +1,3 @@
|
|||||||
QEMU README
|
Read the documentation in qemu-doc.html or on http://wiki.qemu-project.org
|
||||||
===========
|
|
||||||
|
|
||||||
QEMU is a generic and open source machine & userspace emulator and
|
- QEMU team
|
||||||
virtualizer.
|
|
||||||
|
|
||||||
QEMU is capable of emulating a complete machine in software without any
|
|
||||||
need for hardware virtualization support. By using dynamic translation,
|
|
||||||
it achieves very good performance. QEMU can also integrate with the Xen
|
|
||||||
and KVM hypervisors to provide emulated hardware while allowing the
|
|
||||||
hypervisor to manage the CPU. With hypervisor support, QEMU can achieve
|
|
||||||
near native performance for CPUs. When QEMU emulates CPUs directly it is
|
|
||||||
capable of running operating systems made for one machine (e.g. an ARMv7
|
|
||||||
board) on a different machine (e.g. an x86_64 PC board).
|
|
||||||
|
|
||||||
QEMU is also capable of providing userspace API virtualization for Linux
|
|
||||||
and BSD kernel interfaces. This allows binaries compiled against one
|
|
||||||
architecture ABI (e.g. the Linux PPC64 ABI) to be run on a host using a
|
|
||||||
different architecture ABI (e.g. the Linux x86_64 ABI). This does not
|
|
||||||
involve any hardware emulation, simply CPU and syscall emulation.
|
|
||||||
|
|
||||||
QEMU aims to fit into a variety of use cases. It can be invoked directly
|
|
||||||
by users wishing to have full control over its behaviour and settings.
|
|
||||||
It also aims to facilitate integration into higher level management
|
|
||||||
layers, by providing a stable command line interface and monitor API.
|
|
||||||
It is commonly invoked indirectly via the libvirt library when using
|
|
||||||
open source applications such as oVirt, OpenStack and virt-manager.
|
|
||||||
|
|
||||||
QEMU as a whole is released under the GNU General Public License,
|
|
||||||
version 2. For full licensing details, consult the LICENSE file.
|
|
||||||
|
|
||||||
|
|
||||||
Building
|
|
||||||
========
|
|
||||||
|
|
||||||
QEMU is multi-platform software intended to be buildable on all modern
|
|
||||||
Linux platforms, OS-X, Win32 (via the Mingw64 toolchain) and a variety
|
|
||||||
of other UNIX targets. The simple steps to build QEMU are:
|
|
||||||
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
../configure
|
|
||||||
make
|
|
||||||
|
|
||||||
Additional information can also be found online via the QEMU website:
|
|
||||||
|
|
||||||
http://qemu-project.org/Hosts/Linux
|
|
||||||
http://qemu-project.org/Hosts/Mac
|
|
||||||
http://qemu-project.org/Hosts/W32
|
|
||||||
|
|
||||||
|
|
||||||
Submitting patches
|
|
||||||
==================
|
|
||||||
|
|
||||||
The QEMU source code is maintained under the GIT version control system.
|
|
||||||
|
|
||||||
git clone git://git.qemu-project.org/qemu.git
|
|
||||||
|
|
||||||
When submitting patches, the preferred approach is to use 'git
|
|
||||||
format-patch' and/or 'git send-email' to format & send the mail to the
|
|
||||||
qemu-devel@nongnu.org mailing list. All patches submitted must contain
|
|
||||||
a 'Signed-off-by' line from the author. Patches should follow the
|
|
||||||
guidelines set out in the HACKING and CODING_STYLE files.
|
|
||||||
|
|
||||||
Additional information on submitting patches can be found online via
|
|
||||||
the QEMU website
|
|
||||||
|
|
||||||
http://qemu-project.org/Contribute/SubmitAPatch
|
|
||||||
http://qemu-project.org/Contribute/TrivialPatches
|
|
||||||
|
|
||||||
|
|
||||||
Bug reporting
|
|
||||||
=============
|
|
||||||
|
|
||||||
The QEMU project uses Launchpad as its primary upstream bug tracker. Bugs
|
|
||||||
found when running code built from QEMU git or upstream released sources
|
|
||||||
should be reported via:
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/qemu/
|
|
||||||
|
|
||||||
If using QEMU via an operating system vendor pre-built binary package, it
|
|
||||||
is preferable to report bugs to the vendor's own bug tracker first. If
|
|
||||||
the bug is also known to affect latest upstream code, it can also be
|
|
||||||
reported via launchpad.
|
|
||||||
|
|
||||||
For additional information on bug reporting consult:
|
|
||||||
|
|
||||||
http://qemu-project.org/Contribute/ReportABug
|
|
||||||
|
|
||||||
|
|
||||||
Contact
|
|
||||||
=======
|
|
||||||
|
|
||||||
The QEMU community can be contacted in a number of ways, with the two
|
|
||||||
main methods being email and IRC
|
|
||||||
|
|
||||||
- qemu-devel@nongnu.org
|
|
||||||
http://lists.nongnu.org/mailman/listinfo/qemu-devel
|
|
||||||
- #qemu on irc.oftc.net
|
|
||||||
|
|
||||||
Information on additional methods of contacting the community can be
|
|
||||||
found online via the QEMU website:
|
|
||||||
|
|
||||||
http://qemu-project.org/Contribute/StartHere
|
|
||||||
|
|
||||||
-- End
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/accel.h"
|
#include "sysemu/accel.h"
|
||||||
#include "hw/boards.h"
|
#include "hw/boards.h"
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
@@ -33,6 +32,16 @@
|
|||||||
#include "sysemu/qtest.h"
|
#include "sysemu/qtest.h"
|
||||||
#include "hw/xen/xen.h"
|
#include "hw/xen/xen.h"
|
||||||
#include "qom/object.h"
|
#include "qom/object.h"
|
||||||
|
#include "hw/boards.h"
|
||||||
|
|
||||||
|
int tcg_tb_size;
|
||||||
|
static bool tcg_allowed = true;
|
||||||
|
|
||||||
|
static int tcg_init(MachineState *ms)
|
||||||
|
{
|
||||||
|
tcg_exec_init(tcg_tb_size * 1024 * 1024);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const TypeInfo accel_type = {
|
static const TypeInfo accel_type = {
|
||||||
.name = TYPE_ACCEL,
|
.name = TYPE_ACCEL,
|
||||||
@@ -67,7 +76,7 @@ static int accel_init_machine(AccelClass *acc, MachineState *ms)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void configure_accelerator(MachineState *ms)
|
int configure_accelerator(MachineState *ms)
|
||||||
{
|
{
|
||||||
const char *p;
|
const char *p;
|
||||||
char buf[10];
|
char buf[10];
|
||||||
@@ -118,11 +127,31 @@ void configure_accelerator(MachineState *ms)
|
|||||||
if (init_failed) {
|
if (init_failed) {
|
||||||
fprintf(stderr, "Back to %s accelerator.\n", acc->name);
|
fprintf(stderr, "Back to %s accelerator.\n", acc->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !accel_initialised;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void tcg_accel_class_init(ObjectClass *oc, void *data)
|
||||||
|
{
|
||||||
|
AccelClass *ac = ACCEL_CLASS(oc);
|
||||||
|
ac->name = "tcg";
|
||||||
|
ac->init_machine = tcg_init;
|
||||||
|
ac->allowed = &tcg_allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TYPE_TCG_ACCEL ACCEL_CLASS_NAME("tcg")
|
||||||
|
|
||||||
|
static const TypeInfo tcg_accel_type = {
|
||||||
|
.name = TYPE_TCG_ACCEL,
|
||||||
|
.parent = TYPE_ACCEL,
|
||||||
|
.class_init = tcg_accel_class_init,
|
||||||
|
};
|
||||||
|
|
||||||
static void register_accel_types(void)
|
static void register_accel_types(void)
|
||||||
{
|
{
|
||||||
type_register_static(&accel_type);
|
type_register_static(&accel_type);
|
||||||
|
type_register_static(&tcg_accel_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
type_init(register_accel_types);
|
type_init(register_accel_types);
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
obj-$(CONFIG_SOFTMMU) += accel.o
|
|
||||||
obj-y += kvm/
|
|
||||||
obj-y += tcg/
|
|
||||||
obj-y += stubs/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
obj-$(CONFIG_KVM) += kvm-all.o
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Trace events for debugging and performance instrumentation
|
|
||||||
|
|
||||||
# kvm-all.c
|
|
||||||
kvm_ioctl(int type, void *arg) "type 0x%x, arg %p"
|
|
||||||
kvm_vm_ioctl(int type, void *arg) "type 0x%x, arg %p"
|
|
||||||
kvm_vcpu_ioctl(int cpu_index, int type, void *arg) "cpu_index %d, type 0x%x, arg %p"
|
|
||||||
kvm_run_exit(int cpu_index, uint32_t reason) "cpu_index %d, reason %d"
|
|
||||||
kvm_device_ioctl(int fd, int type, void *arg) "dev fd %d, type 0x%x, arg %p"
|
|
||||||
kvm_failed_reg_get(uint64_t id, const char *msg) "Warning: Unable to retrieve ONEREG %" PRIu64 " from KVM: %s"
|
|
||||||
kvm_failed_reg_set(uint64_t id, const char *msg) "Warning: Unable to set ONEREG %" PRIu64 " to KVM: %s"
|
|
||||||
kvm_irqchip_commit_routes(void) ""
|
|
||||||
kvm_irqchip_add_msi_route(char *name, int vector, int virq) "dev %s vector %d virq %d"
|
|
||||||
kvm_irqchip_update_msi_route(int virq) "Updating MSI route virq=%d"
|
|
||||||
kvm_irqchip_release_virq(int virq) "virq %d"
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
obj-$(CONFIG_SOFTMMU) += tcg-all.o
|
|
||||||
obj-$(CONFIG_SOFTMMU) += cputlb.o
|
|
||||||
obj-y += cpu-exec.o cpu-exec-common.o translate-all.o translate-common.o
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* emulator main execution loop
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2005 Fabrice Bellard
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "cpu.h"
|
|
||||||
#include "sysemu/cpus.h"
|
|
||||||
#include "exec/exec-all.h"
|
|
||||||
#include "exec/memory-internal.h"
|
|
||||||
|
|
||||||
/* exit the current TB, but without causing any exception to be raised */
|
|
||||||
void cpu_loop_exit_noexc(CPUState *cpu)
|
|
||||||
{
|
|
||||||
/* XXX: restore cpu registers saved in host registers */
|
|
||||||
|
|
||||||
cpu->exception_index = -1;
|
|
||||||
siglongjmp(cpu->jmp_env, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(CONFIG_SOFTMMU)
|
|
||||||
void cpu_reloading_memory_map(void)
|
|
||||||
{
|
|
||||||
if (qemu_in_vcpu_thread() && current_cpu->running) {
|
|
||||||
/* The guest can in theory prolong the RCU critical section as long
|
|
||||||
* as it feels like. The major problem with this is that because it
|
|
||||||
* can do multiple reconfigurations of the memory map within the
|
|
||||||
* critical section, we could potentially accumulate an unbounded
|
|
||||||
* collection of memory data structures awaiting reclamation.
|
|
||||||
*
|
|
||||||
* Because the only thing we're currently protecting with RCU is the
|
|
||||||
* memory data structures, it's sufficient to break the critical section
|
|
||||||
* in this callback, which we know will get called every time the
|
|
||||||
* memory map is rearranged.
|
|
||||||
*
|
|
||||||
* (If we add anything else in the system that uses RCU to protect
|
|
||||||
* its data structures, we will need to implement some other mechanism
|
|
||||||
* to force TCG CPUs to exit the critical section, at which point this
|
|
||||||
* part of this callback might become unnecessary.)
|
|
||||||
*
|
|
||||||
* This pair matches cpu_exec's rcu_read_lock()/rcu_read_unlock(), which
|
|
||||||
* only protects cpu->as->dispatch. Since we know our caller is about
|
|
||||||
* to reload it, it's safe to split the critical section.
|
|
||||||
*/
|
|
||||||
rcu_read_unlock();
|
|
||||||
rcu_read_lock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void cpu_loop_exit(CPUState *cpu)
|
|
||||||
{
|
|
||||||
siglongjmp(cpu->jmp_env, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cpu_loop_exit_restore(CPUState *cpu, uintptr_t pc)
|
|
||||||
{
|
|
||||||
if (pc) {
|
|
||||||
cpu_restore_state(cpu, pc);
|
|
||||||
}
|
|
||||||
siglongjmp(cpu->jmp_env, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cpu_loop_exit_atomic(CPUState *cpu, uintptr_t pc)
|
|
||||||
{
|
|
||||||
cpu->exception_index = EXCP_ATOMIC;
|
|
||||||
cpu_loop_exit_restore(cpu, pc);
|
|
||||||
}
|
|
||||||
@@ -1,683 +0,0 @@
|
|||||||
/*
|
|
||||||
* emulator main execution loop
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2005 Fabrice Bellard
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "cpu.h"
|
|
||||||
#include "trace.h"
|
|
||||||
#include "disas/disas.h"
|
|
||||||
#include "exec/exec-all.h"
|
|
||||||
#include "tcg.h"
|
|
||||||
#include "qemu/atomic.h"
|
|
||||||
#include "sysemu/qtest.h"
|
|
||||||
#include "qemu/timer.h"
|
|
||||||
#include "exec/address-spaces.h"
|
|
||||||
#include "qemu/rcu.h"
|
|
||||||
#include "exec/tb-hash.h"
|
|
||||||
#include "exec/log.h"
|
|
||||||
#include "qemu/main-loop.h"
|
|
||||||
#if defined(TARGET_I386) && !defined(CONFIG_USER_ONLY)
|
|
||||||
#include "hw/i386/apic.h"
|
|
||||||
#endif
|
|
||||||
#include "sysemu/cpus.h"
|
|
||||||
#include "sysemu/replay.h"
|
|
||||||
|
|
||||||
/* -icount align implementation. */
|
|
||||||
|
|
||||||
typedef struct SyncClocks {
|
|
||||||
int64_t diff_clk;
|
|
||||||
int64_t last_cpu_icount;
|
|
||||||
int64_t realtime_clock;
|
|
||||||
} SyncClocks;
|
|
||||||
|
|
||||||
#if !defined(CONFIG_USER_ONLY)
|
|
||||||
/* Allow the guest to have a max 3ms advance.
|
|
||||||
* The difference between the 2 clocks could therefore
|
|
||||||
* oscillate around 0.
|
|
||||||
*/
|
|
||||||
#define VM_CLOCK_ADVANCE 3000000
|
|
||||||
#define THRESHOLD_REDUCE 1.5
|
|
||||||
#define MAX_DELAY_PRINT_RATE 2000000000LL
|
|
||||||
#define MAX_NB_PRINTS 100
|
|
||||||
|
|
||||||
static void align_clocks(SyncClocks *sc, const CPUState *cpu)
|
|
||||||
{
|
|
||||||
int64_t cpu_icount;
|
|
||||||
|
|
||||||
if (!icount_align_option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu_icount = cpu->icount_extra + cpu->icount_decr.u16.low;
|
|
||||||
sc->diff_clk += cpu_icount_to_ns(sc->last_cpu_icount - cpu_icount);
|
|
||||||
sc->last_cpu_icount = cpu_icount;
|
|
||||||
|
|
||||||
if (sc->diff_clk > VM_CLOCK_ADVANCE) {
|
|
||||||
#ifndef _WIN32
|
|
||||||
struct timespec sleep_delay, rem_delay;
|
|
||||||
sleep_delay.tv_sec = sc->diff_clk / 1000000000LL;
|
|
||||||
sleep_delay.tv_nsec = sc->diff_clk % 1000000000LL;
|
|
||||||
if (nanosleep(&sleep_delay, &rem_delay) < 0) {
|
|
||||||
sc->diff_clk = rem_delay.tv_sec * 1000000000LL + rem_delay.tv_nsec;
|
|
||||||
} else {
|
|
||||||
sc->diff_clk = 0;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
Sleep(sc->diff_clk / SCALE_MS);
|
|
||||||
sc->diff_clk = 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_delay(const SyncClocks *sc)
|
|
||||||
{
|
|
||||||
static float threshold_delay;
|
|
||||||
static int64_t last_realtime_clock;
|
|
||||||
static int nb_prints;
|
|
||||||
|
|
||||||
if (icount_align_option &&
|
|
||||||
sc->realtime_clock - last_realtime_clock >= MAX_DELAY_PRINT_RATE &&
|
|
||||||
nb_prints < MAX_NB_PRINTS) {
|
|
||||||
if ((-sc->diff_clk / (float)1000000000LL > threshold_delay) ||
|
|
||||||
(-sc->diff_clk / (float)1000000000LL <
|
|
||||||
(threshold_delay - THRESHOLD_REDUCE))) {
|
|
||||||
threshold_delay = (-sc->diff_clk / 1000000000LL) + 1;
|
|
||||||
printf("Warning: The guest is now late by %.1f to %.1f seconds\n",
|
|
||||||
threshold_delay - 1,
|
|
||||||
threshold_delay);
|
|
||||||
nb_prints++;
|
|
||||||
last_realtime_clock = sc->realtime_clock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void init_delay_params(SyncClocks *sc,
|
|
||||||
const CPUState *cpu)
|
|
||||||
{
|
|
||||||
if (!icount_align_option) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sc->realtime_clock = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL_RT);
|
|
||||||
sc->diff_clk = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - sc->realtime_clock;
|
|
||||||
sc->last_cpu_icount = cpu->icount_extra + cpu->icount_decr.u16.low;
|
|
||||||
if (sc->diff_clk < max_delay) {
|
|
||||||
max_delay = sc->diff_clk;
|
|
||||||
}
|
|
||||||
if (sc->diff_clk > max_advance) {
|
|
||||||
max_advance = sc->diff_clk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print every 2s max if the guest is late. We limit the number
|
|
||||||
of printed messages to NB_PRINT_MAX(currently 100) */
|
|
||||||
print_delay(sc);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static void align_clocks(SyncClocks *sc, const CPUState *cpu)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void init_delay_params(SyncClocks *sc, const CPUState *cpu)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
#endif /* CONFIG USER ONLY */
|
|
||||||
|
|
||||||
/* Execute a TB, and fix up the CPU state afterwards if necessary */
|
|
||||||
static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb)
|
|
||||||
{
|
|
||||||
CPUArchState *env = cpu->env_ptr;
|
|
||||||
uintptr_t ret;
|
|
||||||
TranslationBlock *last_tb;
|
|
||||||
int tb_exit;
|
|
||||||
uint8_t *tb_ptr = itb->tc_ptr;
|
|
||||||
|
|
||||||
qemu_log_mask_and_addr(CPU_LOG_EXEC, itb->pc,
|
|
||||||
"Trace %p [%d: " TARGET_FMT_lx "] %s\n",
|
|
||||||
itb->tc_ptr, cpu->cpu_index, itb->pc,
|
|
||||||
lookup_symbol(itb->pc));
|
|
||||||
|
|
||||||
#if defined(DEBUG_DISAS)
|
|
||||||
if (qemu_loglevel_mask(CPU_LOG_TB_CPU)
|
|
||||||
&& qemu_log_in_addr_range(itb->pc)) {
|
|
||||||
qemu_log_lock();
|
|
||||||
#if defined(TARGET_I386)
|
|
||||||
log_cpu_state(cpu, CPU_DUMP_CCOP);
|
|
||||||
#else
|
|
||||||
log_cpu_state(cpu, 0);
|
|
||||||
#endif
|
|
||||||
qemu_log_unlock();
|
|
||||||
}
|
|
||||||
#endif /* DEBUG_DISAS */
|
|
||||||
|
|
||||||
cpu->can_do_io = !use_icount;
|
|
||||||
ret = tcg_qemu_tb_exec(env, tb_ptr);
|
|
||||||
cpu->can_do_io = 1;
|
|
||||||
last_tb = (TranslationBlock *)(ret & ~TB_EXIT_MASK);
|
|
||||||
tb_exit = ret & TB_EXIT_MASK;
|
|
||||||
trace_exec_tb_exit(last_tb, tb_exit);
|
|
||||||
|
|
||||||
if (tb_exit > TB_EXIT_IDX1) {
|
|
||||||
/* We didn't start executing this TB (eg because the instruction
|
|
||||||
* counter hit zero); we must restore the guest PC to the address
|
|
||||||
* of the start of the TB.
|
|
||||||
*/
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
qemu_log_mask_and_addr(CPU_LOG_EXEC, last_tb->pc,
|
|
||||||
"Stopped execution of TB chain before %p ["
|
|
||||||
TARGET_FMT_lx "] %s\n",
|
|
||||||
last_tb->tc_ptr, last_tb->pc,
|
|
||||||
lookup_symbol(last_tb->pc));
|
|
||||||
if (cc->synchronize_from_tb) {
|
|
||||||
cc->synchronize_from_tb(cpu, last_tb);
|
|
||||||
} else {
|
|
||||||
assert(cc->set_pc);
|
|
||||||
cc->set_pc(cpu, last_tb->pc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
|
||||||
/* Execute the code without caching the generated code. An interpreter
|
|
||||||
could be used if available. */
|
|
||||||
static void cpu_exec_nocache(CPUState *cpu, int max_cycles,
|
|
||||||
TranslationBlock *orig_tb, bool ignore_icount)
|
|
||||||
{
|
|
||||||
TranslationBlock *tb;
|
|
||||||
|
|
||||||
/* Should never happen.
|
|
||||||
We only end up here when an existing TB is too long. */
|
|
||||||
if (max_cycles > CF_COUNT_MASK)
|
|
||||||
max_cycles = CF_COUNT_MASK;
|
|
||||||
|
|
||||||
tb_lock();
|
|
||||||
tb = tb_gen_code(cpu, orig_tb->pc, orig_tb->cs_base, orig_tb->flags,
|
|
||||||
max_cycles | CF_NOCACHE
|
|
||||||
| (ignore_icount ? CF_IGNORE_ICOUNT : 0));
|
|
||||||
tb->orig_tb = orig_tb;
|
|
||||||
tb_unlock();
|
|
||||||
|
|
||||||
/* execute the generated code */
|
|
||||||
trace_exec_tb_nocache(tb, tb->pc);
|
|
||||||
cpu_tb_exec(cpu, tb);
|
|
||||||
|
|
||||||
tb_lock();
|
|
||||||
tb_phys_invalidate(tb, -1);
|
|
||||||
tb_free(tb);
|
|
||||||
tb_unlock();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void cpu_exec_step(CPUState *cpu)
|
|
||||||
{
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
CPUArchState *env = (CPUArchState *)cpu->env_ptr;
|
|
||||||
TranslationBlock *tb;
|
|
||||||
target_ulong cs_base, pc;
|
|
||||||
uint32_t flags;
|
|
||||||
|
|
||||||
cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
|
|
||||||
if (sigsetjmp(cpu->jmp_env, 0) == 0) {
|
|
||||||
mmap_lock();
|
|
||||||
tb_lock();
|
|
||||||
tb = tb_gen_code(cpu, pc, cs_base, flags,
|
|
||||||
1 | CF_NOCACHE | CF_IGNORE_ICOUNT);
|
|
||||||
tb->orig_tb = NULL;
|
|
||||||
tb_unlock();
|
|
||||||
mmap_unlock();
|
|
||||||
|
|
||||||
cc->cpu_exec_enter(cpu);
|
|
||||||
/* execute the generated code */
|
|
||||||
trace_exec_tb_nocache(tb, pc);
|
|
||||||
cpu_tb_exec(cpu, tb);
|
|
||||||
cc->cpu_exec_exit(cpu);
|
|
||||||
|
|
||||||
tb_lock();
|
|
||||||
tb_phys_invalidate(tb, -1);
|
|
||||||
tb_free(tb);
|
|
||||||
tb_unlock();
|
|
||||||
} else {
|
|
||||||
/* We may have exited due to another problem here, so we need
|
|
||||||
* to reset any tb_locks we may have taken but didn't release.
|
|
||||||
* The mmap_lock is dropped by tb_gen_code if it runs out of
|
|
||||||
* memory.
|
|
||||||
*/
|
|
||||||
#ifndef CONFIG_SOFTMMU
|
|
||||||
tcg_debug_assert(!have_mmap_lock());
|
|
||||||
#endif
|
|
||||||
tb_lock_reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cpu_exec_step_atomic(CPUState *cpu)
|
|
||||||
{
|
|
||||||
start_exclusive();
|
|
||||||
|
|
||||||
/* Since we got here, we know that parallel_cpus must be true. */
|
|
||||||
parallel_cpus = false;
|
|
||||||
cpu_exec_step(cpu);
|
|
||||||
parallel_cpus = true;
|
|
||||||
|
|
||||||
end_exclusive();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct tb_desc {
|
|
||||||
target_ulong pc;
|
|
||||||
target_ulong cs_base;
|
|
||||||
CPUArchState *env;
|
|
||||||
tb_page_addr_t phys_page1;
|
|
||||||
uint32_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool tb_cmp(const void *p, const void *d)
|
|
||||||
{
|
|
||||||
const TranslationBlock *tb = p;
|
|
||||||
const struct tb_desc *desc = d;
|
|
||||||
|
|
||||||
if (tb->pc == desc->pc &&
|
|
||||||
tb->page_addr[0] == desc->phys_page1 &&
|
|
||||||
tb->cs_base == desc->cs_base &&
|
|
||||||
tb->flags == desc->flags &&
|
|
||||||
!atomic_read(&tb->invalid)) {
|
|
||||||
/* check next page if needed */
|
|
||||||
if (tb->page_addr[1] == -1) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
tb_page_addr_t phys_page2;
|
|
||||||
target_ulong virt_page2;
|
|
||||||
|
|
||||||
virt_page2 = (desc->pc & TARGET_PAGE_MASK) + TARGET_PAGE_SIZE;
|
|
||||||
phys_page2 = get_page_addr_code(desc->env, virt_page2);
|
|
||||||
if (tb->page_addr[1] == phys_page2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TranslationBlock *tb_htable_lookup(CPUState *cpu, target_ulong pc,
|
|
||||||
target_ulong cs_base, uint32_t flags)
|
|
||||||
{
|
|
||||||
tb_page_addr_t phys_pc;
|
|
||||||
struct tb_desc desc;
|
|
||||||
uint32_t h;
|
|
||||||
|
|
||||||
desc.env = (CPUArchState *)cpu->env_ptr;
|
|
||||||
desc.cs_base = cs_base;
|
|
||||||
desc.flags = flags;
|
|
||||||
desc.pc = pc;
|
|
||||||
phys_pc = get_page_addr_code(desc.env, pc);
|
|
||||||
desc.phys_page1 = phys_pc & TARGET_PAGE_MASK;
|
|
||||||
h = tb_hash_func(phys_pc, pc, flags);
|
|
||||||
return qht_lookup(&tcg_ctx.tb_ctx.htable, tb_cmp, &desc, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline TranslationBlock *tb_find(CPUState *cpu,
|
|
||||||
TranslationBlock *last_tb,
|
|
||||||
int tb_exit)
|
|
||||||
{
|
|
||||||
CPUArchState *env = (CPUArchState *)cpu->env_ptr;
|
|
||||||
TranslationBlock *tb;
|
|
||||||
target_ulong cs_base, pc;
|
|
||||||
uint32_t flags;
|
|
||||||
bool have_tb_lock = false;
|
|
||||||
|
|
||||||
/* we record a subset of the CPU state. It will
|
|
||||||
always be the same before a given translated block
|
|
||||||
is executed. */
|
|
||||||
cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
|
|
||||||
tb = atomic_rcu_read(&cpu->tb_jmp_cache[tb_jmp_cache_hash_func(pc)]);
|
|
||||||
if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base ||
|
|
||||||
tb->flags != flags)) {
|
|
||||||
tb = tb_htable_lookup(cpu, pc, cs_base, flags);
|
|
||||||
if (!tb) {
|
|
||||||
|
|
||||||
/* mmap_lock is needed by tb_gen_code, and mmap_lock must be
|
|
||||||
* taken outside tb_lock. As system emulation is currently
|
|
||||||
* single threaded the locks are NOPs.
|
|
||||||
*/
|
|
||||||
mmap_lock();
|
|
||||||
tb_lock();
|
|
||||||
have_tb_lock = true;
|
|
||||||
|
|
||||||
/* There's a chance that our desired tb has been translated while
|
|
||||||
* taking the locks so we check again inside the lock.
|
|
||||||
*/
|
|
||||||
tb = tb_htable_lookup(cpu, pc, cs_base, flags);
|
|
||||||
if (!tb) {
|
|
||||||
/* if no translated code available, then translate it now */
|
|
||||||
tb = tb_gen_code(cpu, pc, cs_base, flags, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
mmap_unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We add the TB in the virtual pc hash table for the fast lookup */
|
|
||||||
atomic_set(&cpu->tb_jmp_cache[tb_jmp_cache_hash_func(pc)], tb);
|
|
||||||
}
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
|
||||||
/* We don't take care of direct jumps when address mapping changes in
|
|
||||||
* system emulation. So it's not safe to make a direct jump to a TB
|
|
||||||
* spanning two pages because the mapping for the second page can change.
|
|
||||||
*/
|
|
||||||
if (tb->page_addr[1] != -1) {
|
|
||||||
last_tb = NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/* See if we can patch the calling TB. */
|
|
||||||
if (last_tb && !qemu_loglevel_mask(CPU_LOG_TB_NOCHAIN)) {
|
|
||||||
if (!have_tb_lock) {
|
|
||||||
tb_lock();
|
|
||||||
have_tb_lock = true;
|
|
||||||
}
|
|
||||||
if (!tb->invalid) {
|
|
||||||
tb_add_jump(last_tb, tb_exit, tb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (have_tb_lock) {
|
|
||||||
tb_unlock();
|
|
||||||
}
|
|
||||||
return tb;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool cpu_handle_halt(CPUState *cpu)
|
|
||||||
{
|
|
||||||
if (cpu->halted) {
|
|
||||||
#if defined(TARGET_I386) && !defined(CONFIG_USER_ONLY)
|
|
||||||
if ((cpu->interrupt_request & CPU_INTERRUPT_POLL)
|
|
||||||
&& replay_interrupt()) {
|
|
||||||
X86CPU *x86_cpu = X86_CPU(cpu);
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
apic_poll_irq(x86_cpu->apic_state);
|
|
||||||
cpu_reset_interrupt(cpu, CPU_INTERRUPT_POLL);
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!cpu_has_work(cpu)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
cpu->halted = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void cpu_handle_debug_exception(CPUState *cpu)
|
|
||||||
{
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
CPUWatchpoint *wp;
|
|
||||||
|
|
||||||
if (!cpu->watchpoint_hit) {
|
|
||||||
QTAILQ_FOREACH(wp, &cpu->watchpoints, entry) {
|
|
||||||
wp->flags &= ~BP_WATCHPOINT_HIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cc->debug_excp_handler(cpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool cpu_handle_exception(CPUState *cpu, int *ret)
|
|
||||||
{
|
|
||||||
if (cpu->exception_index >= 0) {
|
|
||||||
if (cpu->exception_index >= EXCP_INTERRUPT) {
|
|
||||||
/* exit request from the cpu execution loop */
|
|
||||||
*ret = cpu->exception_index;
|
|
||||||
if (*ret == EXCP_DEBUG) {
|
|
||||||
cpu_handle_debug_exception(cpu);
|
|
||||||
}
|
|
||||||
cpu->exception_index = -1;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
#if defined(CONFIG_USER_ONLY)
|
|
||||||
/* if user mode only, we simulate a fake exception
|
|
||||||
which will be handled outside the cpu execution
|
|
||||||
loop */
|
|
||||||
#if defined(TARGET_I386)
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
cc->do_interrupt(cpu);
|
|
||||||
#endif
|
|
||||||
*ret = cpu->exception_index;
|
|
||||||
cpu->exception_index = -1;
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
if (replay_exception()) {
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
cc->do_interrupt(cpu);
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
cpu->exception_index = -1;
|
|
||||||
} else if (!replay_has_interrupt()) {
|
|
||||||
/* give a chance to iothread in replay mode */
|
|
||||||
*ret = EXCP_INTERRUPT;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
|
||||||
} else if (replay_has_exception()
|
|
||||||
&& cpu->icount_decr.u16.low + cpu->icount_extra == 0) {
|
|
||||||
/* try to cause an exception pending in the log */
|
|
||||||
cpu_exec_nocache(cpu, 1, tb_find(cpu, NULL, 0), true);
|
|
||||||
*ret = -1;
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool cpu_handle_interrupt(CPUState *cpu,
|
|
||||||
TranslationBlock **last_tb)
|
|
||||||
{
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
|
|
||||||
if (unlikely(atomic_read(&cpu->interrupt_request))) {
|
|
||||||
int interrupt_request;
|
|
||||||
qemu_mutex_lock_iothread();
|
|
||||||
interrupt_request = cpu->interrupt_request;
|
|
||||||
if (unlikely(cpu->singlestep_enabled & SSTEP_NOIRQ)) {
|
|
||||||
/* Mask out external interrupts for this step. */
|
|
||||||
interrupt_request &= ~CPU_INTERRUPT_SSTEP_MASK;
|
|
||||||
}
|
|
||||||
if (interrupt_request & CPU_INTERRUPT_DEBUG) {
|
|
||||||
cpu->interrupt_request &= ~CPU_INTERRUPT_DEBUG;
|
|
||||||
cpu->exception_index = EXCP_DEBUG;
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (replay_mode == REPLAY_MODE_PLAY && !replay_has_interrupt()) {
|
|
||||||
/* Do nothing */
|
|
||||||
} else if (interrupt_request & CPU_INTERRUPT_HALT) {
|
|
||||||
replay_interrupt();
|
|
||||||
cpu->interrupt_request &= ~CPU_INTERRUPT_HALT;
|
|
||||||
cpu->halted = 1;
|
|
||||||
cpu->exception_index = EXCP_HLT;
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#if defined(TARGET_I386)
|
|
||||||
else if (interrupt_request & CPU_INTERRUPT_INIT) {
|
|
||||||
X86CPU *x86_cpu = X86_CPU(cpu);
|
|
||||||
CPUArchState *env = &x86_cpu->env;
|
|
||||||
replay_interrupt();
|
|
||||||
cpu_svm_check_intercept_param(env, SVM_EXIT_INIT, 0, 0);
|
|
||||||
do_cpu_init(x86_cpu);
|
|
||||||
cpu->exception_index = EXCP_HALTED;
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
else if (interrupt_request & CPU_INTERRUPT_RESET) {
|
|
||||||
replay_interrupt();
|
|
||||||
cpu_reset(cpu);
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/* The target hook has 3 exit conditions:
|
|
||||||
False when the interrupt isn't processed,
|
|
||||||
True when it is, and we should restart on a new TB,
|
|
||||||
and via longjmp via cpu_loop_exit. */
|
|
||||||
else {
|
|
||||||
if (cc->cpu_exec_interrupt(cpu, interrupt_request)) {
|
|
||||||
replay_interrupt();
|
|
||||||
*last_tb = NULL;
|
|
||||||
}
|
|
||||||
/* The target hook may have updated the 'cpu->interrupt_request';
|
|
||||||
* reload the 'interrupt_request' value */
|
|
||||||
interrupt_request = cpu->interrupt_request;
|
|
||||||
}
|
|
||||||
if (interrupt_request & CPU_INTERRUPT_EXITTB) {
|
|
||||||
cpu->interrupt_request &= ~CPU_INTERRUPT_EXITTB;
|
|
||||||
/* ensure that no TB jump will be modified as
|
|
||||||
the program flow was changed */
|
|
||||||
*last_tb = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we exit via cpu_loop_exit/longjmp it is reset in cpu_exec */
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Finally, check if we need to exit to the main loop. */
|
|
||||||
if (unlikely(atomic_read(&cpu->exit_request)
|
|
||||||
|| (use_icount && cpu->icount_decr.u16.low + cpu->icount_extra == 0))) {
|
|
||||||
atomic_set(&cpu->exit_request, 0);
|
|
||||||
cpu->exception_index = EXCP_INTERRUPT;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void cpu_loop_exec_tb(CPUState *cpu, TranslationBlock *tb,
|
|
||||||
TranslationBlock **last_tb, int *tb_exit)
|
|
||||||
{
|
|
||||||
uintptr_t ret;
|
|
||||||
int32_t insns_left;
|
|
||||||
|
|
||||||
trace_exec_tb(tb, tb->pc);
|
|
||||||
ret = cpu_tb_exec(cpu, tb);
|
|
||||||
tb = (TranslationBlock *)(ret & ~TB_EXIT_MASK);
|
|
||||||
*tb_exit = ret & TB_EXIT_MASK;
|
|
||||||
if (*tb_exit != TB_EXIT_REQUESTED) {
|
|
||||||
*last_tb = tb;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
*last_tb = NULL;
|
|
||||||
insns_left = atomic_read(&cpu->icount_decr.u32);
|
|
||||||
atomic_set(&cpu->icount_decr.u16.high, 0);
|
|
||||||
if (insns_left < 0) {
|
|
||||||
/* Something asked us to stop executing chained TBs; just
|
|
||||||
* continue round the main loop. Whatever requested the exit
|
|
||||||
* will also have set something else (eg exit_request or
|
|
||||||
* interrupt_request) which we will handle next time around
|
|
||||||
* the loop. But we need to ensure the zeroing of icount_decr
|
|
||||||
* comes before the next read of cpu->exit_request
|
|
||||||
* or cpu->interrupt_request.
|
|
||||||
*/
|
|
||||||
smp_mb();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Instruction counter expired. */
|
|
||||||
assert(use_icount);
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
|
||||||
/* Ensure global icount has gone forward */
|
|
||||||
cpu_update_icount(cpu);
|
|
||||||
/* Refill decrementer and continue execution. */
|
|
||||||
insns_left = MIN(0xffff, cpu->icount_budget);
|
|
||||||
cpu->icount_decr.u16.low = insns_left;
|
|
||||||
cpu->icount_extra = cpu->icount_budget - insns_left;
|
|
||||||
if (!cpu->icount_extra) {
|
|
||||||
/* Execute any remaining instructions, then let the main loop
|
|
||||||
* handle the next event.
|
|
||||||
*/
|
|
||||||
if (insns_left > 0) {
|
|
||||||
cpu_exec_nocache(cpu, insns_left, tb, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* main execution loop */
|
|
||||||
|
|
||||||
int cpu_exec(CPUState *cpu)
|
|
||||||
{
|
|
||||||
CPUClass *cc = CPU_GET_CLASS(cpu);
|
|
||||||
int ret;
|
|
||||||
SyncClocks sc = { 0 };
|
|
||||||
|
|
||||||
/* replay_interrupt may need current_cpu */
|
|
||||||
current_cpu = cpu;
|
|
||||||
|
|
||||||
if (cpu_handle_halt(cpu)) {
|
|
||||||
return EXCP_HALTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
|
|
||||||
cc->cpu_exec_enter(cpu);
|
|
||||||
|
|
||||||
/* Calculate difference between guest clock and host clock.
|
|
||||||
* This delay includes the delay of the last cycle, so
|
|
||||||
* what we have to do is sleep until it is 0. As for the
|
|
||||||
* advance/delay we gain here, we try to fix it next time.
|
|
||||||
*/
|
|
||||||
init_delay_params(&sc, cpu);
|
|
||||||
|
|
||||||
/* prepare setjmp context for exception handling */
|
|
||||||
if (sigsetjmp(cpu->jmp_env, 0) != 0) {
|
|
||||||
#if defined(__clang__) || !QEMU_GNUC_PREREQ(4, 6)
|
|
||||||
/* Some compilers wrongly smash all local variables after
|
|
||||||
* siglongjmp. There were bug reports for gcc 4.5.0 and clang.
|
|
||||||
* Reload essential local variables here for those compilers.
|
|
||||||
* Newer versions of gcc would complain about this code (-Wclobbered). */
|
|
||||||
cpu = current_cpu;
|
|
||||||
cc = CPU_GET_CLASS(cpu);
|
|
||||||
#else /* buggy compiler */
|
|
||||||
/* Assert that the compiler does not smash local variables. */
|
|
||||||
g_assert(cpu == current_cpu);
|
|
||||||
g_assert(cc == CPU_GET_CLASS(cpu));
|
|
||||||
#endif /* buggy compiler */
|
|
||||||
cpu->can_do_io = 1;
|
|
||||||
tb_lock_reset();
|
|
||||||
if (qemu_mutex_iothread_locked()) {
|
|
||||||
qemu_mutex_unlock_iothread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* if an exception is pending, we execute it here */
|
|
||||||
while (!cpu_handle_exception(cpu, &ret)) {
|
|
||||||
TranslationBlock *last_tb = NULL;
|
|
||||||
int tb_exit = 0;
|
|
||||||
|
|
||||||
while (!cpu_handle_interrupt(cpu, &last_tb)) {
|
|
||||||
TranslationBlock *tb = tb_find(cpu, last_tb, tb_exit);
|
|
||||||
cpu_loop_exec_tb(cpu, tb, &last_tb, &tb_exit);
|
|
||||||
/* Try to align the host and virtual clocks
|
|
||||||
if the guest is in advance */
|
|
||||||
align_clocks(&sc, cpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cc->cpu_exec_exit(cpu);
|
|
||||||
rcu_read_unlock();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
1051
accel/tcg/cputlb.c
1051
accel/tcg/cputlb.c
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU System Emulator, accelerator interfaces
|
|
||||||
*
|
|
||||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
|
||||||
* Copyright (c) 2014 Red Hat Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/accel.h"
|
|
||||||
#include "sysemu/sysemu.h"
|
|
||||||
#include "qom/object.h"
|
|
||||||
|
|
||||||
int tcg_tb_size;
|
|
||||||
static bool tcg_allowed = true;
|
|
||||||
|
|
||||||
static int tcg_init(MachineState *ms)
|
|
||||||
{
|
|
||||||
tcg_exec_init(tcg_tb_size * 1024 * 1024);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tcg_accel_class_init(ObjectClass *oc, void *data)
|
|
||||||
{
|
|
||||||
AccelClass *ac = ACCEL_CLASS(oc);
|
|
||||||
ac->name = "tcg";
|
|
||||||
ac->init_machine = tcg_init;
|
|
||||||
ac->allowed = &tcg_allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define TYPE_TCG_ACCEL ACCEL_CLASS_NAME("tcg")
|
|
||||||
|
|
||||||
static const TypeInfo tcg_accel_type = {
|
|
||||||
.name = TYPE_TCG_ACCEL,
|
|
||||||
.parent = TYPE_ACCEL,
|
|
||||||
.class_init = tcg_accel_class_init,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void register_accel_types(void)
|
|
||||||
{
|
|
||||||
type_register_static(&tcg_accel_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_init(register_accel_types);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Trace events for debugging and performance instrumentation
|
|
||||||
|
|
||||||
# TCG related tracing (mostly disabled by default)
|
|
||||||
# cpu-exec.c
|
|
||||||
disable exec_tb(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
|
|
||||||
disable exec_tb_nocache(void *tb, uintptr_t pc) "tb:%p pc=0x%"PRIxPTR
|
|
||||||
disable exec_tb_exit(void *last_tb, unsigned int flags) "tb:%p flags=%x"
|
|
||||||
|
|
||||||
# translate-all.c
|
|
||||||
translate_block(void *tb, uintptr_t pc, uint8_t *tb_code) "tb:%p, pc:0x%"PRIxPTR", tb_code:%p"
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Host code generation common components
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Peter Crosthwaite <crosthwaite.peter@gmail.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "qom/cpu.h"
|
|
||||||
#include "sysemu/cpus.h"
|
|
||||||
#include "qemu/main-loop.h"
|
|
||||||
|
|
||||||
uintptr_t qemu_real_host_page_size;
|
|
||||||
intptr_t qemu_real_host_page_mask;
|
|
||||||
|
|
||||||
#ifndef CONFIG_USER_ONLY
|
|
||||||
/* mask must never be zero, except for A20 change call */
|
|
||||||
static void tcg_handle_interrupt(CPUState *cpu, int mask)
|
|
||||||
{
|
|
||||||
int old_mask;
|
|
||||||
g_assert(qemu_mutex_iothread_locked());
|
|
||||||
|
|
||||||
old_mask = cpu->interrupt_request;
|
|
||||||
cpu->interrupt_request |= mask;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If called from iothread context, wake the target cpu in
|
|
||||||
* case its halted.
|
|
||||||
*/
|
|
||||||
if (!qemu_cpu_is_self(cpu)) {
|
|
||||||
qemu_cpu_kick(cpu);
|
|
||||||
} else {
|
|
||||||
cpu->icount_decr.u16.high = -1;
|
|
||||||
if (use_icount &&
|
|
||||||
!cpu->can_do_io
|
|
||||||
&& (mask & ~old_mask) != 0) {
|
|
||||||
cpu_abort(cpu, "Raised interrupt while not in I/O function");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CPUInterruptHandler cpu_interrupt_handler = tcg_handle_interrupt;
|
|
||||||
#endif
|
|
||||||
254
aio-posix.c
Normal file
254
aio-posix.c
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* QEMU aio implementation
|
||||||
|
*
|
||||||
|
* Copyright IBM, Corp. 2008
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Anthony Liguori <aliguori@us.ibm.com>
|
||||||
|
*
|
||||||
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||||
|
* the COPYING file in the top-level directory.
|
||||||
|
*
|
||||||
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
||||||
|
* GNU GPL, version 2 or (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "block/block.h"
|
||||||
|
#include "qemu/queue.h"
|
||||||
|
#include "qemu/sockets.h"
|
||||||
|
|
||||||
|
struct AioHandler
|
||||||
|
{
|
||||||
|
GPollFD pfd;
|
||||||
|
IOHandler *io_read;
|
||||||
|
IOHandler *io_write;
|
||||||
|
int deleted;
|
||||||
|
int pollfds_idx;
|
||||||
|
void *opaque;
|
||||||
|
QLIST_ENTRY(AioHandler) node;
|
||||||
|
};
|
||||||
|
|
||||||
|
static AioHandler *find_aio_handler(AioContext *ctx, int fd)
|
||||||
|
{
|
||||||
|
AioHandler *node;
|
||||||
|
|
||||||
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
|
if (node->pfd.fd == fd)
|
||||||
|
if (!node->deleted)
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void aio_set_fd_handler(AioContext *ctx,
|
||||||
|
int fd,
|
||||||
|
IOHandler *io_read,
|
||||||
|
IOHandler *io_write,
|
||||||
|
void *opaque)
|
||||||
|
{
|
||||||
|
AioHandler *node;
|
||||||
|
|
||||||
|
node = find_aio_handler(ctx, fd);
|
||||||
|
|
||||||
|
/* Are we deleting the fd handler? */
|
||||||
|
if (!io_read && !io_write) {
|
||||||
|
if (node) {
|
||||||
|
g_source_remove_poll(&ctx->source, &node->pfd);
|
||||||
|
|
||||||
|
/* If the lock is held, just mark the node as deleted */
|
||||||
|
if (ctx->walking_handlers) {
|
||||||
|
node->deleted = 1;
|
||||||
|
node->pfd.revents = 0;
|
||||||
|
} else {
|
||||||
|
/* Otherwise, delete it for real. We can't just mark it as
|
||||||
|
* deleted because deleted nodes are only cleaned up after
|
||||||
|
* releasing the walking_handlers lock.
|
||||||
|
*/
|
||||||
|
QLIST_REMOVE(node, node);
|
||||||
|
g_free(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (node == NULL) {
|
||||||
|
/* Alloc and insert if it's not already there */
|
||||||
|
node = g_new0(AioHandler, 1);
|
||||||
|
node->pfd.fd = fd;
|
||||||
|
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||||
|
|
||||||
|
g_source_add_poll(&ctx->source, &node->pfd);
|
||||||
|
}
|
||||||
|
/* Update handler with latest information */
|
||||||
|
node->io_read = io_read;
|
||||||
|
node->io_write = io_write;
|
||||||
|
node->opaque = opaque;
|
||||||
|
node->pollfds_idx = -1;
|
||||||
|
|
||||||
|
node->pfd.events = (io_read ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0);
|
||||||
|
node->pfd.events |= (io_write ? G_IO_OUT | G_IO_ERR : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_notify(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aio_set_event_notifier(AioContext *ctx,
|
||||||
|
EventNotifier *notifier,
|
||||||
|
EventNotifierHandler *io_read)
|
||||||
|
{
|
||||||
|
aio_set_fd_handler(ctx, event_notifier_get_fd(notifier),
|
||||||
|
(IOHandler *)io_read, NULL, notifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool aio_prepare(AioContext *ctx)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool aio_pending(AioContext *ctx)
|
||||||
|
{
|
||||||
|
AioHandler *node;
|
||||||
|
|
||||||
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
|
int revents;
|
||||||
|
|
||||||
|
revents = node->pfd.revents & node->pfd.events;
|
||||||
|
if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR) && node->io_read) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (revents & (G_IO_OUT | G_IO_ERR) && node->io_write) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool aio_dispatch(AioContext *ctx)
|
||||||
|
{
|
||||||
|
AioHandler *node;
|
||||||
|
bool progress = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If there are callbacks left that have been queued, we need to call them.
|
||||||
|
* Do not call select in this case, because it is possible that the caller
|
||||||
|
* does not need a complete flush (as is the case for aio_poll loops).
|
||||||
|
*/
|
||||||
|
if (aio_bh_poll(ctx)) {
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have to walk very carefully in case aio_set_fd_handler is
|
||||||
|
* called while we're walking.
|
||||||
|
*/
|
||||||
|
node = QLIST_FIRST(&ctx->aio_handlers);
|
||||||
|
while (node) {
|
||||||
|
AioHandler *tmp;
|
||||||
|
int revents;
|
||||||
|
|
||||||
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
|
revents = node->pfd.revents & node->pfd.events;
|
||||||
|
node->pfd.revents = 0;
|
||||||
|
|
||||||
|
if (!node->deleted &&
|
||||||
|
(revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)) &&
|
||||||
|
node->io_read) {
|
||||||
|
node->io_read(node->opaque);
|
||||||
|
|
||||||
|
/* aio_notify() does not count as progress */
|
||||||
|
if (node->opaque != &ctx->notifier) {
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!node->deleted &&
|
||||||
|
(revents & (G_IO_OUT | G_IO_ERR)) &&
|
||||||
|
node->io_write) {
|
||||||
|
node->io_write(node->opaque);
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = node;
|
||||||
|
node = QLIST_NEXT(node, node);
|
||||||
|
|
||||||
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
|
if (!ctx->walking_handlers && tmp->deleted) {
|
||||||
|
QLIST_REMOVE(tmp, node);
|
||||||
|
g_free(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run our timers */
|
||||||
|
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
|
{
|
||||||
|
AioHandler *node;
|
||||||
|
bool was_dispatching;
|
||||||
|
int ret;
|
||||||
|
bool progress;
|
||||||
|
|
||||||
|
was_dispatching = ctx->dispatching;
|
||||||
|
progress = false;
|
||||||
|
|
||||||
|
/* aio_notify can avoid the expensive event_notifier_set if
|
||||||
|
* everything (file descriptors, bottom halves, timers) will
|
||||||
|
* be re-evaluated before the next blocking poll(). This is
|
||||||
|
* already true when aio_poll is called with blocking == false;
|
||||||
|
* if blocking == true, it is only true after poll() returns.
|
||||||
|
*
|
||||||
|
* If we're in a nested event loop, ctx->dispatching might be true.
|
||||||
|
* In that case we can restore it just before returning, but we
|
||||||
|
* have to clear it now.
|
||||||
|
*/
|
||||||
|
aio_set_dispatching(ctx, !blocking);
|
||||||
|
|
||||||
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
|
g_array_set_size(ctx->pollfds, 0);
|
||||||
|
|
||||||
|
/* fill pollfds */
|
||||||
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
|
node->pollfds_idx = -1;
|
||||||
|
if (!node->deleted && node->pfd.events) {
|
||||||
|
GPollFD pfd = {
|
||||||
|
.fd = node->pfd.fd,
|
||||||
|
.events = node->pfd.events,
|
||||||
|
};
|
||||||
|
node->pollfds_idx = ctx->pollfds->len;
|
||||||
|
g_array_append_val(ctx->pollfds, pfd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->walking_handlers--;
|
||||||
|
|
||||||
|
/* wait until next event */
|
||||||
|
ret = qemu_poll_ns((GPollFD *)ctx->pollfds->data,
|
||||||
|
ctx->pollfds->len,
|
||||||
|
blocking ? aio_compute_timeout(ctx) : 0);
|
||||||
|
|
||||||
|
/* if we have any readable fds, dispatch event */
|
||||||
|
if (ret > 0) {
|
||||||
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
|
if (node->pollfds_idx != -1) {
|
||||||
|
GPollFD *pfd = &g_array_index(ctx->pollfds, GPollFD,
|
||||||
|
node->pollfds_idx);
|
||||||
|
node->pfd.revents = pfd->revents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run dispatch even if there were no readable fds to run timers */
|
||||||
|
aio_set_dispatching(ctx, true);
|
||||||
|
if (aio_dispatch(ctx)) {
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_set_dispatching(ctx, was_dispatching);
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
@@ -15,13 +15,10 @@
|
|||||||
* GNU GPL, version 2 or (at your option) any later version.
|
* GNU GPL, version 2 or (at your option) any later version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
#include "qemu/queue.h"
|
#include "qemu/queue.h"
|
||||||
#include "qemu/sockets.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu/rcu_queue.h"
|
|
||||||
|
|
||||||
struct AioHandler {
|
struct AioHandler {
|
||||||
EventNotifier *e;
|
EventNotifier *e;
|
||||||
@@ -31,22 +28,18 @@ struct AioHandler {
|
|||||||
GPollFD pfd;
|
GPollFD pfd;
|
||||||
int deleted;
|
int deleted;
|
||||||
void *opaque;
|
void *opaque;
|
||||||
bool is_external;
|
|
||||||
QLIST_ENTRY(AioHandler) node;
|
QLIST_ENTRY(AioHandler) node;
|
||||||
};
|
};
|
||||||
|
|
||||||
void aio_set_fd_handler(AioContext *ctx,
|
void aio_set_fd_handler(AioContext *ctx,
|
||||||
int fd,
|
int fd,
|
||||||
bool is_external,
|
|
||||||
IOHandler *io_read,
|
IOHandler *io_read,
|
||||||
IOHandler *io_write,
|
IOHandler *io_write,
|
||||||
AioPollFn *io_poll,
|
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
/* fd is a SOCKET in our case */
|
/* fd is a SOCKET in our case */
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
|
|
||||||
qemu_lockcnt_lock(&ctx->list_lock);
|
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
if (node->pfd.fd == fd && !node->deleted) {
|
if (node->pfd.fd == fd && !node->deleted) {
|
||||||
break;
|
break;
|
||||||
@@ -56,14 +49,14 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
/* Are we deleting the fd handler? */
|
/* Are we deleting the fd handler? */
|
||||||
if (!io_read && !io_write) {
|
if (!io_read && !io_write) {
|
||||||
if (node) {
|
if (node) {
|
||||||
/* If aio_poll is in progress, just mark the node as deleted */
|
/* If the lock is held, just mark the node as deleted */
|
||||||
if (qemu_lockcnt_count(&ctx->list_lock)) {
|
if (ctx->walking_handlers) {
|
||||||
node->deleted = 1;
|
node->deleted = 1;
|
||||||
node->pfd.revents = 0;
|
node->pfd.revents = 0;
|
||||||
} else {
|
} else {
|
||||||
/* Otherwise, delete it for real. We can't just mark it as
|
/* Otherwise, delete it for real. We can't just mark it as
|
||||||
* deleted because deleted nodes are only cleaned up after
|
* deleted because deleted nodes are only cleaned up after
|
||||||
* releasing the list_lock.
|
* releasing the walking_handlers lock.
|
||||||
*/
|
*/
|
||||||
QLIST_REMOVE(node, node);
|
QLIST_REMOVE(node, node);
|
||||||
g_free(node);
|
g_free(node);
|
||||||
@@ -76,7 +69,7 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
/* Alloc and insert if it's not already there */
|
/* Alloc and insert if it's not already there */
|
||||||
node = g_new0(AioHandler, 1);
|
node = g_new0(AioHandler, 1);
|
||||||
node->pfd.fd = fd;
|
node->pfd.fd = fd;
|
||||||
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
|
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
node->pfd.events = 0;
|
node->pfd.events = 0;
|
||||||
@@ -93,7 +86,6 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
node->opaque = opaque;
|
node->opaque = opaque;
|
||||||
node->io_read = io_read;
|
node->io_read = io_read;
|
||||||
node->io_write = io_write;
|
node->io_write = io_write;
|
||||||
node->is_external = is_external;
|
|
||||||
|
|
||||||
event = event_notifier_get_handle(&ctx->notifier);
|
event = event_notifier_get_handle(&ctx->notifier);
|
||||||
WSAEventSelect(node->pfd.fd, event,
|
WSAEventSelect(node->pfd.fd, event,
|
||||||
@@ -101,26 +93,15 @@ void aio_set_fd_handler(AioContext *ctx,
|
|||||||
FD_CONNECT | FD_WRITE | FD_OOB);
|
FD_CONNECT | FD_WRITE | FD_OOB);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
|
||||||
aio_notify(ctx);
|
aio_notify(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_set_fd_poll(AioContext *ctx, int fd,
|
|
||||||
IOHandler *io_poll_begin,
|
|
||||||
IOHandler *io_poll_end)
|
|
||||||
{
|
|
||||||
/* Not implemented */
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_set_event_notifier(AioContext *ctx,
|
void aio_set_event_notifier(AioContext *ctx,
|
||||||
EventNotifier *e,
|
EventNotifier *e,
|
||||||
bool is_external,
|
EventNotifierHandler *io_notify)
|
||||||
EventNotifierHandler *io_notify,
|
|
||||||
AioPollFn *io_poll)
|
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
|
|
||||||
qemu_lockcnt_lock(&ctx->list_lock);
|
|
||||||
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
if (node->e == e && !node->deleted) {
|
if (node->e == e && !node->deleted) {
|
||||||
break;
|
break;
|
||||||
@@ -132,14 +113,14 @@ void aio_set_event_notifier(AioContext *ctx,
|
|||||||
if (node) {
|
if (node) {
|
||||||
g_source_remove_poll(&ctx->source, &node->pfd);
|
g_source_remove_poll(&ctx->source, &node->pfd);
|
||||||
|
|
||||||
/* aio_poll is in progress, just mark the node as deleted */
|
/* If the lock is held, just mark the node as deleted */
|
||||||
if (qemu_lockcnt_count(&ctx->list_lock)) {
|
if (ctx->walking_handlers) {
|
||||||
node->deleted = 1;
|
node->deleted = 1;
|
||||||
node->pfd.revents = 0;
|
node->pfd.revents = 0;
|
||||||
} else {
|
} else {
|
||||||
/* Otherwise, delete it for real. We can't just mark it as
|
/* Otherwise, delete it for real. We can't just mark it as
|
||||||
* deleted because deleted nodes are only cleaned up after
|
* deleted because deleted nodes are only cleaned up after
|
||||||
* releasing the list_lock.
|
* releasing the walking_handlers lock.
|
||||||
*/
|
*/
|
||||||
QLIST_REMOVE(node, node);
|
QLIST_REMOVE(node, node);
|
||||||
g_free(node);
|
g_free(node);
|
||||||
@@ -152,8 +133,7 @@ void aio_set_event_notifier(AioContext *ctx,
|
|||||||
node->e = e;
|
node->e = e;
|
||||||
node->pfd.fd = (uintptr_t)event_notifier_get_handle(e);
|
node->pfd.fd = (uintptr_t)event_notifier_get_handle(e);
|
||||||
node->pfd.events = G_IO_IN;
|
node->pfd.events = G_IO_IN;
|
||||||
node->is_external = is_external;
|
QLIST_INSERT_HEAD(&ctx->aio_handlers, node, node);
|
||||||
QLIST_INSERT_HEAD_RCU(&ctx->aio_handlers, node, node);
|
|
||||||
|
|
||||||
g_source_add_poll(&ctx->source, &node->pfd);
|
g_source_add_poll(&ctx->source, &node->pfd);
|
||||||
}
|
}
|
||||||
@@ -161,18 +141,9 @@ void aio_set_event_notifier(AioContext *ctx,
|
|||||||
node->io_notify = io_notify;
|
node->io_notify = io_notify;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
|
||||||
aio_notify(ctx);
|
aio_notify(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_set_event_notifier_poll(AioContext *ctx,
|
|
||||||
EventNotifier *notifier,
|
|
||||||
EventNotifierHandler *io_poll_begin,
|
|
||||||
EventNotifierHandler *io_poll_end)
|
|
||||||
{
|
|
||||||
/* Not implemented */
|
|
||||||
}
|
|
||||||
|
|
||||||
bool aio_prepare(AioContext *ctx)
|
bool aio_prepare(AioContext *ctx)
|
||||||
{
|
{
|
||||||
static struct timeval tv0;
|
static struct timeval tv0;
|
||||||
@@ -180,16 +151,10 @@ bool aio_prepare(AioContext *ctx)
|
|||||||
bool have_select_revents = false;
|
bool have_select_revents = false;
|
||||||
fd_set rfds, wfds;
|
fd_set rfds, wfds;
|
||||||
|
|
||||||
/*
|
|
||||||
* We have to walk very carefully in case aio_set_fd_handler is
|
|
||||||
* called while we're walking.
|
|
||||||
*/
|
|
||||||
qemu_lockcnt_inc(&ctx->list_lock);
|
|
||||||
|
|
||||||
/* fill fd sets */
|
/* fill fd sets */
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
FD_ZERO(&wfds);
|
FD_ZERO(&wfds);
|
||||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
if (node->io_read) {
|
if (node->io_read) {
|
||||||
FD_SET ((SOCKET)node->pfd.fd, &rfds);
|
FD_SET ((SOCKET)node->pfd.fd, &rfds);
|
||||||
}
|
}
|
||||||
@@ -199,7 +164,7 @@ bool aio_prepare(AioContext *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (select(0, &rfds, &wfds, NULL, &tv0) > 0) {
|
if (select(0, &rfds, &wfds, NULL, &tv0) > 0) {
|
||||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
node->pfd.revents = 0;
|
node->pfd.revents = 0;
|
||||||
if (FD_ISSET(node->pfd.fd, &rfds)) {
|
if (FD_ISSET(node->pfd.fd, &rfds)) {
|
||||||
node->pfd.revents |= G_IO_IN;
|
node->pfd.revents |= G_IO_IN;
|
||||||
@@ -213,53 +178,45 @@ bool aio_prepare(AioContext *ctx)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_lockcnt_dec(&ctx->list_lock);
|
|
||||||
return have_select_revents;
|
return have_select_revents;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool aio_pending(AioContext *ctx)
|
bool aio_pending(AioContext *ctx)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
/*
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
* We have to walk very carefully in case aio_set_fd_handler is
|
|
||||||
* called while we're walking.
|
|
||||||
*/
|
|
||||||
qemu_lockcnt_inc(&ctx->list_lock);
|
|
||||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
|
||||||
if (node->pfd.revents && node->io_notify) {
|
if (node->pfd.revents && node->io_notify) {
|
||||||
result = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((node->pfd.revents & G_IO_IN) && node->io_read) {
|
if ((node->pfd.revents & G_IO_IN) && node->io_read) {
|
||||||
result = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if ((node->pfd.revents & G_IO_OUT) && node->io_write) {
|
if ((node->pfd.revents & G_IO_OUT) && node->io_write) {
|
||||||
result = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_lockcnt_dec(&ctx->list_lock);
|
return false;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool aio_dispatch_handlers(AioContext *ctx, HANDLE event)
|
static bool aio_dispatch_handlers(AioContext *ctx, HANDLE event)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
bool progress = false;
|
bool progress = false;
|
||||||
AioHandler *tmp;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have to walk very carefully in case aio_set_fd_handler is
|
* We have to walk very carefully in case aio_set_fd_handler is
|
||||||
* called while we're walking.
|
* called while we're walking.
|
||||||
*/
|
*/
|
||||||
QLIST_FOREACH_SAFE_RCU(node, &ctx->aio_handlers, node, tmp) {
|
node = QLIST_FIRST(&ctx->aio_handlers);
|
||||||
|
while (node) {
|
||||||
|
AioHandler *tmp;
|
||||||
int revents = node->pfd.revents;
|
int revents = node->pfd.revents;
|
||||||
|
|
||||||
|
ctx->walking_handlers++;
|
||||||
|
|
||||||
if (!node->deleted &&
|
if (!node->deleted &&
|
||||||
(revents || event_notifier_get_handle(node->e) == event) &&
|
(revents || event_notifier_get_handle(node->e) == event) &&
|
||||||
node->io_notify) {
|
node->io_notify) {
|
||||||
@@ -294,86 +251,85 @@ static bool aio_dispatch_handlers(AioContext *ctx, HANDLE event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node->deleted) {
|
tmp = node;
|
||||||
if (qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
|
node = QLIST_NEXT(node, node);
|
||||||
QLIST_REMOVE(node, node);
|
|
||||||
g_free(node);
|
ctx->walking_handlers--;
|
||||||
qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
|
|
||||||
}
|
if (!ctx->walking_handlers && tmp->deleted) {
|
||||||
|
QLIST_REMOVE(tmp, node);
|
||||||
|
g_free(tmp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_dispatch(AioContext *ctx)
|
bool aio_dispatch(AioContext *ctx)
|
||||||
{
|
{
|
||||||
qemu_lockcnt_inc(&ctx->list_lock);
|
bool progress;
|
||||||
aio_bh_poll(ctx);
|
|
||||||
aio_dispatch_handlers(ctx, INVALID_HANDLE_VALUE);
|
progress = aio_bh_poll(ctx);
|
||||||
qemu_lockcnt_dec(&ctx->list_lock);
|
progress |= aio_dispatch_handlers(ctx, INVALID_HANDLE_VALUE);
|
||||||
timerlistgroup_run_timers(&ctx->tlg);
|
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||||
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool aio_poll(AioContext *ctx, bool blocking)
|
bool aio_poll(AioContext *ctx, bool blocking)
|
||||||
{
|
{
|
||||||
AioHandler *node;
|
AioHandler *node;
|
||||||
HANDLE events[MAXIMUM_WAIT_OBJECTS + 1];
|
HANDLE events[MAXIMUM_WAIT_OBJECTS + 1];
|
||||||
bool progress, have_select_revents, first;
|
bool was_dispatching, progress, have_select_revents, first;
|
||||||
int count;
|
int count;
|
||||||
int timeout;
|
int timeout;
|
||||||
|
|
||||||
|
have_select_revents = aio_prepare(ctx);
|
||||||
|
if (have_select_revents) {
|
||||||
|
blocking = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
was_dispatching = ctx->dispatching;
|
||||||
progress = false;
|
progress = false;
|
||||||
|
|
||||||
/* aio_notify can avoid the expensive event_notifier_set if
|
/* aio_notify can avoid the expensive event_notifier_set if
|
||||||
* everything (file descriptors, bottom halves, timers) will
|
* everything (file descriptors, bottom halves, timers) will
|
||||||
* be re-evaluated before the next blocking poll(). This is
|
* be re-evaluated before the next blocking poll(). This is
|
||||||
* already true when aio_poll is called with blocking == false;
|
* already true when aio_poll is called with blocking == false;
|
||||||
* if blocking == true, it is only true after poll() returns,
|
* if blocking == true, it is only true after poll() returns.
|
||||||
* so disable the optimization now.
|
*
|
||||||
|
* If we're in a nested event loop, ctx->dispatching might be true.
|
||||||
|
* In that case we can restore it just before returning, but we
|
||||||
|
* have to clear it now.
|
||||||
*/
|
*/
|
||||||
if (blocking) {
|
aio_set_dispatching(ctx, !blocking);
|
||||||
atomic_add(&ctx->notify_me, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_lockcnt_inc(&ctx->list_lock);
|
ctx->walking_handlers++;
|
||||||
have_select_revents = aio_prepare(ctx);
|
|
||||||
|
|
||||||
/* fill fd sets */
|
/* fill fd sets */
|
||||||
count = 0;
|
count = 0;
|
||||||
QLIST_FOREACH_RCU(node, &ctx->aio_handlers, node) {
|
QLIST_FOREACH(node, &ctx->aio_handlers, node) {
|
||||||
if (!node->deleted && node->io_notify
|
if (!node->deleted && node->io_notify) {
|
||||||
&& aio_node_check(ctx, node->is_external)) {
|
|
||||||
events[count++] = event_notifier_get_handle(node->e);
|
events[count++] = event_notifier_get_handle(node->e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx->walking_handlers--;
|
||||||
first = true;
|
first = true;
|
||||||
|
|
||||||
/* ctx->notifier is always registered. */
|
/* wait until next event */
|
||||||
assert(count > 0);
|
while (count > 0) {
|
||||||
|
|
||||||
/* Multiple iterations, all of them non-blocking except the first,
|
|
||||||
* may be necessary to process all pending events. After the first
|
|
||||||
* WaitForMultipleObjects call ctx->notify_me will be decremented.
|
|
||||||
*/
|
|
||||||
do {
|
|
||||||
HANDLE event;
|
HANDLE event;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
timeout = blocking && !have_select_revents
|
timeout = blocking
|
||||||
? qemu_timeout_ns_to_ms(aio_compute_timeout(ctx)) : 0;
|
? qemu_timeout_ns_to_ms(aio_compute_timeout(ctx)) : 0;
|
||||||
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
ret = WaitForMultipleObjects(count, events, FALSE, timeout);
|
||||||
if (blocking) {
|
aio_set_dispatching(ctx, true);
|
||||||
assert(first);
|
|
||||||
atomic_sub(&ctx->notify_me, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first) {
|
if (first && aio_bh_poll(ctx)) {
|
||||||
aio_notify_accept(ctx);
|
progress = true;
|
||||||
progress |= aio_bh_poll(ctx);
|
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
/* if we have any signaled events, dispatch event */
|
/* if we have any signaled events, dispatch event */
|
||||||
event = NULL;
|
event = NULL;
|
||||||
@@ -388,20 +344,10 @@ bool aio_poll(AioContext *ctx, bool blocking)
|
|||||||
blocking = false;
|
blocking = false;
|
||||||
|
|
||||||
progress |= aio_dispatch_handlers(ctx, event);
|
progress |= aio_dispatch_handlers(ctx, event);
|
||||||
} while (count > 0);
|
}
|
||||||
|
|
||||||
qemu_lockcnt_dec(&ctx->list_lock);
|
|
||||||
|
|
||||||
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
progress |= timerlistgroup_run_timers(&ctx->tlg);
|
||||||
|
|
||||||
|
aio_set_dispatching(ctx, was_dispatching);
|
||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_context_setup(AioContext *ctx)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
|
|
||||||
int64_t grow, int64_t shrink, Error **errp)
|
|
||||||
{
|
|
||||||
error_setg(errp, "AioContext polling is not implemented on Windows");
|
|
||||||
}
|
|
||||||
1398
arch_init.c
1398
arch_init.c
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Data plane event loop
|
* QEMU System Emulator
|
||||||
*
|
*
|
||||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||||
* Copyright (c) 2009-2017 QEMU contributors
|
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -23,16 +22,11 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "block/thread-pool.h"
|
#include "block/thread-pool.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
#include "qemu/atomic.h"
|
#include "qemu/atomic.h"
|
||||||
#include "block/raw-aio.h"
|
|
||||||
#include "qemu/coroutine_int.h"
|
|
||||||
#include "trace.h"
|
|
||||||
|
|
||||||
/***********************************************************/
|
/***********************************************************/
|
||||||
/* bottom halves (can be seen as timers which expire ASAP) */
|
/* bottom halves (can be seen as timers which expire ASAP) */
|
||||||
@@ -47,26 +41,6 @@ struct QEMUBH {
|
|||||||
bool deleted;
|
bool deleted;
|
||||||
};
|
};
|
||||||
|
|
||||||
void aio_bh_schedule_oneshot(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
|
||||||
{
|
|
||||||
QEMUBH *bh;
|
|
||||||
bh = g_new(QEMUBH, 1);
|
|
||||||
*bh = (QEMUBH){
|
|
||||||
.ctx = ctx,
|
|
||||||
.cb = cb,
|
|
||||||
.opaque = opaque,
|
|
||||||
};
|
|
||||||
qemu_lockcnt_lock(&ctx->list_lock);
|
|
||||||
bh->next = ctx->first_bh;
|
|
||||||
bh->scheduled = 1;
|
|
||||||
bh->deleted = 1;
|
|
||||||
/* Make sure that the members are ready before putting bh into list */
|
|
||||||
smp_wmb();
|
|
||||||
ctx->first_bh = bh;
|
|
||||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
|
||||||
aio_notify(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
@@ -76,70 +50,60 @@ QEMUBH *aio_bh_new(AioContext *ctx, QEMUBHFunc *cb, void *opaque)
|
|||||||
.cb = cb,
|
.cb = cb,
|
||||||
.opaque = opaque,
|
.opaque = opaque,
|
||||||
};
|
};
|
||||||
qemu_lockcnt_lock(&ctx->list_lock);
|
qemu_mutex_lock(&ctx->bh_lock);
|
||||||
bh->next = ctx->first_bh;
|
bh->next = ctx->first_bh;
|
||||||
/* Make sure that the members are ready before putting bh into list */
|
/* Make sure that the members are ready before putting bh into list */
|
||||||
smp_wmb();
|
smp_wmb();
|
||||||
ctx->first_bh = bh;
|
ctx->first_bh = bh;
|
||||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
qemu_mutex_unlock(&ctx->bh_lock);
|
||||||
return bh;
|
return bh;
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_bh_call(QEMUBH *bh)
|
/* Multiple occurrences of aio_bh_poll cannot be called concurrently */
|
||||||
{
|
|
||||||
bh->cb(bh->opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Multiple occurrences of aio_bh_poll cannot be called concurrently.
|
|
||||||
* The count in ctx->list_lock is incremented before the call, and is
|
|
||||||
* not affected by the call.
|
|
||||||
*/
|
|
||||||
int aio_bh_poll(AioContext *ctx)
|
int aio_bh_poll(AioContext *ctx)
|
||||||
{
|
{
|
||||||
QEMUBH *bh, **bhp, *next;
|
QEMUBH *bh, **bhp, *next;
|
||||||
int ret;
|
int ret;
|
||||||
bool deleted = false;
|
|
||||||
|
ctx->walking_bh++;
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
for (bh = atomic_rcu_read(&ctx->first_bh); bh; bh = next) {
|
for (bh = ctx->first_bh; bh; bh = next) {
|
||||||
next = atomic_rcu_read(&bh->next);
|
/* Make sure that fetching bh happens before accessing its members */
|
||||||
|
smp_read_barrier_depends();
|
||||||
|
next = bh->next;
|
||||||
/* The atomic_xchg is paired with the one in qemu_bh_schedule. The
|
/* The atomic_xchg is paired with the one in qemu_bh_schedule. The
|
||||||
* implicit memory barrier ensures that the callback sees all writes
|
* implicit memory barrier ensures that the callback sees all writes
|
||||||
* done by the scheduling thread. It also ensures that the scheduling
|
* done by the scheduling thread. It also ensures that the scheduling
|
||||||
* thread sees the zero before bh->cb has run, and thus will call
|
* thread sees the zero before bh->cb has run, and thus will call
|
||||||
* aio_notify again if necessary.
|
* aio_notify again if necessary.
|
||||||
*/
|
*/
|
||||||
if (atomic_xchg(&bh->scheduled, 0)) {
|
if (!bh->deleted && atomic_xchg(&bh->scheduled, 0)) {
|
||||||
/* Idle BHs don't count as progress */
|
if (!bh->idle)
|
||||||
if (!bh->idle) {
|
|
||||||
ret = 1;
|
ret = 1;
|
||||||
}
|
|
||||||
bh->idle = 0;
|
bh->idle = 0;
|
||||||
aio_bh_call(bh);
|
bh->cb(bh->opaque);
|
||||||
}
|
|
||||||
if (bh->deleted) {
|
|
||||||
deleted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx->walking_bh--;
|
||||||
|
|
||||||
/* remove deleted bhs */
|
/* remove deleted bhs */
|
||||||
if (!deleted) {
|
if (!ctx->walking_bh) {
|
||||||
return ret;
|
qemu_mutex_lock(&ctx->bh_lock);
|
||||||
}
|
|
||||||
|
|
||||||
if (qemu_lockcnt_dec_if_lock(&ctx->list_lock)) {
|
|
||||||
bhp = &ctx->first_bh;
|
bhp = &ctx->first_bh;
|
||||||
while (*bhp) {
|
while (*bhp) {
|
||||||
bh = *bhp;
|
bh = *bhp;
|
||||||
if (bh->deleted && !bh->scheduled) {
|
if (bh->deleted) {
|
||||||
*bhp = bh->next;
|
*bhp = bh->next;
|
||||||
g_free(bh);
|
g_free(bh);
|
||||||
} else {
|
} else {
|
||||||
bhp = &bh->next;
|
bhp = &bh->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qemu_lockcnt_inc_and_unlock(&ctx->list_lock);
|
qemu_mutex_unlock(&ctx->bh_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,9 +157,8 @@ aio_compute_timeout(AioContext *ctx)
|
|||||||
int timeout = -1;
|
int timeout = -1;
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
|
|
||||||
for (bh = atomic_rcu_read(&ctx->first_bh); bh;
|
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
||||||
bh = atomic_rcu_read(&bh->next)) {
|
if (!bh->deleted && bh->scheduled) {
|
||||||
if (bh->scheduled) {
|
|
||||||
if (bh->idle) {
|
if (bh->idle) {
|
||||||
/* idle bottom halves will be polled at least
|
/* idle bottom halves will be polled at least
|
||||||
* every 10ms */
|
* every 10ms */
|
||||||
@@ -221,8 +184,6 @@ aio_ctx_prepare(GSource *source, gint *timeout)
|
|||||||
{
|
{
|
||||||
AioContext *ctx = (AioContext *) source;
|
AioContext *ctx = (AioContext *) source;
|
||||||
|
|
||||||
atomic_or(&ctx->notify_me, 1);
|
|
||||||
|
|
||||||
/* We assume there is no timeout already supplied */
|
/* We assume there is no timeout already supplied */
|
||||||
*timeout = qemu_timeout_ns_to_ms(aio_compute_timeout(ctx));
|
*timeout = qemu_timeout_ns_to_ms(aio_compute_timeout(ctx));
|
||||||
|
|
||||||
@@ -239,11 +200,8 @@ aio_ctx_check(GSource *source)
|
|||||||
AioContext *ctx = (AioContext *) source;
|
AioContext *ctx = (AioContext *) source;
|
||||||
QEMUBH *bh;
|
QEMUBH *bh;
|
||||||
|
|
||||||
atomic_and(&ctx->notify_me, ~1);
|
|
||||||
aio_notify_accept(ctx);
|
|
||||||
|
|
||||||
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
for (bh = ctx->first_bh; bh; bh = bh->next) {
|
||||||
if (bh->scheduled) {
|
if (!bh->deleted && bh->scheduled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,35 +226,11 @@ aio_ctx_finalize(GSource *source)
|
|||||||
AioContext *ctx = (AioContext *) source;
|
AioContext *ctx = (AioContext *) source;
|
||||||
|
|
||||||
thread_pool_free(ctx->thread_pool);
|
thread_pool_free(ctx->thread_pool);
|
||||||
|
aio_set_event_notifier(ctx, &ctx->notifier, NULL);
|
||||||
#ifdef CONFIG_LINUX_AIO
|
|
||||||
if (ctx->linux_aio) {
|
|
||||||
laio_detach_aio_context(ctx->linux_aio, ctx);
|
|
||||||
laio_cleanup(ctx->linux_aio);
|
|
||||||
ctx->linux_aio = NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
assert(QSLIST_EMPTY(&ctx->scheduled_coroutines));
|
|
||||||
qemu_bh_delete(ctx->co_schedule_bh);
|
|
||||||
|
|
||||||
qemu_lockcnt_lock(&ctx->list_lock);
|
|
||||||
assert(!qemu_lockcnt_count(&ctx->list_lock));
|
|
||||||
while (ctx->first_bh) {
|
|
||||||
QEMUBH *next = ctx->first_bh->next;
|
|
||||||
|
|
||||||
/* qemu_bh_delete() must have been called on BHs in this AioContext */
|
|
||||||
assert(ctx->first_bh->deleted);
|
|
||||||
|
|
||||||
g_free(ctx->first_bh);
|
|
||||||
ctx->first_bh = next;
|
|
||||||
}
|
|
||||||
qemu_lockcnt_unlock(&ctx->list_lock);
|
|
||||||
|
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier, false, NULL, NULL);
|
|
||||||
event_notifier_cleanup(&ctx->notifier);
|
event_notifier_cleanup(&ctx->notifier);
|
||||||
qemu_rec_mutex_destroy(&ctx->lock);
|
rfifolock_destroy(&ctx->lock);
|
||||||
qemu_lockcnt_destroy(&ctx->list_lock);
|
qemu_mutex_destroy(&ctx->bh_lock);
|
||||||
|
g_array_free(ctx->pollfds, TRUE);
|
||||||
timerlistgroup_deinit(&ctx->tlg);
|
timerlistgroup_deinit(&ctx->tlg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,157 +255,60 @@ ThreadPool *aio_get_thread_pool(AioContext *ctx)
|
|||||||
return ctx->thread_pool;
|
return ctx->thread_pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_LINUX_AIO
|
void aio_set_dispatching(AioContext *ctx, bool dispatching)
|
||||||
LinuxAioState *aio_get_linux_aio(AioContext *ctx)
|
|
||||||
{
|
{
|
||||||
if (!ctx->linux_aio) {
|
ctx->dispatching = dispatching;
|
||||||
ctx->linux_aio = laio_init();
|
if (!dispatching) {
|
||||||
laio_attach_aio_context(ctx->linux_aio, ctx);
|
/* Write ctx->dispatching before reading e.g. bh->scheduled.
|
||||||
|
* Optimization: this is only needed when we're entering the "unsafe"
|
||||||
|
* phase where other threads must call event_notifier_set.
|
||||||
|
*/
|
||||||
|
smp_mb();
|
||||||
}
|
}
|
||||||
return ctx->linux_aio;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
void aio_notify(AioContext *ctx)
|
void aio_notify(AioContext *ctx)
|
||||||
{
|
{
|
||||||
/* Write e.g. bh->scheduled before reading ctx->notify_me. Pairs
|
/* Write e.g. bh->scheduled before reading ctx->dispatching. */
|
||||||
* with atomic_or in aio_ctx_prepare or atomic_add in aio_poll.
|
|
||||||
*/
|
|
||||||
smp_mb();
|
smp_mb();
|
||||||
if (ctx->notify_me) {
|
if (!ctx->dispatching) {
|
||||||
event_notifier_set(&ctx->notifier);
|
event_notifier_set(&ctx->notifier);
|
||||||
atomic_mb_set(&ctx->notified, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_notify_accept(AioContext *ctx)
|
static void aio_timerlist_notify(void *opaque)
|
||||||
{
|
|
||||||
if (atomic_xchg(&ctx->notified, false)) {
|
|
||||||
event_notifier_test_and_clear(&ctx->notifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aio_timerlist_notify(void *opaque, QEMUClockType type)
|
|
||||||
{
|
{
|
||||||
aio_notify(opaque);
|
aio_notify(opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void event_notifier_dummy_cb(EventNotifier *e)
|
static void aio_rfifolock_cb(void *opaque)
|
||||||
{
|
{
|
||||||
}
|
/* Kick owner thread in case they are blocked in aio_poll() */
|
||||||
|
aio_notify(opaque);
|
||||||
/* Returns true if aio_notify() was called (e.g. a BH was scheduled) */
|
|
||||||
static bool event_notifier_poll(void *opaque)
|
|
||||||
{
|
|
||||||
EventNotifier *e = opaque;
|
|
||||||
AioContext *ctx = container_of(e, AioContext, notifier);
|
|
||||||
|
|
||||||
return atomic_read(&ctx->notified);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void co_schedule_bh_cb(void *opaque)
|
|
||||||
{
|
|
||||||
AioContext *ctx = opaque;
|
|
||||||
QSLIST_HEAD(, Coroutine) straight, reversed;
|
|
||||||
|
|
||||||
QSLIST_MOVE_ATOMIC(&reversed, &ctx->scheduled_coroutines);
|
|
||||||
QSLIST_INIT(&straight);
|
|
||||||
|
|
||||||
while (!QSLIST_EMPTY(&reversed)) {
|
|
||||||
Coroutine *co = QSLIST_FIRST(&reversed);
|
|
||||||
QSLIST_REMOVE_HEAD(&reversed, co_scheduled_next);
|
|
||||||
QSLIST_INSERT_HEAD(&straight, co, co_scheduled_next);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!QSLIST_EMPTY(&straight)) {
|
|
||||||
Coroutine *co = QSLIST_FIRST(&straight);
|
|
||||||
QSLIST_REMOVE_HEAD(&straight, co_scheduled_next);
|
|
||||||
trace_aio_co_schedule_bh_cb(ctx, co);
|
|
||||||
aio_context_acquire(ctx);
|
|
||||||
qemu_coroutine_enter(co);
|
|
||||||
aio_context_release(ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AioContext *aio_context_new(Error **errp)
|
AioContext *aio_context_new(Error **errp)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
AioContext *ctx;
|
AioContext *ctx;
|
||||||
|
|
||||||
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext));
|
||||||
aio_context_setup(ctx);
|
|
||||||
|
|
||||||
ret = event_notifier_init(&ctx->notifier, false);
|
ret = event_notifier_init(&ctx->notifier, false);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
g_source_destroy(&ctx->source);
|
||||||
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
|
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
|
||||||
goto fail;
|
return NULL;
|
||||||
}
|
}
|
||||||
g_source_set_can_recurse(&ctx->source, true);
|
g_source_set_can_recurse(&ctx->source, true);
|
||||||
qemu_lockcnt_init(&ctx->list_lock);
|
|
||||||
|
|
||||||
ctx->co_schedule_bh = aio_bh_new(ctx, co_schedule_bh_cb, ctx);
|
|
||||||
QSLIST_INIT(&ctx->scheduled_coroutines);
|
|
||||||
|
|
||||||
aio_set_event_notifier(ctx, &ctx->notifier,
|
aio_set_event_notifier(ctx, &ctx->notifier,
|
||||||
false,
|
|
||||||
(EventNotifierHandler *)
|
(EventNotifierHandler *)
|
||||||
event_notifier_dummy_cb,
|
event_notifier_test_and_clear);
|
||||||
event_notifier_poll);
|
ctx->pollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
|
||||||
#ifdef CONFIG_LINUX_AIO
|
|
||||||
ctx->linux_aio = NULL;
|
|
||||||
#endif
|
|
||||||
ctx->thread_pool = NULL;
|
ctx->thread_pool = NULL;
|
||||||
qemu_rec_mutex_init(&ctx->lock);
|
qemu_mutex_init(&ctx->bh_lock);
|
||||||
|
rfifolock_init(&ctx->lock, aio_rfifolock_cb, ctx);
|
||||||
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);
|
||||||
|
|
||||||
ctx->poll_ns = 0;
|
|
||||||
ctx->poll_max_ns = 0;
|
|
||||||
ctx->poll_grow = 0;
|
|
||||||
ctx->poll_shrink = 0;
|
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
fail:
|
|
||||||
g_source_destroy(&ctx->source);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_co_schedule(AioContext *ctx, Coroutine *co)
|
|
||||||
{
|
|
||||||
trace_aio_co_schedule(ctx, co);
|
|
||||||
QSLIST_INSERT_HEAD_ATOMIC(&ctx->scheduled_coroutines,
|
|
||||||
co, co_scheduled_next);
|
|
||||||
qemu_bh_schedule(ctx->co_schedule_bh);
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_co_wake(struct Coroutine *co)
|
|
||||||
{
|
|
||||||
AioContext *ctx;
|
|
||||||
|
|
||||||
/* Read coroutine before co->ctx. Matches smp_wmb in
|
|
||||||
* qemu_coroutine_enter.
|
|
||||||
*/
|
|
||||||
smp_read_barrier_depends();
|
|
||||||
ctx = atomic_read(&co->ctx);
|
|
||||||
|
|
||||||
aio_co_enter(ctx, co);
|
|
||||||
}
|
|
||||||
|
|
||||||
void aio_co_enter(AioContext *ctx, struct Coroutine *co)
|
|
||||||
{
|
|
||||||
if (ctx != qemu_get_current_aio_context()) {
|
|
||||||
aio_co_schedule(ctx, co);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qemu_in_coroutine()) {
|
|
||||||
Coroutine *self = qemu_coroutine_self();
|
|
||||||
assert(self != co);
|
|
||||||
QSIMPLEQ_INSERT_TAIL(&self->co_queue_wakeup, co, co_queue_next);
|
|
||||||
} else {
|
|
||||||
aio_context_acquire(ctx);
|
|
||||||
qemu_aio_coroutine_enter(ctx, co);
|
|
||||||
aio_context_release(ctx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_context_ref(AioContext *ctx)
|
void aio_context_ref(AioContext *ctx)
|
||||||
@@ -486,10 +323,10 @@ void aio_context_unref(AioContext *ctx)
|
|||||||
|
|
||||||
void aio_context_acquire(AioContext *ctx)
|
void aio_context_acquire(AioContext *ctx)
|
||||||
{
|
{
|
||||||
qemu_rec_mutex_lock(&ctx->lock);
|
rfifolock_lock(&ctx->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void aio_context_release(AioContext *ctx)
|
void aio_context_release(AioContext *ctx)
|
||||||
{
|
{
|
||||||
qemu_rec_mutex_unlock(&ctx->lock);
|
rfifolock_unlock(&ctx->lock);
|
||||||
}
|
}
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
/*
|
|
||||||
* Atomic helper templates
|
|
||||||
* Included from tcg-runtime.c and cputlb.c.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Red Hat, Inc
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if DATA_SIZE == 16
|
|
||||||
# define SUFFIX o
|
|
||||||
# define DATA_TYPE Int128
|
|
||||||
# define BSWAP bswap128
|
|
||||||
#elif DATA_SIZE == 8
|
|
||||||
# define SUFFIX q
|
|
||||||
# define DATA_TYPE uint64_t
|
|
||||||
# define BSWAP bswap64
|
|
||||||
#elif DATA_SIZE == 4
|
|
||||||
# define SUFFIX l
|
|
||||||
# define DATA_TYPE uint32_t
|
|
||||||
# define BSWAP bswap32
|
|
||||||
#elif DATA_SIZE == 2
|
|
||||||
# define SUFFIX w
|
|
||||||
# define DATA_TYPE uint16_t
|
|
||||||
# define BSWAP bswap16
|
|
||||||
#elif DATA_SIZE == 1
|
|
||||||
# define SUFFIX b
|
|
||||||
# define DATA_TYPE uint8_t
|
|
||||||
# define BSWAP
|
|
||||||
#else
|
|
||||||
# error unsupported data size
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if DATA_SIZE >= 4
|
|
||||||
# define ABI_TYPE DATA_TYPE
|
|
||||||
#else
|
|
||||||
# define ABI_TYPE uint32_t
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Define host-endian atomic operations. Note that END is used within
|
|
||||||
the ATOMIC_NAME macro, and redefined below. */
|
|
||||||
#if DATA_SIZE == 1
|
|
||||||
# define END
|
|
||||||
#elif defined(HOST_WORDS_BIGENDIAN)
|
|
||||||
# define END _be
|
|
||||||
#else
|
|
||||||
# define END _le
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE cmpv, ABI_TYPE newv EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
return atomic_cmpxchg__nocheck(haddr, cmpv, newv);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DATA_SIZE >= 16
|
|
||||||
ABI_TYPE ATOMIC_NAME(ld)(CPUArchState *env, target_ulong addr EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE val, *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
__atomic_load(haddr, &val, __ATOMIC_RELAXED);
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATOMIC_NAME(st)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
__atomic_store(haddr, &val, __ATOMIC_RELAXED);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
return atomic_xchg__nocheck(haddr, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define GEN_ATOMIC_HELPER(X) \
|
|
||||||
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, target_ulong addr, \
|
|
||||||
ABI_TYPE val EXTRA_ARGS) \
|
|
||||||
{ \
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP; \
|
|
||||||
return atomic_##X(haddr, val); \
|
|
||||||
} \
|
|
||||||
|
|
||||||
GEN_ATOMIC_HELPER(fetch_add)
|
|
||||||
GEN_ATOMIC_HELPER(fetch_and)
|
|
||||||
GEN_ATOMIC_HELPER(fetch_or)
|
|
||||||
GEN_ATOMIC_HELPER(fetch_xor)
|
|
||||||
GEN_ATOMIC_HELPER(add_fetch)
|
|
||||||
GEN_ATOMIC_HELPER(and_fetch)
|
|
||||||
GEN_ATOMIC_HELPER(or_fetch)
|
|
||||||
GEN_ATOMIC_HELPER(xor_fetch)
|
|
||||||
|
|
||||||
#undef GEN_ATOMIC_HELPER
|
|
||||||
#endif /* DATA SIZE >= 16 */
|
|
||||||
|
|
||||||
#undef END
|
|
||||||
|
|
||||||
#if DATA_SIZE > 1
|
|
||||||
|
|
||||||
/* Define reverse-host-endian atomic operations. Note that END is used
|
|
||||||
within the ATOMIC_NAME macro. */
|
|
||||||
#ifdef HOST_WORDS_BIGENDIAN
|
|
||||||
# define END _le
|
|
||||||
#else
|
|
||||||
# define END _be
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ABI_TYPE ATOMIC_NAME(cmpxchg)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE cmpv, ABI_TYPE newv EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
return BSWAP(atomic_cmpxchg__nocheck(haddr, BSWAP(cmpv), BSWAP(newv)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DATA_SIZE >= 16
|
|
||||||
ABI_TYPE ATOMIC_NAME(ld)(CPUArchState *env, target_ulong addr EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE val, *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
__atomic_load(haddr, &val, __ATOMIC_RELAXED);
|
|
||||||
return BSWAP(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ATOMIC_NAME(st)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
val = BSWAP(val);
|
|
||||||
__atomic_store(haddr, &val, __ATOMIC_RELAXED);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
ABI_TYPE ATOMIC_NAME(xchg)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
return BSWAP(atomic_xchg__nocheck(haddr, BSWAP(val)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#define GEN_ATOMIC_HELPER(X) \
|
|
||||||
ABI_TYPE ATOMIC_NAME(X)(CPUArchState *env, target_ulong addr, \
|
|
||||||
ABI_TYPE val EXTRA_ARGS) \
|
|
||||||
{ \
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP; \
|
|
||||||
return BSWAP(atomic_##X(haddr, BSWAP(val))); \
|
|
||||||
}
|
|
||||||
|
|
||||||
GEN_ATOMIC_HELPER(fetch_and)
|
|
||||||
GEN_ATOMIC_HELPER(fetch_or)
|
|
||||||
GEN_ATOMIC_HELPER(fetch_xor)
|
|
||||||
GEN_ATOMIC_HELPER(and_fetch)
|
|
||||||
GEN_ATOMIC_HELPER(or_fetch)
|
|
||||||
GEN_ATOMIC_HELPER(xor_fetch)
|
|
||||||
|
|
||||||
#undef GEN_ATOMIC_HELPER
|
|
||||||
|
|
||||||
/* Note that for addition, we need to use a separate cmpxchg loop instead
|
|
||||||
of bswaps for the reverse-host-endian helpers. */
|
|
||||||
ABI_TYPE ATOMIC_NAME(fetch_add)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
DATA_TYPE ldo, ldn, ret, sto;
|
|
||||||
|
|
||||||
ldo = atomic_read__nocheck(haddr);
|
|
||||||
while (1) {
|
|
||||||
ret = BSWAP(ldo);
|
|
||||||
sto = BSWAP(ret + val);
|
|
||||||
ldn = atomic_cmpxchg__nocheck(haddr, ldo, sto);
|
|
||||||
if (ldn == ldo) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
ldo = ldn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ABI_TYPE ATOMIC_NAME(add_fetch)(CPUArchState *env, target_ulong addr,
|
|
||||||
ABI_TYPE val EXTRA_ARGS)
|
|
||||||
{
|
|
||||||
DATA_TYPE *haddr = ATOMIC_MMU_LOOKUP;
|
|
||||||
DATA_TYPE ldo, ldn, ret, sto;
|
|
||||||
|
|
||||||
ldo = atomic_read__nocheck(haddr);
|
|
||||||
while (1) {
|
|
||||||
ret = BSWAP(ldo) + val;
|
|
||||||
sto = BSWAP(ret);
|
|
||||||
ldn = atomic_cmpxchg__nocheck(haddr, ldo, sto);
|
|
||||||
if (ldn == ldo) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
ldo = ldn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* DATA_SIZE >= 16 */
|
|
||||||
|
|
||||||
#undef END
|
|
||||||
#endif /* DATA_SIZE > 1 */
|
|
||||||
|
|
||||||
#undef BSWAP
|
|
||||||
#undef ABI_TYPE
|
|
||||||
#undef DATA_TYPE
|
|
||||||
#undef SUFFIX
|
|
||||||
#undef DATA_SIZE
|
|
||||||
@@ -5,9 +5,13 @@ common-obj-$(CONFIG_SPICE) += spiceaudio.o
|
|||||||
common-obj-$(CONFIG_COREAUDIO) += coreaudio.o
|
common-obj-$(CONFIG_COREAUDIO) += coreaudio.o
|
||||||
common-obj-$(CONFIG_ALSA) += alsaaudio.o
|
common-obj-$(CONFIG_ALSA) += alsaaudio.o
|
||||||
common-obj-$(CONFIG_DSOUND) += dsoundaudio.o
|
common-obj-$(CONFIG_DSOUND) += dsoundaudio.o
|
||||||
|
common-obj-$(CONFIG_FMOD) += fmodaudio.o
|
||||||
|
common-obj-$(CONFIG_ESD) += esdaudio.o
|
||||||
common-obj-$(CONFIG_PA) += paaudio.o
|
common-obj-$(CONFIG_PA) += paaudio.o
|
||||||
|
common-obj-$(CONFIG_WINWAVE) += winwaveaudio.o
|
||||||
common-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o
|
common-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o
|
||||||
common-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o
|
common-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o
|
||||||
common-obj-y += wavcapture.o
|
common-obj-y += wavcapture.o
|
||||||
|
|
||||||
|
$(obj)/audio.o $(obj)/fmodaudio.o: QEMU_CFLAGS += $(FMOD_CFLAGS)
|
||||||
sdlaudio.o-cflags := $(SDL_CFLAGS)
|
sdlaudio.o-cflags := $(SDL_CFLAGS)
|
||||||
|
|||||||
@@ -21,12 +21,10 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "trace.h"
|
|
||||||
|
|
||||||
#if QEMU_GNUC_PREREQ(4, 3)
|
#if QEMU_GNUC_PREREQ(4, 3)
|
||||||
#pragma GCC diagnostic ignored "-Waddress"
|
#pragma GCC diagnostic ignored "-Waddress"
|
||||||
@@ -35,28 +33,9 @@
|
|||||||
#define AUDIO_CAP "alsa"
|
#define AUDIO_CAP "alsa"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
|
|
||||||
typedef struct ALSAConf {
|
|
||||||
int size_in_usec_in;
|
|
||||||
int size_in_usec_out;
|
|
||||||
const char *pcm_name_in;
|
|
||||||
const char *pcm_name_out;
|
|
||||||
unsigned int buffer_size_in;
|
|
||||||
unsigned int period_size_in;
|
|
||||||
unsigned int buffer_size_out;
|
|
||||||
unsigned int period_size_out;
|
|
||||||
unsigned int threshold;
|
|
||||||
|
|
||||||
int buffer_size_in_overridden;
|
|
||||||
int period_size_in_overridden;
|
|
||||||
|
|
||||||
int buffer_size_out_overridden;
|
|
||||||
int period_size_out_overridden;
|
|
||||||
} ALSAConf;
|
|
||||||
|
|
||||||
struct pollhlp {
|
struct pollhlp {
|
||||||
snd_pcm_t *handle;
|
snd_pcm_t *handle;
|
||||||
struct pollfd *pfds;
|
struct pollfd *pfds;
|
||||||
ALSAConf *conf;
|
|
||||||
int count;
|
int count;
|
||||||
int mask;
|
int mask;
|
||||||
};
|
};
|
||||||
@@ -77,6 +56,30 @@ typedef struct ALSAVoiceIn {
|
|||||||
struct pollhlp pollhlp;
|
struct pollhlp pollhlp;
|
||||||
} ALSAVoiceIn;
|
} ALSAVoiceIn;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int size_in_usec_in;
|
||||||
|
int size_in_usec_out;
|
||||||
|
const char *pcm_name_in;
|
||||||
|
const char *pcm_name_out;
|
||||||
|
unsigned int buffer_size_in;
|
||||||
|
unsigned int period_size_in;
|
||||||
|
unsigned int buffer_size_out;
|
||||||
|
unsigned int period_size_out;
|
||||||
|
unsigned int threshold;
|
||||||
|
|
||||||
|
int buffer_size_in_overridden;
|
||||||
|
int period_size_in_overridden;
|
||||||
|
|
||||||
|
int buffer_size_out_overridden;
|
||||||
|
int period_size_out_overridden;
|
||||||
|
int verbose;
|
||||||
|
} conf = {
|
||||||
|
.buffer_size_out = 4096,
|
||||||
|
.period_size_out = 1024,
|
||||||
|
.pcm_name_out = "default",
|
||||||
|
.pcm_name_in = "default",
|
||||||
|
};
|
||||||
|
|
||||||
struct alsa_params_req {
|
struct alsa_params_req {
|
||||||
int freq;
|
int freq;
|
||||||
snd_pcm_format_t fmt;
|
snd_pcm_format_t fmt;
|
||||||
@@ -202,7 +205,9 @@ static void alsa_poll_handler (void *opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(revents & hlp->mask)) {
|
if (!(revents & hlp->mask)) {
|
||||||
trace_alsa_revents(revents);
|
if (conf.verbose) {
|
||||||
|
dolog ("revents = %d\n", revents);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,14 +266,31 @@ static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask)
|
|||||||
|
|
||||||
for (i = 0; i < count; ++i) {
|
for (i = 0; i < count; ++i) {
|
||||||
if (pfds[i].events & POLLIN) {
|
if (pfds[i].events & POLLIN) {
|
||||||
qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler, NULL, hlp);
|
err = qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler,
|
||||||
|
NULL, hlp);
|
||||||
}
|
}
|
||||||
if (pfds[i].events & POLLOUT) {
|
if (pfds[i].events & POLLOUT) {
|
||||||
trace_alsa_pollout(i, pfds[i].fd);
|
if (conf.verbose) {
|
||||||
qemu_set_fd_handler (pfds[i].fd, NULL, alsa_poll_handler, hlp);
|
dolog ("POLLOUT %d %d\n", i, pfds[i].fd);
|
||||||
|
}
|
||||||
|
err = qemu_set_fd_handler (pfds[i].fd, NULL,
|
||||||
|
alsa_poll_handler, hlp);
|
||||||
|
}
|
||||||
|
if (conf.verbose) {
|
||||||
|
dolog ("Set handler events=%#x index=%d fd=%d err=%d\n",
|
||||||
|
pfds[i].events, i, pfds[i].fd, err);
|
||||||
}
|
}
|
||||||
trace_alsa_set_handler(pfds[i].events, i, pfds[i].fd, err);
|
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
dolog ("Failed to set handler events=%#x index=%d fd=%d err=%d\n",
|
||||||
|
pfds[i].events, i, pfds[i].fd, err);
|
||||||
|
|
||||||
|
while (i--) {
|
||||||
|
qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
g_free (pfds);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hlp->pfds = pfds;
|
hlp->pfds = pfds;
|
||||||
hlp->count = count;
|
hlp->count = count;
|
||||||
@@ -454,15 +476,14 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int alsa_open (int in, struct alsa_params_req *req,
|
static int alsa_open (int in, struct alsa_params_req *req,
|
||||||
struct alsa_params_obt *obt, snd_pcm_t **handlep,
|
struct alsa_params_obt *obt, snd_pcm_t **handlep)
|
||||||
ALSAConf *conf)
|
|
||||||
{
|
{
|
||||||
snd_pcm_t *handle;
|
snd_pcm_t *handle;
|
||||||
snd_pcm_hw_params_t *hw_params;
|
snd_pcm_hw_params_t *hw_params;
|
||||||
int err;
|
int err;
|
||||||
int size_in_usec;
|
int size_in_usec;
|
||||||
unsigned int freq, nchannels;
|
unsigned int freq, nchannels;
|
||||||
const char *pcm_name = in ? conf->pcm_name_in : conf->pcm_name_out;
|
const char *pcm_name = in ? conf.pcm_name_in : conf.pcm_name_out;
|
||||||
snd_pcm_uframes_t obt_buffer_size;
|
snd_pcm_uframes_t obt_buffer_size;
|
||||||
const char *typ = in ? "ADC" : "DAC";
|
const char *typ = in ? "ADC" : "DAC";
|
||||||
snd_pcm_format_t obtfmt;
|
snd_pcm_format_t obtfmt;
|
||||||
@@ -501,7 +522,7 @@ static int alsa_open (int in, struct alsa_params_req *req,
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt);
|
err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt);
|
||||||
if (err < 0) {
|
if (err < 0 && conf.verbose) {
|
||||||
alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt);
|
alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,7 +654,7 @@ static int alsa_open (int in, struct alsa_params_req *req,
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in && conf->threshold) {
|
if (!in && conf.threshold) {
|
||||||
snd_pcm_uframes_t threshold;
|
snd_pcm_uframes_t threshold;
|
||||||
int bytes_per_sec;
|
int bytes_per_sec;
|
||||||
|
|
||||||
@@ -655,7 +676,7 @@ static int alsa_open (int in, struct alsa_params_req *req,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
threshold = (conf->threshold * bytes_per_sec) / 1000;
|
threshold = (conf.threshold * bytes_per_sec) / 1000;
|
||||||
alsa_set_threshold (handle, threshold);
|
alsa_set_threshold (handle, threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,9 +686,10 @@ static int alsa_open (int in, struct alsa_params_req *req,
|
|||||||
|
|
||||||
*handlep = handle;
|
*handlep = handle;
|
||||||
|
|
||||||
if (obtfmt != req->fmt ||
|
if (conf.verbose &&
|
||||||
|
(obtfmt != req->fmt ||
|
||||||
obt->nchannels != req->nchannels ||
|
obt->nchannels != req->nchannels ||
|
||||||
obt->freq != req->freq) {
|
obt->freq != req->freq)) {
|
||||||
dolog ("Audio parameters for %s\n", typ);
|
dolog ("Audio parameters for %s\n", typ);
|
||||||
alsa_dump_info (req, obt, obtfmt);
|
alsa_dump_info (req, obt, obtfmt);
|
||||||
}
|
}
|
||||||
@@ -721,7 +743,9 @@ static void alsa_write_pending (ALSAVoiceOut *alsa)
|
|||||||
if (written <= 0) {
|
if (written <= 0) {
|
||||||
switch (written) {
|
switch (written) {
|
||||||
case 0:
|
case 0:
|
||||||
trace_alsa_wrote_zero(len);
|
if (conf.verbose) {
|
||||||
|
dolog ("Failed to write %d frames (wrote zero)\n", len);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case -EPIPE:
|
case -EPIPE:
|
||||||
@@ -730,7 +754,9 @@ static void alsa_write_pending (ALSAVoiceOut *alsa)
|
|||||||
len);
|
len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
trace_alsa_xrun_out();
|
if (conf.verbose) {
|
||||||
|
dolog ("Recovering from playback xrun\n");
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case -ESTRPIPE:
|
case -ESTRPIPE:
|
||||||
@@ -741,7 +767,9 @@ static void alsa_write_pending (ALSAVoiceOut *alsa)
|
|||||||
len);
|
len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
trace_alsa_resume_out();
|
if (conf.verbose) {
|
||||||
|
dolog ("Resuming suspended output stream\n");
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case -EAGAIN:
|
case -EAGAIN:
|
||||||
@@ -791,27 +819,25 @@ static void alsa_fini_out (HWVoiceOut *hw)
|
|||||||
alsa->pcm_buf = NULL;
|
alsa->pcm_buf = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int alsa_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
|
ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw;
|
||||||
struct alsa_params_req req;
|
struct alsa_params_req req;
|
||||||
struct alsa_params_obt obt;
|
struct alsa_params_obt obt;
|
||||||
snd_pcm_t *handle;
|
snd_pcm_t *handle;
|
||||||
struct audsettings obt_as;
|
struct audsettings obt_as;
|
||||||
ALSAConf *conf = drv_opaque;
|
|
||||||
|
|
||||||
req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
|
req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
|
||||||
req.freq = as->freq;
|
req.freq = as->freq;
|
||||||
req.nchannels = as->nchannels;
|
req.nchannels = as->nchannels;
|
||||||
req.period_size = conf->period_size_out;
|
req.period_size = conf.period_size_out;
|
||||||
req.buffer_size = conf->buffer_size_out;
|
req.buffer_size = conf.buffer_size_out;
|
||||||
req.size_in_usec = conf->size_in_usec_out;
|
req.size_in_usec = conf.size_in_usec_out;
|
||||||
req.override_mask =
|
req.override_mask =
|
||||||
(conf->period_size_out_overridden ? 1 : 0) |
|
(conf.period_size_out_overridden ? 1 : 0) |
|
||||||
(conf->buffer_size_out_overridden ? 2 : 0);
|
(conf.buffer_size_out_overridden ? 2 : 0);
|
||||||
|
|
||||||
if (alsa_open (0, &req, &obt, &handle, conf)) {
|
if (alsa_open (0, &req, &obt, &handle)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,7 +858,6 @@ static int alsa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
alsa->handle = handle;
|
alsa->handle = handle;
|
||||||
alsa->pollhlp.conf = conf;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -903,26 +928,25 @@ static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
static int alsa_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
|
ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw;
|
||||||
struct alsa_params_req req;
|
struct alsa_params_req req;
|
||||||
struct alsa_params_obt obt;
|
struct alsa_params_obt obt;
|
||||||
snd_pcm_t *handle;
|
snd_pcm_t *handle;
|
||||||
struct audsettings obt_as;
|
struct audsettings obt_as;
|
||||||
ALSAConf *conf = drv_opaque;
|
|
||||||
|
|
||||||
req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
|
req.fmt = aud_to_alsafmt (as->fmt, as->endianness);
|
||||||
req.freq = as->freq;
|
req.freq = as->freq;
|
||||||
req.nchannels = as->nchannels;
|
req.nchannels = as->nchannels;
|
||||||
req.period_size = conf->period_size_in;
|
req.period_size = conf.period_size_in;
|
||||||
req.buffer_size = conf->buffer_size_in;
|
req.buffer_size = conf.buffer_size_in;
|
||||||
req.size_in_usec = conf->size_in_usec_in;
|
req.size_in_usec = conf.size_in_usec_in;
|
||||||
req.override_mask =
|
req.override_mask =
|
||||||
(conf->period_size_in_overridden ? 1 : 0) |
|
(conf.period_size_in_overridden ? 1 : 0) |
|
||||||
(conf->buffer_size_in_overridden ? 2 : 0);
|
(conf.buffer_size_in_overridden ? 2 : 0);
|
||||||
|
|
||||||
if (alsa_open (1, &req, &obt, &handle, conf)) {
|
if (alsa_open (1, &req, &obt, &handle)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -943,7 +967,6 @@ static int alsa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
alsa->handle = handle;
|
alsa->handle = handle;
|
||||||
alsa->pollhlp.conf = conf;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,10 +1022,14 @@ static int alsa_run_in (HWVoiceIn *hw)
|
|||||||
dolog ("Failed to resume suspended input stream\n");
|
dolog ("Failed to resume suspended input stream\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
trace_alsa_resume_in();
|
if (conf.verbose) {
|
||||||
|
dolog ("Resuming suspended input stream\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
trace_alsa_no_frames(state);
|
if (conf.verbose) {
|
||||||
|
dolog ("No frames available and ALSA state is %d\n", state);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1037,7 +1064,9 @@ static int alsa_run_in (HWVoiceIn *hw)
|
|||||||
if (nread <= 0) {
|
if (nread <= 0) {
|
||||||
switch (nread) {
|
switch (nread) {
|
||||||
case 0:
|
case 0:
|
||||||
trace_alsa_read_zero(len);
|
if (conf.verbose) {
|
||||||
|
dolog ("Failed to read %ld frames (read zero)\n", len);
|
||||||
|
}
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
case -EPIPE:
|
case -EPIPE:
|
||||||
@@ -1045,7 +1074,9 @@ static int alsa_run_in (HWVoiceIn *hw)
|
|||||||
alsa_logerr (nread, "Failed to read %ld frames\n", len);
|
alsa_logerr (nread, "Failed to read %ld frames\n", len);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
trace_alsa_xrun_in();
|
if (conf.verbose) {
|
||||||
|
dolog ("Recovering from capture xrun\n");
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case -EAGAIN:
|
case -EAGAIN:
|
||||||
@@ -1117,85 +1148,82 @@ static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ALSAConf glob_conf = {
|
|
||||||
.buffer_size_out = 4096,
|
|
||||||
.period_size_out = 1024,
|
|
||||||
.pcm_name_out = "default",
|
|
||||||
.pcm_name_in = "default",
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *alsa_audio_init (void)
|
static void *alsa_audio_init (void)
|
||||||
{
|
{
|
||||||
ALSAConf *conf = g_malloc(sizeof(ALSAConf));
|
return &conf;
|
||||||
*conf = glob_conf;
|
|
||||||
return conf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void alsa_audio_fini (void *opaque)
|
static void alsa_audio_fini (void *opaque)
|
||||||
{
|
{
|
||||||
g_free(opaque);
|
(void) opaque;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option alsa_options[] = {
|
static struct audio_option alsa_options[] = {
|
||||||
{
|
{
|
||||||
.name = "DAC_SIZE_IN_USEC",
|
.name = "DAC_SIZE_IN_USEC",
|
||||||
.tag = AUD_OPT_BOOL,
|
.tag = AUD_OPT_BOOL,
|
||||||
.valp = &glob_conf.size_in_usec_out,
|
.valp = &conf.size_in_usec_out,
|
||||||
.descr = "DAC period/buffer size in microseconds (otherwise in frames)"
|
.descr = "DAC period/buffer size in microseconds (otherwise in frames)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "DAC_PERIOD_SIZE",
|
.name = "DAC_PERIOD_SIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.period_size_out,
|
.valp = &conf.period_size_out,
|
||||||
.descr = "DAC period size (0 to go with system default)",
|
.descr = "DAC period size (0 to go with system default)",
|
||||||
.overriddenp = &glob_conf.period_size_out_overridden
|
.overriddenp = &conf.period_size_out_overridden
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "DAC_BUFFER_SIZE",
|
.name = "DAC_BUFFER_SIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.buffer_size_out,
|
.valp = &conf.buffer_size_out,
|
||||||
.descr = "DAC buffer size (0 to go with system default)",
|
.descr = "DAC buffer size (0 to go with system default)",
|
||||||
.overriddenp = &glob_conf.buffer_size_out_overridden
|
.overriddenp = &conf.buffer_size_out_overridden
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ADC_SIZE_IN_USEC",
|
.name = "ADC_SIZE_IN_USEC",
|
||||||
.tag = AUD_OPT_BOOL,
|
.tag = AUD_OPT_BOOL,
|
||||||
.valp = &glob_conf.size_in_usec_in,
|
.valp = &conf.size_in_usec_in,
|
||||||
.descr =
|
.descr =
|
||||||
"ADC period/buffer size in microseconds (otherwise in frames)"
|
"ADC period/buffer size in microseconds (otherwise in frames)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ADC_PERIOD_SIZE",
|
.name = "ADC_PERIOD_SIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.period_size_in,
|
.valp = &conf.period_size_in,
|
||||||
.descr = "ADC period size (0 to go with system default)",
|
.descr = "ADC period size (0 to go with system default)",
|
||||||
.overriddenp = &glob_conf.period_size_in_overridden
|
.overriddenp = &conf.period_size_in_overridden
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ADC_BUFFER_SIZE",
|
.name = "ADC_BUFFER_SIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.buffer_size_in,
|
.valp = &conf.buffer_size_in,
|
||||||
.descr = "ADC buffer size (0 to go with system default)",
|
.descr = "ADC buffer size (0 to go with system default)",
|
||||||
.overriddenp = &glob_conf.buffer_size_in_overridden
|
.overriddenp = &conf.buffer_size_in_overridden
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "THRESHOLD",
|
.name = "THRESHOLD",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.threshold,
|
.valp = &conf.threshold,
|
||||||
.descr = "(undocumented)"
|
.descr = "(undocumented)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "DAC_DEV",
|
.name = "DAC_DEV",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.pcm_name_out,
|
.valp = &conf.pcm_name_out,
|
||||||
.descr = "DAC device name (for instance dmix)"
|
.descr = "DAC device name (for instance dmix)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ADC_DEV",
|
.name = "ADC_DEV",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.pcm_name_in,
|
.valp = &conf.pcm_name_in,
|
||||||
.descr = "ADC device name"
|
.descr = "ADC device name"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "VERBOSE",
|
||||||
|
.tag = AUD_OPT_BOOL,
|
||||||
|
.valp = &conf.verbose,
|
||||||
|
.descr = "Behave in a more verbose way"
|
||||||
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,18 +21,16 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "monitor/monitor.h"
|
#include "monitor/monitor.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "sysemu/replay.h"
|
|
||||||
|
|
||||||
#define AUDIO_CAP "audio"
|
#define AUDIO_CAP "audio"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
|
|
||||||
|
/* #define DEBUG_PLIVE */
|
||||||
/* #define DEBUG_LIVE */
|
/* #define DEBUG_LIVE */
|
||||||
/* #define DEBUG_OUT */
|
/* #define DEBUG_OUT */
|
||||||
/* #define DEBUG_CAPTURE */
|
/* #define DEBUG_CAPTURE */
|
||||||
@@ -68,6 +66,8 @@ static struct {
|
|||||||
int hertz;
|
int hertz;
|
||||||
int64_t ticks;
|
int64_t ticks;
|
||||||
} period;
|
} period;
|
||||||
|
int plive;
|
||||||
|
int log_to_monitor;
|
||||||
int try_poll_in;
|
int try_poll_in;
|
||||||
int try_poll_out;
|
int try_poll_out;
|
||||||
} conf = {
|
} conf = {
|
||||||
@@ -96,6 +96,8 @@ static struct {
|
|||||||
},
|
},
|
||||||
|
|
||||||
.period = { .hertz = 100 },
|
.period = { .hertz = 100 },
|
||||||
|
.plive = 0,
|
||||||
|
.log_to_monitor = 0,
|
||||||
.try_poll_in = 1,
|
.try_poll_in = 1,
|
||||||
.try_poll_out = 1,
|
.try_poll_out = 1,
|
||||||
};
|
};
|
||||||
@@ -329,11 +331,20 @@ static const char *audio_get_conf_str (const char *key,
|
|||||||
|
|
||||||
void AUD_vlog (const char *cap, const char *fmt, va_list ap)
|
void AUD_vlog (const char *cap, const char *fmt, va_list ap)
|
||||||
{
|
{
|
||||||
|
if (conf.log_to_monitor) {
|
||||||
if (cap) {
|
if (cap) {
|
||||||
fprintf(stderr, "%s: ", cap);
|
monitor_printf(default_mon, "%s: ", cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
vfprintf(stderr, fmt, ap);
|
monitor_vprintf(default_mon, fmt, ap);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (cap) {
|
||||||
|
fprintf (stderr, "%s: ", cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfprintf (stderr, fmt, ap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AUD_log (const char *cap, const char *fmt, ...)
|
void AUD_log (const char *cap, const char *fmt, ...)
|
||||||
@@ -1113,7 +1124,7 @@ static int audio_is_timer_needed (void)
|
|||||||
static void audio_reset_timer (AudioState *s)
|
static void audio_reset_timer (AudioState *s)
|
||||||
{
|
{
|
||||||
if (audio_is_timer_needed ()) {
|
if (audio_is_timer_needed ()) {
|
||||||
timer_mod_anticipate_ns(s->ts,
|
timer_mod (s->ts,
|
||||||
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
|
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + conf.period.ticks);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1132,6 +1143,8 @@ static void audio_timer (void *opaque)
|
|||||||
*/
|
*/
|
||||||
int AUD_write (SWVoiceOut *sw, void *buf, int size)
|
int AUD_write (SWVoiceOut *sw, void *buf, int size)
|
||||||
{
|
{
|
||||||
|
int bytes;
|
||||||
|
|
||||||
if (!sw) {
|
if (!sw) {
|
||||||
/* XXX: Consider options */
|
/* XXX: Consider options */
|
||||||
return size;
|
return size;
|
||||||
@@ -1142,11 +1155,14 @@ int AUD_write (SWVoiceOut *sw, void *buf, int size)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sw->hw->pcm_ops->write(sw, buf, size);
|
bytes = sw->hw->pcm_ops->write (sw, buf, size);
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AUD_read (SWVoiceIn *sw, void *buf, int size)
|
int AUD_read (SWVoiceIn *sw, void *buf, int size)
|
||||||
{
|
{
|
||||||
|
int bytes;
|
||||||
|
|
||||||
if (!sw) {
|
if (!sw) {
|
||||||
/* XXX: Consider options */
|
/* XXX: Consider options */
|
||||||
return size;
|
return size;
|
||||||
@@ -1157,7 +1173,8 @@ int AUD_read (SWVoiceIn *sw, void *buf, int size)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sw->hw->pcm_ops->read(sw, buf, size);
|
bytes = sw->hw->pcm_ops->read (sw, buf, size);
|
||||||
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AUD_get_buffer_size_out (SWVoiceOut *sw)
|
int AUD_get_buffer_size_out (SWVoiceOut *sw)
|
||||||
@@ -1388,7 +1405,6 @@ static void audio_run_out (AudioState *s)
|
|||||||
|
|
||||||
prev_rpos = hw->rpos;
|
prev_rpos = hw->rpos;
|
||||||
played = hw->pcm_ops->run_out (hw, live);
|
played = hw->pcm_ops->run_out (hw, live);
|
||||||
replay_audio_out(&played);
|
|
||||||
if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) {
|
if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) {
|
||||||
dolog ("hw->rpos=%d hw->samples=%d played=%d\n",
|
dolog ("hw->rpos=%d hw->samples=%d played=%d\n",
|
||||||
hw->rpos, hw->samples, played);
|
hw->rpos, hw->samples, played);
|
||||||
@@ -1438,6 +1454,9 @@ static void audio_run_out (AudioState *s)
|
|||||||
while (sw) {
|
while (sw) {
|
||||||
sw1 = sw->entries.le_next;
|
sw1 = sw->entries.le_next;
|
||||||
if (!sw->active && !sw->callback.fn) {
|
if (!sw->active && !sw->callback.fn) {
|
||||||
|
#ifdef DEBUG_PLIVE
|
||||||
|
dolog ("Finishing with old voice\n");
|
||||||
|
#endif
|
||||||
audio_close_out (sw);
|
audio_close_out (sw);
|
||||||
}
|
}
|
||||||
sw = sw1;
|
sw = sw1;
|
||||||
@@ -1452,12 +1471,9 @@ static void audio_run_in (AudioState *s)
|
|||||||
|
|
||||||
while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) {
|
while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) {
|
||||||
SWVoiceIn *sw;
|
SWVoiceIn *sw;
|
||||||
int captured = 0, min;
|
int captured, min;
|
||||||
|
|
||||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
captured = hw->pcm_ops->run_in (hw);
|
||||||
captured = hw->pcm_ops->run_in(hw);
|
|
||||||
}
|
|
||||||
replay_audio_in(&captured, hw->conv_buf, &hw->wpos, hw->samples);
|
|
||||||
|
|
||||||
min = audio_pcm_hw_find_min_in (hw);
|
min = audio_pcm_hw_find_min_in (hw);
|
||||||
hw->total_samples_captured += captured - min;
|
hw->total_samples_captured += captured - min;
|
||||||
@@ -1632,6 +1648,18 @@ static struct audio_option audio_options[] = {
|
|||||||
.valp = &conf.period.hertz,
|
.valp = &conf.period.hertz,
|
||||||
.descr = "Timer period in HZ (0 - use lowest possible)"
|
.descr = "Timer period in HZ (0 - use lowest possible)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "PLIVE",
|
||||||
|
.tag = AUD_OPT_BOOL,
|
||||||
|
.valp = &conf.plive,
|
||||||
|
.descr = "(undocumented)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "LOG_TO_MONITOR",
|
||||||
|
.tag = AUD_OPT_BOOL,
|
||||||
|
.valp = &conf.log_to_monitor,
|
||||||
|
.descr = "Print logging messages to monitor instead of stderr"
|
||||||
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1744,21 +1772,13 @@ static void audio_vm_change_state_handler (void *opaque, int running,
|
|||||||
audio_reset_timer (s);
|
audio_reset_timer (s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_cleaning_up;
|
static void audio_atexit (void)
|
||||||
|
|
||||||
bool audio_is_cleaning_up(void)
|
|
||||||
{
|
|
||||||
return is_cleaning_up;
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_cleanup(void)
|
|
||||||
{
|
{
|
||||||
AudioState *s = &glob_audio_state;
|
AudioState *s = &glob_audio_state;
|
||||||
HWVoiceOut *hwo, *hwon;
|
HWVoiceOut *hwo = NULL;
|
||||||
HWVoiceIn *hwi, *hwin;
|
HWVoiceIn *hwi = NULL;
|
||||||
|
|
||||||
is_cleaning_up = true;
|
while ((hwo = audio_pcm_hw_find_any_out (hwo))) {
|
||||||
QLIST_FOREACH_SAFE(hwo, &glob_audio_state.hw_head_out, entries, hwon) {
|
|
||||||
SWVoiceCap *sc;
|
SWVoiceCap *sc;
|
||||||
|
|
||||||
if (hwo->enabled) {
|
if (hwo->enabled) {
|
||||||
@@ -1774,20 +1794,17 @@ void audio_cleanup(void)
|
|||||||
cb->ops.destroy (cb->opaque);
|
cb->ops.destroy (cb->opaque);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QLIST_REMOVE(hwo, entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QLIST_FOREACH_SAFE(hwi, &glob_audio_state.hw_head_in, entries, hwin) {
|
while ((hwi = audio_pcm_hw_find_any_in (hwi))) {
|
||||||
if (hwi->enabled) {
|
if (hwi->enabled) {
|
||||||
hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE);
|
hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE);
|
||||||
}
|
}
|
||||||
hwi->pcm_ops->fini_in (hwi);
|
hwi->pcm_ops->fini_in (hwi);
|
||||||
QLIST_REMOVE(hwi, entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->drv) {
|
if (s->drv) {
|
||||||
s->drv->fini (s->drv_opaque);
|
s->drv->fini (s->drv_opaque);
|
||||||
s->drv = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1815,9 +1832,12 @@ static void audio_init (void)
|
|||||||
QLIST_INIT (&s->hw_head_out);
|
QLIST_INIT (&s->hw_head_out);
|
||||||
QLIST_INIT (&s->hw_head_in);
|
QLIST_INIT (&s->hw_head_in);
|
||||||
QLIST_INIT (&s->cap_head);
|
QLIST_INIT (&s->cap_head);
|
||||||
atexit(audio_cleanup);
|
atexit (audio_atexit);
|
||||||
|
|
||||||
s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
|
s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
|
||||||
|
if (!s->ts) {
|
||||||
|
hw_error("Could not create audio timer\n");
|
||||||
|
}
|
||||||
|
|
||||||
audio_process_options ("AUDIO", audio_options);
|
audio_process_options ("AUDIO", audio_options);
|
||||||
|
|
||||||
@@ -1868,8 +1888,12 @@ static void audio_init (void)
|
|||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
done = !audio_driver_init (s, &no_audio_driver);
|
done = !audio_driver_init (s, &no_audio_driver);
|
||||||
assert(done);
|
if (!done) {
|
||||||
dolog("warning: Using timer based audio emulation\n");
|
hw_error("Could not initialize audio subsystem\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dolog ("warning: Using timer based audio emulation\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conf.period.hertz <= 0) {
|
if (conf.period.hertz <= 0) {
|
||||||
@@ -1880,7 +1904,8 @@ static void audio_init (void)
|
|||||||
}
|
}
|
||||||
conf.period.ticks = 1;
|
conf.period.ticks = 1;
|
||||||
} else {
|
} else {
|
||||||
conf.period.ticks = NANOSECONDS_PER_SECOND / conf.period.hertz;
|
conf.period.ticks =
|
||||||
|
muldiv64 (1, get_ticks_per_sec (), conf.period.hertz);
|
||||||
}
|
}
|
||||||
|
|
||||||
e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
|
e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
|
||||||
@@ -1982,7 +2007,8 @@ CaptureVoiceOut *AUD_add_capture (
|
|||||||
QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
|
QLIST_INSERT_HEAD (&s->cap_head, cap, entries);
|
||||||
QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
|
QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);
|
||||||
|
|
||||||
QLIST_FOREACH(hw, &glob_audio_state.hw_head_out, entries) {
|
hw = NULL;
|
||||||
|
while ((hw = audio_pcm_hw_find_any_out (hw))) {
|
||||||
audio_attach_capture (hw);
|
audio_attach_capture (hw);
|
||||||
}
|
}
|
||||||
return cap;
|
return cap;
|
||||||
@@ -2028,8 +2054,6 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque)
|
|||||||
sw = sw1;
|
sw = sw1;
|
||||||
}
|
}
|
||||||
QLIST_REMOVE (cap, entries);
|
QLIST_REMOVE (cap, entries);
|
||||||
g_free (cap->hw.mix_buf);
|
|
||||||
g_free (cap->buf);
|
|
||||||
g_free (cap);
|
g_free (cap);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef QEMU_AUDIO_H
|
#ifndef QEMU_AUDIO_H
|
||||||
#define QEMU_AUDIO_H
|
#define QEMU_AUDIO_H
|
||||||
|
|
||||||
|
#include "config-host.h"
|
||||||
#include "qemu/queue.h"
|
#include "qemu/queue.h"
|
||||||
|
|
||||||
typedef void (*audio_callback_fn) (void *opaque, int avail);
|
typedef void (*audio_callback_fn) (void *opaque, int avail);
|
||||||
@@ -163,12 +163,4 @@ static inline void *advance (void *p, int incr)
|
|||||||
int wav_start_capture (CaptureState *s, const char *path, int freq,
|
int wav_start_capture (CaptureState *s, const char *path, int freq,
|
||||||
int bits, int nchannels);
|
int bits, int nchannels);
|
||||||
|
|
||||||
bool audio_is_cleaning_up(void);
|
#endif /* audio.h */
|
||||||
void audio_cleanup(void);
|
|
||||||
|
|
||||||
void audio_sample_to_uint64(void *samples, int pos,
|
|
||||||
uint64_t *left, uint64_t *right);
|
|
||||||
void audio_sample_from_uint64(void *samples, int pos,
|
|
||||||
uint64_t left, uint64_t right);
|
|
||||||
|
|
||||||
#endif /* QEMU_AUDIO_H */
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef QEMU_AUDIO_INT_H
|
#ifndef QEMU_AUDIO_INT_H
|
||||||
#define QEMU_AUDIO_INT_H
|
#define QEMU_AUDIO_INT_H
|
||||||
|
|
||||||
@@ -157,13 +156,13 @@ struct audio_driver {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct audio_pcm_ops {
|
struct audio_pcm_ops {
|
||||||
int (*init_out)(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque);
|
int (*init_out)(HWVoiceOut *hw, struct audsettings *as);
|
||||||
void (*fini_out)(HWVoiceOut *hw);
|
void (*fini_out)(HWVoiceOut *hw);
|
||||||
int (*run_out) (HWVoiceOut *hw, int live);
|
int (*run_out) (HWVoiceOut *hw, int live);
|
||||||
int (*write) (SWVoiceOut *sw, void *buf, int size);
|
int (*write) (SWVoiceOut *sw, void *buf, int size);
|
||||||
int (*ctl_out) (HWVoiceOut *hw, int cmd, ...);
|
int (*ctl_out) (HWVoiceOut *hw, int cmd, ...);
|
||||||
|
|
||||||
int (*init_in) (HWVoiceIn *hw, struct audsettings *as, void *drv_opaque);
|
int (*init_in) (HWVoiceIn *hw, struct audsettings *as);
|
||||||
void (*fini_in) (HWVoiceIn *hw);
|
void (*fini_in) (HWVoiceIn *hw);
|
||||||
int (*run_in) (HWVoiceIn *hw);
|
int (*run_in) (HWVoiceIn *hw);
|
||||||
int (*read) (SWVoiceIn *sw, void *buf, int size);
|
int (*read) (SWVoiceIn *sw, void *buf, int size);
|
||||||
@@ -207,11 +206,14 @@ extern struct audio_driver no_audio_driver;
|
|||||||
extern struct audio_driver oss_audio_driver;
|
extern struct audio_driver oss_audio_driver;
|
||||||
extern struct audio_driver sdl_audio_driver;
|
extern struct audio_driver sdl_audio_driver;
|
||||||
extern struct audio_driver wav_audio_driver;
|
extern struct audio_driver wav_audio_driver;
|
||||||
|
extern struct audio_driver fmod_audio_driver;
|
||||||
extern struct audio_driver alsa_audio_driver;
|
extern struct audio_driver alsa_audio_driver;
|
||||||
extern struct audio_driver coreaudio_audio_driver;
|
extern struct audio_driver coreaudio_audio_driver;
|
||||||
extern struct audio_driver dsound_audio_driver;
|
extern struct audio_driver dsound_audio_driver;
|
||||||
|
extern struct audio_driver esd_audio_driver;
|
||||||
extern struct audio_driver pa_audio_driver;
|
extern struct audio_driver pa_audio_driver;
|
||||||
extern struct audio_driver spice_audio_driver;
|
extern struct audio_driver spice_audio_driver;
|
||||||
|
extern struct audio_driver winwave_audio_driver;
|
||||||
extern const struct mixeng_volume nominal_volume;
|
extern const struct mixeng_volume nominal_volume;
|
||||||
|
|
||||||
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
|
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
|
||||||
@@ -258,4 +260,4 @@ static inline int audio_ring_dist (int dst, int src, int len)
|
|||||||
#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__)
|
#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* QEMU_AUDIO_INT_H */
|
#endif /* audio_int.h */
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ int audio_pt_wait (struct audio_pt *, const char *);
|
|||||||
int audio_pt_unlock_and_signal (struct audio_pt *, const char *);
|
int audio_pt_unlock_and_signal (struct audio_pt *, const char *);
|
||||||
int audio_pt_join (struct audio_pt *, void **, const char *);
|
int audio_pt_join (struct audio_pt *, void **, const char *);
|
||||||
|
|
||||||
#endif /* QEMU_AUDIO_PT_INT_H */
|
#endif /* audio_pt_int.h */
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as)
|
|||||||
#ifdef DAC
|
#ifdef DAC
|
||||||
QLIST_INIT (&hw->cap_head);
|
QLIST_INIT (&hw->cap_head);
|
||||||
#endif
|
#endif
|
||||||
if (glue (hw->pcm_ops->init_, TYPE) (hw, as, s->drv_opaque)) {
|
if (glue (hw->pcm_ops->init_, TYPE) (hw, as)) {
|
||||||
goto err0;
|
goto err0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +398,10 @@ SW *glue (AUD_open_, TYPE) (
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
AudioState *s = &glob_audio_state;
|
AudioState *s = &glob_audio_state;
|
||||||
|
#ifdef DAC
|
||||||
|
int live = 0;
|
||||||
|
SW *old_sw = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) {
|
if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) {
|
||||||
dolog ("card=%p name=%p callback_fn=%p as=%p\n",
|
dolog ("card=%p name=%p callback_fn=%p as=%p\n",
|
||||||
@@ -422,6 +426,29 @@ SW *glue (AUD_open_, TYPE) (
|
|||||||
return sw;
|
return sw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DAC
|
||||||
|
if (conf.plive && sw && (!sw->active && !sw->empty)) {
|
||||||
|
live = sw->total_hw_samples_mixed;
|
||||||
|
|
||||||
|
#ifdef DEBUG_PLIVE
|
||||||
|
dolog ("Replacing voice %s with %d live samples\n", SW_NAME (sw), live);
|
||||||
|
dolog ("Old %s freq %d, bits %d, channels %d\n",
|
||||||
|
SW_NAME (sw), sw->info.freq, sw->info.bits, sw->info.nchannels);
|
||||||
|
dolog ("New %s freq %d, bits %d, channels %d\n",
|
||||||
|
name,
|
||||||
|
as->freq,
|
||||||
|
(as->fmt == AUD_FMT_S16 || as->fmt == AUD_FMT_U16) ? 16 : 8,
|
||||||
|
as->nchannels);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (live) {
|
||||||
|
old_sw = sw;
|
||||||
|
old_sw->callback.fn = NULL;
|
||||||
|
sw = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!glue (conf.fixed_, TYPE).enabled && sw) {
|
if (!glue (conf.fixed_, TYPE).enabled && sw) {
|
||||||
glue (AUD_close_, TYPE) (card, sw);
|
glue (AUD_close_, TYPE) (card, sw);
|
||||||
sw = NULL;
|
sw = NULL;
|
||||||
@@ -454,6 +481,20 @@ SW *glue (AUD_open_, TYPE) (
|
|||||||
sw->callback.fn = callback_fn;
|
sw->callback.fn = callback_fn;
|
||||||
sw->callback.opaque = callback_opaque;
|
sw->callback.opaque = callback_opaque;
|
||||||
|
|
||||||
|
#ifdef DAC
|
||||||
|
if (live) {
|
||||||
|
int mixed =
|
||||||
|
(live << old_sw->info.shift)
|
||||||
|
* old_sw->info.bytes_per_second
|
||||||
|
/ sw->info.bytes_per_second;
|
||||||
|
|
||||||
|
#ifdef DEBUG_PLIVE
|
||||||
|
dolog ("Silence will be mixed %d\n", mixed);
|
||||||
|
#endif
|
||||||
|
sw->total_hw_samples_mixed += mixed;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef DEBUG_AUDIO
|
#ifdef DEBUG_AUDIO
|
||||||
dolog ("%s\n", name);
|
dolog ("%s\n", name);
|
||||||
audio_pcm_print_info ("hw", &sw->hw->info);
|
audio_pcm_print_info ("hw", &sw->hw->info);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* public domain */
|
/* public domain */
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
|
||||||
#define AUDIO_CAP "win-int"
|
#define AUDIO_CAP "win-int"
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include <CoreAudio/CoreAudio.h>
|
#include <CoreAudio/CoreAudio.h>
|
||||||
|
#include <string.h> /* strerror */
|
||||||
#include <pthread.h> /* pthread_X */
|
#include <pthread.h> /* pthread_X */
|
||||||
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
@@ -32,248 +32,28 @@
|
|||||||
#define AUDIO_CAP "coreaudio"
|
#define AUDIO_CAP "coreaudio"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
|
|
||||||
#ifndef MAC_OS_X_VERSION_10_6
|
struct {
|
||||||
#define MAC_OS_X_VERSION_10_6 1060
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int buffer_frames;
|
int buffer_frames;
|
||||||
int nbuffers;
|
int nbuffers;
|
||||||
} CoreaudioConf;
|
int isAtexit;
|
||||||
|
} conf = {
|
||||||
|
.buffer_frames = 512,
|
||||||
|
.nbuffers = 4,
|
||||||
|
.isAtexit = 0
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct coreaudioVoiceOut {
|
typedef struct coreaudioVoiceOut {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
int isAtexit;
|
||||||
AudioDeviceID outputDeviceID;
|
AudioDeviceID outputDeviceID;
|
||||||
UInt32 audioDevicePropertyBufferFrameSize;
|
UInt32 audioDevicePropertyBufferFrameSize;
|
||||||
AudioStreamBasicDescription outputStreamBasicDescription;
|
AudioStreamBasicDescription outputStreamBasicDescription;
|
||||||
AudioDeviceIOProcID ioprocid;
|
|
||||||
int live;
|
int live;
|
||||||
int decr;
|
int decr;
|
||||||
int rpos;
|
int rpos;
|
||||||
} coreaudioVoiceOut;
|
} coreaudioVoiceOut;
|
||||||
|
|
||||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
|
|
||||||
/* The APIs used here only become available from 10.6 */
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_voice(AudioDeviceID *id)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*id);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioHardwarePropertyDefaultOutputDevice,
|
|
||||||
kAudioObjectPropertyScopeGlobal,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
&size,
|
|
||||||
id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_framesizerange(AudioDeviceID id,
|
|
||||||
AudioValueRange *framerange)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framerange);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyBufferFrameSizeRange,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
&size,
|
|
||||||
framerange);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framesize);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyBufferFrameSize,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
&size,
|
|
||||||
framesize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framesize);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyBufferFrameSize,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectSetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
size,
|
|
||||||
framesize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_streamformat(AudioDeviceID id,
|
|
||||||
AudioStreamBasicDescription *d)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*d);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyStreamFormat,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
&size,
|
|
||||||
d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_set_streamformat(AudioDeviceID id,
|
|
||||||
AudioStreamBasicDescription *d)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*d);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyStreamFormat,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectSetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
size,
|
|
||||||
d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*result);
|
|
||||||
AudioObjectPropertyAddress addr = {
|
|
||||||
kAudioDevicePropertyDeviceIsRunning,
|
|
||||||
kAudioDevicePropertyScopeOutput,
|
|
||||||
kAudioObjectPropertyElementMaster
|
|
||||||
};
|
|
||||||
|
|
||||||
return AudioObjectGetPropertyData(id,
|
|
||||||
&addr,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
&size,
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/* Legacy versions of functions using deprecated APIs */
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_voice(AudioDeviceID *id)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*id);
|
|
||||||
|
|
||||||
return AudioHardwareGetProperty(
|
|
||||||
kAudioHardwarePropertyDefaultOutputDevice,
|
|
||||||
&size,
|
|
||||||
id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_framesizerange(AudioDeviceID id,
|
|
||||||
AudioValueRange *framerange)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framerange);
|
|
||||||
|
|
||||||
return AudioDeviceGetProperty(
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
kAudioDevicePropertyBufferFrameSizeRange,
|
|
||||||
&size,
|
|
||||||
framerange);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framesize);
|
|
||||||
|
|
||||||
return AudioDeviceGetProperty(
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
kAudioDevicePropertyBufferFrameSize,
|
|
||||||
&size,
|
|
||||||
framesize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*framesize);
|
|
||||||
|
|
||||||
return AudioDeviceSetProperty(
|
|
||||||
id,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
kAudioDevicePropertyBufferFrameSize,
|
|
||||||
size,
|
|
||||||
framesize);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_streamformat(AudioDeviceID id,
|
|
||||||
AudioStreamBasicDescription *d)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*d);
|
|
||||||
|
|
||||||
return AudioDeviceGetProperty(
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
kAudioDevicePropertyStreamFormat,
|
|
||||||
&size,
|
|
||||||
d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_set_streamformat(AudioDeviceID id,
|
|
||||||
AudioStreamBasicDescription *d)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*d);
|
|
||||||
|
|
||||||
return AudioDeviceSetProperty(
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
kAudioDevicePropertyStreamFormat,
|
|
||||||
size,
|
|
||||||
d);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result)
|
|
||||||
{
|
|
||||||
UInt32 size = sizeof(*result);
|
|
||||||
|
|
||||||
return AudioDeviceGetProperty(
|
|
||||||
id,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
kAudioDevicePropertyDeviceIsRunning,
|
|
||||||
&size,
|
|
||||||
result);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void coreaudio_logstatus (OSStatus status)
|
static void coreaudio_logstatus (OSStatus status)
|
||||||
{
|
{
|
||||||
const char *str = "BUG";
|
const char *str = "BUG";
|
||||||
@@ -368,7 +148,10 @@ static inline UInt32 isPlaying (AudioDeviceID outputDeviceID)
|
|||||||
{
|
{
|
||||||
OSStatus status;
|
OSStatus status;
|
||||||
UInt32 result = 0;
|
UInt32 result = 0;
|
||||||
status = coreaudio_get_isrunning(outputDeviceID, &result);
|
UInt32 propertySize = sizeof(outputDeviceID);
|
||||||
|
status = AudioDeviceGetProperty(
|
||||||
|
outputDeviceID, 0, 0,
|
||||||
|
kAudioDevicePropertyDeviceIsRunning, &propertySize, &result);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr(status,
|
coreaudio_logerr(status,
|
||||||
"Could not determine whether Device is playing\n");
|
"Could not determine whether Device is playing\n");
|
||||||
@@ -376,6 +159,11 @@ static inline UInt32 isPlaying (AudioDeviceID outputDeviceID)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void coreaudio_atexit (void)
|
||||||
|
{
|
||||||
|
conf.isAtexit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name)
|
static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@@ -499,15 +287,14 @@ static int coreaudio_write (SWVoiceOut *sw, void *buf, int len)
|
|||||||
return audio_pcm_sw_write (sw, buf, len);
|
return audio_pcm_sw_write (sw, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int coreaudio_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
OSStatus status;
|
OSStatus status;
|
||||||
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
||||||
|
UInt32 propertySize;
|
||||||
int err;
|
int err;
|
||||||
const char *typ = "playback";
|
const char *typ = "playback";
|
||||||
AudioValueRange frameRange;
|
AudioValueRange frameRange;
|
||||||
CoreaudioConf *conf = drv_opaque;
|
|
||||||
|
|
||||||
/* create mutex */
|
/* create mutex */
|
||||||
err = pthread_mutex_init(&core->mutex, NULL);
|
err = pthread_mutex_init(&core->mutex, NULL);
|
||||||
@@ -518,7 +305,12 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
|
|
||||||
audio_pcm_init_info (&hw->info, as);
|
audio_pcm_init_info (&hw->info, as);
|
||||||
|
|
||||||
status = coreaudio_get_voice(&core->outputDeviceID);
|
/* open default output device */
|
||||||
|
propertySize = sizeof(core->outputDeviceID);
|
||||||
|
status = AudioHardwareGetProperty(
|
||||||
|
kAudioHardwarePropertyDefaultOutputDevice,
|
||||||
|
&propertySize,
|
||||||
|
&core->outputDeviceID);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ,
|
coreaudio_logerr2 (status, typ,
|
||||||
"Could not get default output Device\n");
|
"Could not get default output Device\n");
|
||||||
@@ -530,7 +322,13 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* get minimum and maximum buffer frame sizes */
|
/* get minimum and maximum buffer frame sizes */
|
||||||
status = coreaudio_get_framesizerange(core->outputDeviceID,
|
propertySize = sizeof(frameRange);
|
||||||
|
status = AudioDeviceGetProperty(
|
||||||
|
core->outputDeviceID,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
kAudioDevicePropertyBufferFrameSizeRange,
|
||||||
|
&propertySize,
|
||||||
&frameRange);
|
&frameRange);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ,
|
coreaudio_logerr2 (status, typ,
|
||||||
@@ -538,20 +336,27 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frameRange.mMinimum > conf->buffer_frames) {
|
if (frameRange.mMinimum > conf.buffer_frames) {
|
||||||
core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
|
core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
|
||||||
dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
|
dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
|
||||||
}
|
}
|
||||||
else if (frameRange.mMaximum < conf->buffer_frames) {
|
else if (frameRange.mMaximum < conf.buffer_frames) {
|
||||||
core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
|
core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
|
||||||
dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
|
dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core->audioDevicePropertyBufferFrameSize = conf->buffer_frames;
|
core->audioDevicePropertyBufferFrameSize = conf.buffer_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* set Buffer Frame Size */
|
/* set Buffer Frame Size */
|
||||||
status = coreaudio_set_framesize(core->outputDeviceID,
|
propertySize = sizeof(core->audioDevicePropertyBufferFrameSize);
|
||||||
|
status = AudioDeviceSetProperty(
|
||||||
|
core->outputDeviceID,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
kAudioDevicePropertyBufferFrameSize,
|
||||||
|
propertySize,
|
||||||
&core->audioDevicePropertyBufferFrameSize);
|
&core->audioDevicePropertyBufferFrameSize);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ,
|
coreaudio_logerr2 (status, typ,
|
||||||
@@ -561,17 +366,29 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* get Buffer Frame Size */
|
/* get Buffer Frame Size */
|
||||||
status = coreaudio_get_framesize(core->outputDeviceID,
|
propertySize = sizeof(core->audioDevicePropertyBufferFrameSize);
|
||||||
|
status = AudioDeviceGetProperty(
|
||||||
|
core->outputDeviceID,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
kAudioDevicePropertyBufferFrameSize,
|
||||||
|
&propertySize,
|
||||||
&core->audioDevicePropertyBufferFrameSize);
|
&core->audioDevicePropertyBufferFrameSize);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ,
|
coreaudio_logerr2 (status, typ,
|
||||||
"Could not get device buffer frame size\n");
|
"Could not get device buffer frame size\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
hw->samples = conf->nbuffers * core->audioDevicePropertyBufferFrameSize;
|
hw->samples = conf.nbuffers * core->audioDevicePropertyBufferFrameSize;
|
||||||
|
|
||||||
/* get StreamFormat */
|
/* get StreamFormat */
|
||||||
status = coreaudio_get_streamformat(core->outputDeviceID,
|
propertySize = sizeof(core->outputStreamBasicDescription);
|
||||||
|
status = AudioDeviceGetProperty(
|
||||||
|
core->outputDeviceID,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
kAudioDevicePropertyStreamFormat,
|
||||||
|
&propertySize,
|
||||||
&core->outputStreamBasicDescription);
|
&core->outputStreamBasicDescription);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ,
|
coreaudio_logerr2 (status, typ,
|
||||||
@@ -582,7 +399,14 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
|
|
||||||
/* set Samplerate */
|
/* set Samplerate */
|
||||||
core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq;
|
core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq;
|
||||||
status = coreaudio_set_streamformat(core->outputDeviceID,
|
propertySize = sizeof(core->outputStreamBasicDescription);
|
||||||
|
status = AudioDeviceSetProperty(
|
||||||
|
core->outputDeviceID,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
kAudioDevicePropertyStreamFormat,
|
||||||
|
propertySize,
|
||||||
&core->outputStreamBasicDescription);
|
&core->outputStreamBasicDescription);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n",
|
coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n",
|
||||||
@@ -592,12 +416,8 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* set Callback */
|
/* set Callback */
|
||||||
core->ioprocid = NULL;
|
status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw);
|
||||||
status = AudioDeviceCreateIOProcID(core->outputDeviceID,
|
if (status != kAudioHardwareNoError) {
|
||||||
audioDeviceIOProc,
|
|
||||||
hw,
|
|
||||||
&core->ioprocid);
|
|
||||||
if (status != kAudioHardwareNoError || core->ioprocid == NULL) {
|
|
||||||
coreaudio_logerr2 (status, typ, "Could not set IOProc\n");
|
coreaudio_logerr2 (status, typ, "Could not set IOProc\n");
|
||||||
core->outputDeviceID = kAudioDeviceUnknown;
|
core->outputDeviceID = kAudioDeviceUnknown;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -605,10 +425,10 @@ static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
|
|
||||||
/* start Playback */
|
/* start Playback */
|
||||||
if (!isPlaying(core->outputDeviceID)) {
|
if (!isPlaying(core->outputDeviceID)) {
|
||||||
status = AudioDeviceStart(core->outputDeviceID, core->ioprocid);
|
status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr2 (status, typ, "Could not start playback\n");
|
coreaudio_logerr2 (status, typ, "Could not start playback\n");
|
||||||
AudioDeviceDestroyIOProcID(core->outputDeviceID, core->ioprocid);
|
AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc);
|
||||||
core->outputDeviceID = kAudioDeviceUnknown;
|
core->outputDeviceID = kAudioDeviceUnknown;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -623,18 +443,18 @@ static void coreaudio_fini_out (HWVoiceOut *hw)
|
|||||||
int err;
|
int err;
|
||||||
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
|
||||||
|
|
||||||
if (!audio_is_cleaning_up()) {
|
if (!conf.isAtexit) {
|
||||||
/* stop playback */
|
/* stop playback */
|
||||||
if (isPlaying(core->outputDeviceID)) {
|
if (isPlaying(core->outputDeviceID)) {
|
||||||
status = AudioDeviceStop(core->outputDeviceID, core->ioprocid);
|
status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr (status, "Could not stop playback\n");
|
coreaudio_logerr (status, "Could not stop playback\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove callback */
|
/* remove callback */
|
||||||
status = AudioDeviceDestroyIOProcID(core->outputDeviceID,
|
status = AudioDeviceRemoveIOProc(core->outputDeviceID,
|
||||||
core->ioprocid);
|
audioDeviceIOProc);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr (status, "Could not remove IOProc\n");
|
coreaudio_logerr (status, "Could not remove IOProc\n");
|
||||||
}
|
}
|
||||||
@@ -657,7 +477,7 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
case VOICE_ENABLE:
|
case VOICE_ENABLE:
|
||||||
/* start playback */
|
/* start playback */
|
||||||
if (!isPlaying(core->outputDeviceID)) {
|
if (!isPlaying(core->outputDeviceID)) {
|
||||||
status = AudioDeviceStart(core->outputDeviceID, core->ioprocid);
|
status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc);
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr (status, "Could not resume playback\n");
|
coreaudio_logerr (status, "Could not resume playback\n");
|
||||||
}
|
}
|
||||||
@@ -666,10 +486,9 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
|
|
||||||
case VOICE_DISABLE:
|
case VOICE_DISABLE:
|
||||||
/* stop playback */
|
/* stop playback */
|
||||||
if (!audio_is_cleaning_up()) {
|
if (!conf.isAtexit) {
|
||||||
if (isPlaying(core->outputDeviceID)) {
|
if (isPlaying(core->outputDeviceID)) {
|
||||||
status = AudioDeviceStop(core->outputDeviceID,
|
status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc);
|
||||||
core->ioprocid);
|
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
coreaudio_logerr (status, "Could not pause playback\n");
|
coreaudio_logerr (status, "Could not pause playback\n");
|
||||||
}
|
}
|
||||||
@@ -680,35 +499,28 @@ static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CoreaudioConf glob_conf = {
|
|
||||||
.buffer_frames = 512,
|
|
||||||
.nbuffers = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *coreaudio_audio_init (void)
|
static void *coreaudio_audio_init (void)
|
||||||
{
|
{
|
||||||
CoreaudioConf *conf = g_malloc(sizeof(CoreaudioConf));
|
atexit(coreaudio_atexit);
|
||||||
*conf = glob_conf;
|
return &coreaudio_audio_init;
|
||||||
|
|
||||||
return conf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coreaudio_audio_fini (void *opaque)
|
static void coreaudio_audio_fini (void *opaque)
|
||||||
{
|
{
|
||||||
g_free(opaque);
|
(void) opaque;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option coreaudio_options[] = {
|
static struct audio_option coreaudio_options[] = {
|
||||||
{
|
{
|
||||||
.name = "BUFFER_SIZE",
|
.name = "BUFFER_SIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.buffer_frames,
|
.valp = &conf.buffer_frames,
|
||||||
.descr = "Size of the buffer in frames"
|
.descr = "Size of the buffer in frames"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "BUFFER_COUNT",
|
.name = "BUFFER_COUNT",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.nbuffers,
|
.valp = &conf.nbuffers,
|
||||||
.descr = "Number of buffers"
|
.descr = "Number of buffers"
|
||||||
},
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
|
|||||||
@@ -67,11 +67,11 @@ static int glue (dsound_lock_, TYPE) (
|
|||||||
LPVOID *p2p,
|
LPVOID *p2p,
|
||||||
DWORD *blen1p,
|
DWORD *blen1p,
|
||||||
DWORD *blen2p,
|
DWORD *blen2p,
|
||||||
int entire,
|
int entire
|
||||||
dsound *s
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
int i;
|
||||||
LPVOID p1 = NULL, p2 = NULL;
|
LPVOID p1 = NULL, p2 = NULL;
|
||||||
DWORD blen1 = 0, blen2 = 0;
|
DWORD blen1 = 0, blen2 = 0;
|
||||||
DWORD flag;
|
DWORD flag;
|
||||||
@@ -81,21 +81,40 @@ static int glue (dsound_lock_, TYPE) (
|
|||||||
#else
|
#else
|
||||||
flag = entire ? DSBLOCK_ENTIREBUFFER : 0;
|
flag = entire ? DSBLOCK_ENTIREBUFFER : 0;
|
||||||
#endif
|
#endif
|
||||||
hr = glue(IFACE, _Lock)(buf, pos, len, &p1, &blen1, &p2, &blen2, flag);
|
for (i = 0; i < conf.lock_retries; ++i) {
|
||||||
|
hr = glue (IFACE, _Lock) (
|
||||||
|
buf,
|
||||||
|
pos,
|
||||||
|
len,
|
||||||
|
&p1,
|
||||||
|
&blen1,
|
||||||
|
&p2,
|
||||||
|
&blen2,
|
||||||
|
flag
|
||||||
|
);
|
||||||
|
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
#ifndef DSBTYPE_IN
|
#ifndef DSBTYPE_IN
|
||||||
if (hr == DSERR_BUFFERLOST) {
|
if (hr == DSERR_BUFFERLOST) {
|
||||||
if (glue (dsound_restore_, TYPE) (buf, s)) {
|
if (glue (dsound_restore_, TYPE) (buf)) {
|
||||||
dsound_logerr (hr, "Could not lock " NAME "\n");
|
dsound_logerr (hr, "Could not lock " NAME "\n");
|
||||||
}
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
dsound_logerr (hr, "Could not lock " NAME "\n");
|
dsound_logerr (hr, "Could not lock " NAME "\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == conf.lock_retries) {
|
||||||
|
dolog ("%d attempts to lock " NAME " failed\n", i);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) {
|
if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) {
|
||||||
dolog ("DirectSound returned misaligned buffer %ld %ld\n",
|
dolog ("DirectSound returned misaligned buffer %ld %ld\n",
|
||||||
blen1, blen2);
|
blen1, blen2);
|
||||||
@@ -155,19 +174,16 @@ static void dsound_fini_out (HWVoiceOut *hw)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DSBTYPE_IN
|
#ifdef DSBTYPE_IN
|
||||||
static int dsound_init_in(HWVoiceIn *hw, struct audsettings *as,
|
static int dsound_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
#else
|
#else
|
||||||
static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int dsound_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
dsound *s = drv_opaque;
|
dsound *s = &glob_dsound;
|
||||||
WAVEFORMATEX wfx;
|
WAVEFORMATEX wfx;
|
||||||
struct audsettings obt_as;
|
struct audsettings obt_as;
|
||||||
DSoundConf *conf = &s->conf;
|
|
||||||
#ifdef DSBTYPE_IN
|
#ifdef DSBTYPE_IN
|
||||||
const char *typ = "ADC";
|
const char *typ = "ADC";
|
||||||
DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
|
DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
|
||||||
@@ -194,7 +210,7 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
bd.dwSize = sizeof (bd);
|
bd.dwSize = sizeof (bd);
|
||||||
bd.lpwfxFormat = &wfx;
|
bd.lpwfxFormat = &wfx;
|
||||||
#ifdef DSBTYPE_IN
|
#ifdef DSBTYPE_IN
|
||||||
bd.dwBufferBytes = conf->bufsize_in;
|
bd.dwBufferBytes = conf.bufsize_in;
|
||||||
hr = IDirectSoundCapture_CreateCaptureBuffer (
|
hr = IDirectSoundCapture_CreateCaptureBuffer (
|
||||||
s->dsound_capture,
|
s->dsound_capture,
|
||||||
&bd,
|
&bd,
|
||||||
@@ -203,7 +219,7 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
);
|
);
|
||||||
#else
|
#else
|
||||||
bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
|
bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
|
||||||
bd.dwBufferBytes = conf->bufsize_out;
|
bd.dwBufferBytes = conf.bufsize_out;
|
||||||
hr = IDirectSound_CreateSoundBuffer (
|
hr = IDirectSound_CreateSoundBuffer (
|
||||||
s->dsound,
|
s->dsound,
|
||||||
&bd,
|
&bd,
|
||||||
@@ -253,7 +269,6 @@ static int dsound_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
hw->samples = bc.dwBufferBytes >> hw->info.shift;
|
hw->samples = bc.dwBufferBytes >> hw->info.shift;
|
||||||
ds->s = s;
|
|
||||||
|
|
||||||
#ifdef DEBUG_DSOUND
|
#ifdef DEBUG_DSOUND
|
||||||
dolog ("caps %ld, desc %ld\n",
|
dolog ("caps %ld, desc %ld\n",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
* SEAL 1.07 by Carlos 'pel' Hasan was used as documentation
|
* SEAL 1.07 by Carlos 'pel' Hasan was used as documentation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
@@ -42,25 +41,42 @@
|
|||||||
|
|
||||||
/* #define DEBUG_DSOUND */
|
/* #define DEBUG_DSOUND */
|
||||||
|
|
||||||
typedef struct {
|
static struct {
|
||||||
|
int lock_retries;
|
||||||
|
int restore_retries;
|
||||||
|
int getstatus_retries;
|
||||||
|
int set_primary;
|
||||||
int bufsize_in;
|
int bufsize_in;
|
||||||
int bufsize_out;
|
int bufsize_out;
|
||||||
|
struct audsettings settings;
|
||||||
int latency_millis;
|
int latency_millis;
|
||||||
} DSoundConf;
|
} conf = {
|
||||||
|
.lock_retries = 1,
|
||||||
|
.restore_retries = 1,
|
||||||
|
.getstatus_retries = 1,
|
||||||
|
.set_primary = 0,
|
||||||
|
.bufsize_in = 16384,
|
||||||
|
.bufsize_out = 16384,
|
||||||
|
.settings.freq = 44100,
|
||||||
|
.settings.nchannels = 2,
|
||||||
|
.settings.fmt = AUD_FMT_S16,
|
||||||
|
.latency_millis = 10
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
LPDIRECTSOUND dsound;
|
LPDIRECTSOUND dsound;
|
||||||
LPDIRECTSOUNDCAPTURE dsound_capture;
|
LPDIRECTSOUNDCAPTURE dsound_capture;
|
||||||
|
LPDIRECTSOUNDBUFFER dsound_primary_buffer;
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
DSoundConf conf;
|
|
||||||
} dsound;
|
} dsound;
|
||||||
|
|
||||||
|
static dsound glob_dsound;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
LPDIRECTSOUNDBUFFER dsound_buffer;
|
LPDIRECTSOUNDBUFFER dsound_buffer;
|
||||||
DWORD old_pos;
|
DWORD old_pos;
|
||||||
int first_time;
|
int first_time;
|
||||||
dsound *s;
|
|
||||||
#ifdef DEBUG_DSOUND
|
#ifdef DEBUG_DSOUND
|
||||||
DWORD old_ppos;
|
DWORD old_ppos;
|
||||||
DWORD played;
|
DWORD played;
|
||||||
@@ -72,7 +88,6 @@ typedef struct {
|
|||||||
HWVoiceIn hw;
|
HWVoiceIn hw;
|
||||||
int first_time;
|
int first_time;
|
||||||
LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer;
|
LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer;
|
||||||
dsound *s;
|
|
||||||
} DSoundVoiceIn;
|
} DSoundVoiceIn;
|
||||||
|
|
||||||
static void dsound_log_hresult (HRESULT hr)
|
static void dsound_log_hresult (HRESULT hr)
|
||||||
@@ -266,17 +281,29 @@ static void print_wave_format (WAVEFORMATEX *wfx)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s)
|
static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < conf.restore_retries; ++i) {
|
||||||
hr = IDirectSoundBuffer_Restore (dsb);
|
hr = IDirectSoundBuffer_Restore (dsb);
|
||||||
|
|
||||||
if (hr != DS_OK) {
|
switch (hr) {
|
||||||
|
case DS_OK:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case DSERR_BUFFERLOST:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
dsound_logerr (hr, "Could not restore playback buffer\n");
|
dsound_logerr (hr, "Could not restore playback buffer\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
dolog ("%d attempts to restore playback buffer failed\n", i);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "dsound_template.h"
|
#include "dsound_template.h"
|
||||||
@@ -284,11 +311,12 @@ static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s)
|
|||||||
#include "dsound_template.h"
|
#include "dsound_template.h"
|
||||||
#undef DSBTYPE_IN
|
#undef DSBTYPE_IN
|
||||||
|
|
||||||
static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp,
|
static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp)
|
||||||
dsound *s)
|
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < conf.getstatus_retries; ++i) {
|
||||||
hr = IDirectSoundBuffer_GetStatus (dsb, statusp);
|
hr = IDirectSoundBuffer_GetStatus (dsb, statusp);
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
dsound_logerr (hr, "Could not get playback buffer status\n");
|
dsound_logerr (hr, "Could not get playback buffer status\n");
|
||||||
@@ -296,9 +324,13 @@ static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (*statusp & DSERR_BUFFERLOST) {
|
if (*statusp & DSERR_BUFFERLOST) {
|
||||||
dsound_restore_out(dsb, s);
|
if (dsound_restore_out (dsb)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -344,8 +376,7 @@ static void dsound_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len)
|
|||||||
hw->rpos = pos % hw->samples;
|
hw->rpos = pos % hw->samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,
|
static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb)
|
||||||
dsound *s)
|
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
LPVOID p1, p2;
|
LPVOID p1, p2;
|
||||||
@@ -358,8 +389,7 @@ static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,
|
|||||||
hw->samples << hw->info.shift,
|
hw->samples << hw->info.shift,
|
||||||
&p1, &p2,
|
&p1, &p2,
|
||||||
&blen1, &blen2,
|
&blen1, &blen2,
|
||||||
1,
|
1
|
||||||
s
|
|
||||||
);
|
);
|
||||||
if (err) {
|
if (err) {
|
||||||
return;
|
return;
|
||||||
@@ -385,9 +415,25 @@ static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,
|
|||||||
dsound_unlock_out (dsb, p1, p2, blen1, blen2);
|
dsound_unlock_out (dsb, p1, p2, blen1, blen2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dsound_open (dsound *s)
|
static void dsound_close (dsound *s)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
if (s->dsound_primary_buffer) {
|
||||||
|
hr = IDirectSoundBuffer_Release (s->dsound_primary_buffer);
|
||||||
|
if (FAILED (hr)) {
|
||||||
|
dsound_logerr (hr, "Could not release primary buffer\n");
|
||||||
|
}
|
||||||
|
s->dsound_primary_buffer = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dsound_open (dsound *s)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
HRESULT hr;
|
||||||
|
WAVEFORMATEX wfx;
|
||||||
|
DSBUFFERDESC dsbd;
|
||||||
HWND hwnd;
|
HWND hwnd;
|
||||||
|
|
||||||
hwnd = GetForegroundWindow ();
|
hwnd = GetForegroundWindow ();
|
||||||
@@ -403,7 +449,63 @@ static int dsound_open (dsound *s)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!conf.set_primary) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = waveformat_from_audio_settings (&wfx, &conf.settings);
|
||||||
|
if (err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset (&dsbd, 0, sizeof (dsbd));
|
||||||
|
dsbd.dwSize = sizeof (dsbd);
|
||||||
|
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||||
|
dsbd.dwBufferBytes = 0;
|
||||||
|
dsbd.lpwfxFormat = NULL;
|
||||||
|
|
||||||
|
hr = IDirectSound_CreateSoundBuffer (
|
||||||
|
s->dsound,
|
||||||
|
&dsbd,
|
||||||
|
&s->dsound_primary_buffer,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
if (FAILED (hr)) {
|
||||||
|
dsound_logerr (hr, "Could not create primary playback buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = IDirectSoundBuffer_SetFormat (s->dsound_primary_buffer, &wfx);
|
||||||
|
if (FAILED (hr)) {
|
||||||
|
dsound_logerr (hr, "Could not set primary playback buffer format\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = IDirectSoundBuffer_GetFormat (
|
||||||
|
s->dsound_primary_buffer,
|
||||||
|
&wfx,
|
||||||
|
sizeof (wfx),
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
if (FAILED (hr)) {
|
||||||
|
dsound_logerr (hr, "Could not get primary playback buffer format\n");
|
||||||
|
goto fail0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DSOUND
|
||||||
|
dolog ("Primary\n");
|
||||||
|
print_wave_format (&wfx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
err = waveformat_to_audio_settings (&wfx, &s->settings);
|
||||||
|
if (err) {
|
||||||
|
goto fail0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail0:
|
||||||
|
dsound_close (s);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
||||||
@@ -412,7 +514,6 @@ static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
DWORD status;
|
DWORD status;
|
||||||
DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
|
DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
|
||||||
LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
|
LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
|
||||||
dsound *s = ds->s;
|
|
||||||
|
|
||||||
if (!dsb) {
|
if (!dsb) {
|
||||||
dolog ("Attempt to control voice without a buffer\n");
|
dolog ("Attempt to control voice without a buffer\n");
|
||||||
@@ -421,7 +522,7 @@ static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case VOICE_ENABLE:
|
case VOICE_ENABLE:
|
||||||
if (dsound_get_status_out (dsb, &status, s)) {
|
if (dsound_get_status_out (dsb, &status)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +531,7 @@ static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
dsound_clear_sample (hw, dsb, s);
|
dsound_clear_sample (hw, dsb);
|
||||||
|
|
||||||
hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING);
|
hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING);
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
@@ -440,7 +541,7 @@ static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case VOICE_DISABLE:
|
case VOICE_DISABLE:
|
||||||
if (dsound_get_status_out (dsb, &status, s)) {
|
if (dsound_get_status_out (dsb, &status)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,8 +578,6 @@ static int dsound_run_out (HWVoiceOut *hw, int live)
|
|||||||
DWORD wpos, ppos, old_pos;
|
DWORD wpos, ppos, old_pos;
|
||||||
LPVOID p1, p2;
|
LPVOID p1, p2;
|
||||||
int bufsize;
|
int bufsize;
|
||||||
dsound *s = ds->s;
|
|
||||||
DSoundConf *conf = &s->conf;
|
|
||||||
|
|
||||||
if (!dsb) {
|
if (!dsb) {
|
||||||
dolog ("Attempt to run empty with playback buffer\n");
|
dolog ("Attempt to run empty with playback buffer\n");
|
||||||
@@ -501,14 +600,14 @@ static int dsound_run_out (HWVoiceOut *hw, int live)
|
|||||||
len = live << hwshift;
|
len = live << hwshift;
|
||||||
|
|
||||||
if (ds->first_time) {
|
if (ds->first_time) {
|
||||||
if (conf->latency_millis) {
|
if (conf.latency_millis) {
|
||||||
DWORD cur_blat;
|
DWORD cur_blat;
|
||||||
|
|
||||||
cur_blat = audio_ring_dist (wpos, ppos, bufsize);
|
cur_blat = audio_ring_dist (wpos, ppos, bufsize);
|
||||||
ds->first_time = 0;
|
ds->first_time = 0;
|
||||||
old_pos = wpos;
|
old_pos = wpos;
|
||||||
old_pos +=
|
old_pos +=
|
||||||
millis_to_bytes (&hw->info, conf->latency_millis) - cur_blat;
|
millis_to_bytes (&hw->info, conf.latency_millis) - cur_blat;
|
||||||
old_pos %= bufsize;
|
old_pos %= bufsize;
|
||||||
old_pos &= ~hw->info.align;
|
old_pos &= ~hw->info.align;
|
||||||
}
|
}
|
||||||
@@ -564,8 +663,7 @@ static int dsound_run_out (HWVoiceOut *hw, int live)
|
|||||||
len,
|
len,
|
||||||
&p1, &p2,
|
&p1, &p2,
|
||||||
&blen1, &blen2,
|
&blen1, &blen2,
|
||||||
0,
|
0
|
||||||
s
|
|
||||||
);
|
);
|
||||||
if (err) {
|
if (err) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -668,7 +766,6 @@ static int dsound_run_in (HWVoiceIn *hw)
|
|||||||
DWORD cpos, rpos;
|
DWORD cpos, rpos;
|
||||||
LPVOID p1, p2;
|
LPVOID p1, p2;
|
||||||
int hwshift;
|
int hwshift;
|
||||||
dsound *s = ds->s;
|
|
||||||
|
|
||||||
if (!dscb) {
|
if (!dscb) {
|
||||||
dolog ("Attempt to run without capture buffer\n");
|
dolog ("Attempt to run without capture buffer\n");
|
||||||
@@ -723,8 +820,7 @@ static int dsound_run_in (HWVoiceIn *hw)
|
|||||||
&p2,
|
&p2,
|
||||||
&blen1,
|
&blen1,
|
||||||
&blen2,
|
&blen2,
|
||||||
0,
|
0
|
||||||
s
|
|
||||||
);
|
);
|
||||||
if (err) {
|
if (err) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -747,19 +843,12 @@ static int dsound_run_in (HWVoiceIn *hw)
|
|||||||
return decr;
|
return decr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DSoundConf glob_conf = {
|
|
||||||
.bufsize_in = 16384,
|
|
||||||
.bufsize_out = 16384,
|
|
||||||
.latency_millis = 10
|
|
||||||
};
|
|
||||||
|
|
||||||
static void dsound_audio_fini (void *opaque)
|
static void dsound_audio_fini (void *opaque)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
dsound *s = opaque;
|
dsound *s = opaque;
|
||||||
|
|
||||||
if (!s->dsound) {
|
if (!s->dsound) {
|
||||||
g_free(s);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,7 +859,6 @@ static void dsound_audio_fini (void *opaque)
|
|||||||
s->dsound = NULL;
|
s->dsound = NULL;
|
||||||
|
|
||||||
if (!s->dsound_capture) {
|
if (!s->dsound_capture) {
|
||||||
g_free(s);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,21 +867,17 @@ static void dsound_audio_fini (void *opaque)
|
|||||||
dsound_logerr (hr, "Could not release DirectSoundCapture\n");
|
dsound_logerr (hr, "Could not release DirectSoundCapture\n");
|
||||||
}
|
}
|
||||||
s->dsound_capture = NULL;
|
s->dsound_capture = NULL;
|
||||||
|
|
||||||
g_free(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *dsound_audio_init (void)
|
static void *dsound_audio_init (void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
dsound *s = g_malloc0(sizeof(dsound));
|
dsound *s = &glob_dsound;
|
||||||
|
|
||||||
s->conf = glob_conf;
|
|
||||||
hr = CoInitialize (NULL);
|
hr = CoInitialize (NULL);
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
dsound_logerr (hr, "Could not initialize COM\n");
|
dsound_logerr (hr, "Could not initialize COM\n");
|
||||||
g_free(s);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,7 +890,6 @@ static void *dsound_audio_init (void)
|
|||||||
);
|
);
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
dsound_logerr (hr, "Could not create DirectSound instance\n");
|
dsound_logerr (hr, "Could not create DirectSound instance\n");
|
||||||
g_free(s);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,7 +901,7 @@ static void *dsound_audio_init (void)
|
|||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
dsound_logerr (hr, "Could not release DirectSound\n");
|
dsound_logerr (hr, "Could not release DirectSound\n");
|
||||||
}
|
}
|
||||||
g_free(s);
|
s->dsound = NULL;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,22 +938,64 @@ static void *dsound_audio_init (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option dsound_options[] = {
|
static struct audio_option dsound_options[] = {
|
||||||
|
{
|
||||||
|
.name = "LOCK_RETRIES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.lock_retries,
|
||||||
|
.descr = "Number of times to attempt locking the buffer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "RESTOURE_RETRIES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.restore_retries,
|
||||||
|
.descr = "Number of times to attempt restoring the buffer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "GETSTATUS_RETRIES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.getstatus_retries,
|
||||||
|
.descr = "Number of times to attempt getting status of the buffer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "SET_PRIMARY",
|
||||||
|
.tag = AUD_OPT_BOOL,
|
||||||
|
.valp = &conf.set_primary,
|
||||||
|
.descr = "Set the parameters of primary buffer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.name = "LATENCY_MILLIS",
|
.name = "LATENCY_MILLIS",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.latency_millis,
|
.valp = &conf.latency_millis,
|
||||||
.descr = "(undocumented)"
|
.descr = "(undocumented)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "PRIMARY_FREQ",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.settings.freq,
|
||||||
|
.descr = "Primary buffer frequency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "PRIMARY_CHANNELS",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.settings.nchannels,
|
||||||
|
.descr = "Primary buffer number of channels (1 - mono, 2 - stereo)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "PRIMARY_FMT",
|
||||||
|
.tag = AUD_OPT_FMT,
|
||||||
|
.valp = &conf.settings.fmt,
|
||||||
|
.descr = "Primary buffer format"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.name = "BUFSIZE_OUT",
|
.name = "BUFSIZE_OUT",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.bufsize_out,
|
.valp = &conf.bufsize_out,
|
||||||
.descr = "(undocumented)"
|
.descr = "(undocumented)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "BUFSIZE_IN",
|
.name = "BUFSIZE_IN",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.bufsize_in,
|
.valp = &conf.bufsize_in,
|
||||||
.descr = "(undocumented)"
|
.descr = "(undocumented)"
|
||||||
},
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
|
|||||||
557
audio/esdaudio.c
Normal file
557
audio/esdaudio.c
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
/*
|
||||||
|
* QEMU ESD audio driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2006 Frederick Reeve (brushed up by malc)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <esd.h>
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
#define AUDIO_CAP "esd"
|
||||||
|
#include "audio_int.h"
|
||||||
|
#include "audio_pt_int.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HWVoiceOut hw;
|
||||||
|
int done;
|
||||||
|
int live;
|
||||||
|
int decr;
|
||||||
|
int rpos;
|
||||||
|
void *pcm_buf;
|
||||||
|
int fd;
|
||||||
|
struct audio_pt pt;
|
||||||
|
} ESDVoiceOut;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HWVoiceIn hw;
|
||||||
|
int done;
|
||||||
|
int dead;
|
||||||
|
int incr;
|
||||||
|
int wpos;
|
||||||
|
void *pcm_buf;
|
||||||
|
int fd;
|
||||||
|
struct audio_pt pt;
|
||||||
|
} ESDVoiceIn;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int samples;
|
||||||
|
int divisor;
|
||||||
|
char *dac_host;
|
||||||
|
char *adc_host;
|
||||||
|
} conf = {
|
||||||
|
.samples = 1024,
|
||||||
|
.divisor = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void GCC_FMT_ATTR (2, 3) qesd_logerr (int err, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* playback */
|
||||||
|
static void *qesd_thread_out (void *arg)
|
||||||
|
{
|
||||||
|
ESDVoiceOut *esd = arg;
|
||||||
|
HWVoiceOut *hw = &esd->hw;
|
||||||
|
int threshold;
|
||||||
|
|
||||||
|
threshold = conf.divisor ? hw->samples / conf.divisor : 0;
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int decr, to_mix, rpos;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (esd->done) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esd->live > threshold) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decr = to_mix = esd->live;
|
||||||
|
rpos = hw->rpos;
|
||||||
|
|
||||||
|
if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (to_mix) {
|
||||||
|
ssize_t written;
|
||||||
|
int chunk = audio_MIN (to_mix, hw->samples - rpos);
|
||||||
|
struct st_sample *src = hw->mix_buf + rpos;
|
||||||
|
|
||||||
|
hw->clip (esd->pcm_buf, src, chunk);
|
||||||
|
|
||||||
|
again:
|
||||||
|
written = write (esd->fd, esd->pcm_buf, chunk << hw->info.shift);
|
||||||
|
if (written == -1) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
qesd_logerr (errno, "write failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (written != chunk << hw->info.shift) {
|
||||||
|
int wsamples = written >> hw->info.shift;
|
||||||
|
int wbytes = wsamples << hw->info.shift;
|
||||||
|
if (wbytes != written) {
|
||||||
|
dolog ("warning: Misaligned write %d (requested %zd), "
|
||||||
|
"alignment %d\n",
|
||||||
|
wbytes, written, hw->info.align + 1);
|
||||||
|
}
|
||||||
|
to_mix -= wsamples;
|
||||||
|
rpos = (rpos + wsamples) % hw->samples;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpos = (rpos + chunk) % hw->samples;
|
||||||
|
to_mix -= chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
esd->rpos = rpos;
|
||||||
|
esd->live -= decr;
|
||||||
|
esd->decr += decr;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
audio_pt_unlock (&esd->pt, AUDIO_FUNC);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_run_out (HWVoiceOut *hw, int live)
|
||||||
|
{
|
||||||
|
int decr;
|
||||||
|
ESDVoiceOut *esd = (ESDVoiceOut *) hw;
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
decr = audio_MIN (live, esd->decr);
|
||||||
|
esd->decr -= decr;
|
||||||
|
esd->live = live - decr;
|
||||||
|
hw->rpos = esd->rpos;
|
||||||
|
if (esd->live > 0) {
|
||||||
|
audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
audio_pt_unlock (&esd->pt, AUDIO_FUNC);
|
||||||
|
}
|
||||||
|
return decr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_write (SWVoiceOut *sw, void *buf, int len)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_write (sw, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
ESDVoiceOut *esd = (ESDVoiceOut *) hw;
|
||||||
|
struct audsettings obt_as = *as;
|
||||||
|
int esdfmt = ESD_STREAM | ESD_PLAY;
|
||||||
|
|
||||||
|
esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO;
|
||||||
|
switch (as->fmt) {
|
||||||
|
case AUD_FMT_S8:
|
||||||
|
case AUD_FMT_U8:
|
||||||
|
esdfmt |= ESD_BITS8;
|
||||||
|
obt_as.fmt = AUD_FMT_U8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_S32:
|
||||||
|
case AUD_FMT_U32:
|
||||||
|
dolog ("Will use 16 instead of 32 bit samples\n");
|
||||||
|
/* fall through */
|
||||||
|
case AUD_FMT_S16:
|
||||||
|
case AUD_FMT_U16:
|
||||||
|
deffmt:
|
||||||
|
esdfmt |= ESD_BITS16;
|
||||||
|
obt_as.fmt = AUD_FMT_S16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dolog ("Internal logic error: Bad audio format %d\n", as->fmt);
|
||||||
|
goto deffmt;
|
||||||
|
|
||||||
|
}
|
||||||
|
obt_as.endianness = AUDIO_HOST_ENDIANNESS;
|
||||||
|
|
||||||
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
|
|
||||||
|
hw->samples = conf.samples;
|
||||||
|
esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
||||||
|
if (!esd->pcm_buf) {
|
||||||
|
dolog ("Could not allocate buffer (%d bytes)\n",
|
||||||
|
hw->samples << hw->info.shift);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
esd->fd = esd_play_stream (esdfmt, as->freq, conf.dac_host, NULL);
|
||||||
|
if (esd->fd < 0) {
|
||||||
|
qesd_logerr (errno, "esd_play_stream failed\n");
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_init (&esd->pt, qesd_thread_out, esd, AUDIO_CAP, AUDIO_FUNC)) {
|
||||||
|
goto fail2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail2:
|
||||||
|
if (close (esd->fd)) {
|
||||||
|
qesd_logerr (errno, "%s: close on esd socket(%d) failed\n",
|
||||||
|
AUDIO_FUNC, esd->fd);
|
||||||
|
}
|
||||||
|
esd->fd = -1;
|
||||||
|
|
||||||
|
fail1:
|
||||||
|
g_free (esd->pcm_buf);
|
||||||
|
esd->pcm_buf = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qesd_fini_out (HWVoiceOut *hw)
|
||||||
|
{
|
||||||
|
void *ret;
|
||||||
|
ESDVoiceOut *esd = (ESDVoiceOut *) hw;
|
||||||
|
|
||||||
|
audio_pt_lock (&esd->pt, AUDIO_FUNC);
|
||||||
|
esd->done = 1;
|
||||||
|
audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC);
|
||||||
|
audio_pt_join (&esd->pt, &ret, AUDIO_FUNC);
|
||||||
|
|
||||||
|
if (esd->fd >= 0) {
|
||||||
|
if (close (esd->fd)) {
|
||||||
|
qesd_logerr (errno, "failed to close esd socket\n");
|
||||||
|
}
|
||||||
|
esd->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_pt_fini (&esd->pt, AUDIO_FUNC);
|
||||||
|
|
||||||
|
g_free (esd->pcm_buf);
|
||||||
|
esd->pcm_buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
(void) hw;
|
||||||
|
(void) cmd;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* capture */
|
||||||
|
static void *qesd_thread_in (void *arg)
|
||||||
|
{
|
||||||
|
ESDVoiceIn *esd = arg;
|
||||||
|
HWVoiceIn *hw = &esd->hw;
|
||||||
|
int threshold;
|
||||||
|
|
||||||
|
threshold = conf.divisor ? hw->samples / conf.divisor : 0;
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int incr, to_grab, wpos;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (esd->done) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esd->dead > threshold) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
incr = to_grab = esd->dead;
|
||||||
|
wpos = hw->wpos;
|
||||||
|
|
||||||
|
if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (to_grab) {
|
||||||
|
ssize_t nread;
|
||||||
|
int chunk = audio_MIN (to_grab, hw->samples - wpos);
|
||||||
|
void *buf = advance (esd->pcm_buf, wpos);
|
||||||
|
|
||||||
|
again:
|
||||||
|
nread = read (esd->fd, buf, chunk << hw->info.shift);
|
||||||
|
if (nread == -1) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
qesd_logerr (errno, "read failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread != chunk << hw->info.shift) {
|
||||||
|
int rsamples = nread >> hw->info.shift;
|
||||||
|
int rbytes = rsamples << hw->info.shift;
|
||||||
|
if (rbytes != nread) {
|
||||||
|
dolog ("warning: Misaligned write %d (requested %zd), "
|
||||||
|
"alignment %d\n",
|
||||||
|
rbytes, nread, hw->info.align + 1);
|
||||||
|
}
|
||||||
|
to_grab -= rsamples;
|
||||||
|
wpos = (wpos + rsamples) % hw->samples;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw->conv (hw->conv_buf + wpos, buf, nread >> hw->info.shift);
|
||||||
|
wpos = (wpos + chunk) % hw->samples;
|
||||||
|
to_grab -= chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
esd->wpos = wpos;
|
||||||
|
esd->dead -= incr;
|
||||||
|
esd->incr += incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
exit:
|
||||||
|
audio_pt_unlock (&esd->pt, AUDIO_FUNC);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_run_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
int live, incr, dead;
|
||||||
|
ESDVoiceIn *esd = (ESDVoiceIn *) hw;
|
||||||
|
|
||||||
|
if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
live = audio_pcm_hw_get_live_in (hw);
|
||||||
|
dead = hw->samples - live;
|
||||||
|
incr = audio_MIN (dead, esd->incr);
|
||||||
|
esd->incr -= incr;
|
||||||
|
esd->dead = dead - incr;
|
||||||
|
hw->wpos = esd->wpos;
|
||||||
|
if (esd->dead > 0) {
|
||||||
|
audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
audio_pt_unlock (&esd->pt, AUDIO_FUNC);
|
||||||
|
}
|
||||||
|
return incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_read (SWVoiceIn *sw, void *buf, int len)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_read (sw, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
ESDVoiceIn *esd = (ESDVoiceIn *) hw;
|
||||||
|
struct audsettings obt_as = *as;
|
||||||
|
int esdfmt = ESD_STREAM | ESD_RECORD;
|
||||||
|
|
||||||
|
esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO;
|
||||||
|
switch (as->fmt) {
|
||||||
|
case AUD_FMT_S8:
|
||||||
|
case AUD_FMT_U8:
|
||||||
|
esdfmt |= ESD_BITS8;
|
||||||
|
obt_as.fmt = AUD_FMT_U8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_S16:
|
||||||
|
case AUD_FMT_U16:
|
||||||
|
esdfmt |= ESD_BITS16;
|
||||||
|
obt_as.fmt = AUD_FMT_S16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_S32:
|
||||||
|
case AUD_FMT_U32:
|
||||||
|
dolog ("Will use 16 instead of 32 bit samples\n");
|
||||||
|
esdfmt |= ESD_BITS16;
|
||||||
|
obt_as.fmt = AUD_FMT_S16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obt_as.endianness = AUDIO_HOST_ENDIANNESS;
|
||||||
|
|
||||||
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
|
|
||||||
|
hw->samples = conf.samples;
|
||||||
|
esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
||||||
|
if (!esd->pcm_buf) {
|
||||||
|
dolog ("Could not allocate buffer (%d bytes)\n",
|
||||||
|
hw->samples << hw->info.shift);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
esd->fd = esd_record_stream (esdfmt, as->freq, conf.adc_host, NULL);
|
||||||
|
if (esd->fd < 0) {
|
||||||
|
qesd_logerr (errno, "esd_record_stream failed\n");
|
||||||
|
goto fail1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_pt_init (&esd->pt, qesd_thread_in, esd, AUDIO_CAP, AUDIO_FUNC)) {
|
||||||
|
goto fail2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail2:
|
||||||
|
if (close (esd->fd)) {
|
||||||
|
qesd_logerr (errno, "%s: close on esd socket(%d) failed\n",
|
||||||
|
AUDIO_FUNC, esd->fd);
|
||||||
|
}
|
||||||
|
esd->fd = -1;
|
||||||
|
|
||||||
|
fail1:
|
||||||
|
g_free (esd->pcm_buf);
|
||||||
|
esd->pcm_buf = NULL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qesd_fini_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
void *ret;
|
||||||
|
ESDVoiceIn *esd = (ESDVoiceIn *) hw;
|
||||||
|
|
||||||
|
audio_pt_lock (&esd->pt, AUDIO_FUNC);
|
||||||
|
esd->done = 1;
|
||||||
|
audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC);
|
||||||
|
audio_pt_join (&esd->pt, &ret, AUDIO_FUNC);
|
||||||
|
|
||||||
|
if (esd->fd >= 0) {
|
||||||
|
if (close (esd->fd)) {
|
||||||
|
qesd_logerr (errno, "failed to close esd socket\n");
|
||||||
|
}
|
||||||
|
esd->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_pt_fini (&esd->pt, AUDIO_FUNC);
|
||||||
|
|
||||||
|
g_free (esd->pcm_buf);
|
||||||
|
esd->pcm_buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int qesd_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
(void) hw;
|
||||||
|
(void) cmd;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* common */
|
||||||
|
static void *qesd_audio_init (void)
|
||||||
|
{
|
||||||
|
return &conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qesd_audio_fini (void *opaque)
|
||||||
|
{
|
||||||
|
(void) opaque;
|
||||||
|
ldebug ("esd_fini");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct audio_option qesd_options[] = {
|
||||||
|
{
|
||||||
|
.name = "SAMPLES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.samples,
|
||||||
|
.descr = "buffer size in samples"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "DIVISOR",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.divisor,
|
||||||
|
.descr = "threshold divisor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "DAC_HOST",
|
||||||
|
.tag = AUD_OPT_STR,
|
||||||
|
.valp = &conf.dac_host,
|
||||||
|
.descr = "playback host"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "ADC_HOST",
|
||||||
|
.tag = AUD_OPT_STR,
|
||||||
|
.valp = &conf.adc_host,
|
||||||
|
.descr = "capture host"
|
||||||
|
},
|
||||||
|
{ /* End of list */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct audio_pcm_ops qesd_pcm_ops = {
|
||||||
|
.init_out = qesd_init_out,
|
||||||
|
.fini_out = qesd_fini_out,
|
||||||
|
.run_out = qesd_run_out,
|
||||||
|
.write = qesd_write,
|
||||||
|
.ctl_out = qesd_ctl_out,
|
||||||
|
|
||||||
|
.init_in = qesd_init_in,
|
||||||
|
.fini_in = qesd_fini_in,
|
||||||
|
.run_in = qesd_run_in,
|
||||||
|
.read = qesd_read,
|
||||||
|
.ctl_in = qesd_ctl_in,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct audio_driver esd_audio_driver = {
|
||||||
|
.name = "esd",
|
||||||
|
.descr = "http://en.wikipedia.org/wiki/Esound",
|
||||||
|
.options = qesd_options,
|
||||||
|
.init = qesd_audio_init,
|
||||||
|
.fini = qesd_audio_fini,
|
||||||
|
.pcm_ops = &qesd_pcm_ops,
|
||||||
|
.can_be_default = 0,
|
||||||
|
.max_voices_out = INT_MAX,
|
||||||
|
.max_voices_in = INT_MAX,
|
||||||
|
.voice_size_out = sizeof (ESDVoiceOut),
|
||||||
|
.voice_size_in = sizeof (ESDVoiceIn)
|
||||||
|
};
|
||||||
685
audio/fmodaudio.c
Normal file
685
audio/fmodaudio.c
Normal file
@@ -0,0 +1,685 @@
|
|||||||
|
/*
|
||||||
|
* QEMU FMOD audio driver
|
||||||
|
*
|
||||||
|
* Copyright (c) 2004-2005 Vassili Karpov (malc)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <fmod.h>
|
||||||
|
#include <fmod_errors.h>
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
#define AUDIO_CAP "fmod"
|
||||||
|
#include "audio_int.h"
|
||||||
|
|
||||||
|
typedef struct FMODVoiceOut {
|
||||||
|
HWVoiceOut hw;
|
||||||
|
unsigned int old_pos;
|
||||||
|
FSOUND_SAMPLE *fmod_sample;
|
||||||
|
int channel;
|
||||||
|
} FMODVoiceOut;
|
||||||
|
|
||||||
|
typedef struct FMODVoiceIn {
|
||||||
|
HWVoiceIn hw;
|
||||||
|
FSOUND_SAMPLE *fmod_sample;
|
||||||
|
} FMODVoiceIn;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const char *drvname;
|
||||||
|
int nb_samples;
|
||||||
|
int freq;
|
||||||
|
int nb_channels;
|
||||||
|
int bufsize;
|
||||||
|
int broken_adc;
|
||||||
|
} conf = {
|
||||||
|
.nb_samples = 2048 * 2,
|
||||||
|
.freq = 44100,
|
||||||
|
.nb_channels = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
AUD_log (AUDIO_CAP, "Reason: %s\n",
|
||||||
|
FMOD_ErrorString (FSOUND_GetError ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GCC_FMT_ATTR (2, 3) fmod_logerr2 (
|
||||||
|
const char *typ,
|
||||||
|
const char *fmt,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
AUD_log (AUDIO_CAP, "Reason: %s\n",
|
||||||
|
FMOD_ErrorString (FSOUND_GetError ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_write (SWVoiceOut *sw, void *buf, int len)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_write (sw, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmod_clear_sample (FMODVoiceOut *fmd)
|
||||||
|
{
|
||||||
|
HWVoiceOut *hw = &fmd->hw;
|
||||||
|
int status;
|
||||||
|
void *p1 = 0, *p2 = 0;
|
||||||
|
unsigned int len1 = 0, len2 = 0;
|
||||||
|
|
||||||
|
status = FSOUND_Sample_Lock (
|
||||||
|
fmd->fmod_sample,
|
||||||
|
0,
|
||||||
|
hw->samples << hw->info.shift,
|
||||||
|
&p1,
|
||||||
|
&p2,
|
||||||
|
&len1,
|
||||||
|
&len2
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to lock sample\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((len1 & hw->info.align) || (len2 & hw->info.align)) {
|
||||||
|
dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
|
||||||
|
len1, len2, hw->info.align + 1);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((len1 + len2) - (hw->samples << hw->info.shift)) {
|
||||||
|
dolog ("Lock returned incomplete length %d, %d\n",
|
||||||
|
len1 + len2, hw->samples << hw->info.shift);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_pcm_info_clear_buf (&hw->info, p1, hw->samples);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to unlock sample\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len)
|
||||||
|
{
|
||||||
|
int src_len1 = dst_len;
|
||||||
|
int src_len2 = 0;
|
||||||
|
int pos = hw->rpos + dst_len;
|
||||||
|
struct st_sample *src1 = hw->mix_buf + hw->rpos;
|
||||||
|
struct st_sample *src2 = NULL;
|
||||||
|
|
||||||
|
if (pos > hw->samples) {
|
||||||
|
src_len1 = hw->samples - hw->rpos;
|
||||||
|
src2 = hw->mix_buf;
|
||||||
|
src_len2 = dst_len - src_len1;
|
||||||
|
pos = src_len2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src_len1) {
|
||||||
|
hw->clip (dst, src1, src_len1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src_len2) {
|
||||||
|
dst = advance (dst, src_len1 << hw->info.shift);
|
||||||
|
hw->clip (dst, src2, src_len2);
|
||||||
|
}
|
||||||
|
|
||||||
|
hw->rpos = pos % hw->samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2,
|
||||||
|
unsigned int blen1, unsigned int blen2)
|
||||||
|
{
|
||||||
|
int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to unlock sample\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_lock_sample (
|
||||||
|
FSOUND_SAMPLE *sample,
|
||||||
|
struct audio_pcm_info *info,
|
||||||
|
int pos,
|
||||||
|
int len,
|
||||||
|
void **p1,
|
||||||
|
void **p2,
|
||||||
|
unsigned int *blen1,
|
||||||
|
unsigned int *blen2
|
||||||
|
)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = FSOUND_Sample_Lock (
|
||||||
|
sample,
|
||||||
|
pos << info->shift,
|
||||||
|
len << info->shift,
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
blen1,
|
||||||
|
blen2
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to lock sample\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*blen1 & info->align) || (*blen2 & info->align)) {
|
||||||
|
dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
|
||||||
|
*blen1, *blen2, info->align + 1);
|
||||||
|
|
||||||
|
fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2);
|
||||||
|
|
||||||
|
*p1 = NULL - 1;
|
||||||
|
*p2 = NULL - 1;
|
||||||
|
*blen1 = ~0U;
|
||||||
|
*blen2 = ~0U;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!*p1 && *blen1) {
|
||||||
|
dolog ("warning: !p1 && blen1=%d\n", *blen1);
|
||||||
|
*blen1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!p2 && *blen2) {
|
||||||
|
dolog ("warning: !p2 && blen2=%d\n", *blen2);
|
||||||
|
*blen2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_run_out (HWVoiceOut *hw, int live)
|
||||||
|
{
|
||||||
|
FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
|
||||||
|
int decr;
|
||||||
|
void *p1 = 0, *p2 = 0;
|
||||||
|
unsigned int blen1 = 0, blen2 = 0;
|
||||||
|
unsigned int len1 = 0, len2 = 0;
|
||||||
|
|
||||||
|
if (!hw->pending_disable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
decr = live;
|
||||||
|
|
||||||
|
if (fmd->channel >= 0) {
|
||||||
|
int len = decr;
|
||||||
|
int old_pos = fmd->old_pos;
|
||||||
|
int ppos = FSOUND_GetCurrentPosition (fmd->channel);
|
||||||
|
|
||||||
|
if (ppos == old_pos || !ppos) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((old_pos < ppos) && ((old_pos + len) > ppos)) {
|
||||||
|
len = ppos - old_pos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) {
|
||||||
|
len = hw->samples - old_pos + ppos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decr = len;
|
||||||
|
|
||||||
|
if (audio_bug (AUDIO_FUNC, decr < 0)) {
|
||||||
|
dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n",
|
||||||
|
decr, live, ppos, old_pos, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!decr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
|
||||||
|
fmd->old_pos, decr,
|
||||||
|
&p1, &p2,
|
||||||
|
&blen1, &blen2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
len1 = blen1 >> hw->info.shift;
|
||||||
|
len2 = blen2 >> hw->info.shift;
|
||||||
|
ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2);
|
||||||
|
decr = len1 + len2;
|
||||||
|
|
||||||
|
if (p1 && len1) {
|
||||||
|
fmod_write_sample (hw, p1, len1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p2 && len2) {
|
||||||
|
fmod_write_sample (hw, p2, len2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
|
||||||
|
|
||||||
|
fmd->old_pos = (fmd->old_pos + decr) % hw->samples;
|
||||||
|
return decr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aud_to_fmodfmt (audfmt_e fmt, int stereo)
|
||||||
|
{
|
||||||
|
int mode = FSOUND_LOOP_NORMAL;
|
||||||
|
|
||||||
|
switch (fmt) {
|
||||||
|
case AUD_FMT_S8:
|
||||||
|
mode |= FSOUND_SIGNED | FSOUND_8BITS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_U8:
|
||||||
|
mode |= FSOUND_UNSIGNED | FSOUND_8BITS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_S16:
|
||||||
|
mode |= FSOUND_SIGNED | FSOUND_16BITS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUD_FMT_U16:
|
||||||
|
mode |= FSOUND_UNSIGNED | FSOUND_16BITS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dolog ("Internal logic error: Bad audio format %d\n", fmt);
|
||||||
|
#ifdef DEBUG_FMOD
|
||||||
|
abort ();
|
||||||
|
#endif
|
||||||
|
mode |= FSOUND_8BITS;
|
||||||
|
}
|
||||||
|
mode |= stereo ? FSOUND_STEREO : FSOUND_MONO;
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmod_fini_out (HWVoiceOut *hw)
|
||||||
|
{
|
||||||
|
FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
|
||||||
|
|
||||||
|
if (fmd->fmod_sample) {
|
||||||
|
FSOUND_Sample_Free (fmd->fmod_sample);
|
||||||
|
fmd->fmod_sample = 0;
|
||||||
|
|
||||||
|
if (fmd->channel >= 0) {
|
||||||
|
FSOUND_StopSound (fmd->channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
int mode, channel;
|
||||||
|
FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
|
||||||
|
struct audsettings obt_as = *as;
|
||||||
|
|
||||||
|
mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
|
||||||
|
fmd->fmod_sample = FSOUND_Sample_Alloc (
|
||||||
|
FSOUND_FREE, /* index */
|
||||||
|
conf.nb_samples, /* length */
|
||||||
|
mode, /* mode */
|
||||||
|
as->freq, /* freq */
|
||||||
|
255, /* volume */
|
||||||
|
128, /* pan */
|
||||||
|
255 /* priority */
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fmd->fmod_sample) {
|
||||||
|
fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1);
|
||||||
|
if (channel < 0) {
|
||||||
|
fmod_logerr2 ("DAC", "Failed to start playing sound\n");
|
||||||
|
FSOUND_Sample_Free (fmd->fmod_sample);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fmd->channel = channel;
|
||||||
|
|
||||||
|
/* FMOD always operates on little endian frames? */
|
||||||
|
obt_as.endianness = 0;
|
||||||
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
|
hw->samples = conf.nb_samples;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case VOICE_ENABLE:
|
||||||
|
fmod_clear_sample (fmd);
|
||||||
|
status = FSOUND_SetPaused (fmd->channel, 0);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to resume channel %d\n", fmd->channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VOICE_DISABLE:
|
||||||
|
status = FSOUND_SetPaused (fmd->channel, 1);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to pause channel %d\n", fmd->channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
int mode;
|
||||||
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
|
||||||
|
struct audsettings obt_as = *as;
|
||||||
|
|
||||||
|
if (conf.broken_adc) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
|
||||||
|
fmd->fmod_sample = FSOUND_Sample_Alloc (
|
||||||
|
FSOUND_FREE, /* index */
|
||||||
|
conf.nb_samples, /* length */
|
||||||
|
mode, /* mode */
|
||||||
|
as->freq, /* freq */
|
||||||
|
255, /* volume */
|
||||||
|
128, /* pan */
|
||||||
|
255 /* priority */
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fmd->fmod_sample) {
|
||||||
|
fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FMOD always operates on little endian frames? */
|
||||||
|
obt_as.endianness = 0;
|
||||||
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
|
hw->samples = conf.nb_samples;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmod_fini_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
|
||||||
|
|
||||||
|
if (fmd->fmod_sample) {
|
||||||
|
FSOUND_Record_Stop ();
|
||||||
|
FSOUND_Sample_Free (fmd->fmod_sample);
|
||||||
|
fmd->fmod_sample = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_run_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
|
||||||
|
int hwshift = hw->info.shift;
|
||||||
|
int live, dead, new_pos, len;
|
||||||
|
unsigned int blen1 = 0, blen2 = 0;
|
||||||
|
unsigned int len1, len2;
|
||||||
|
unsigned int decr;
|
||||||
|
void *p1, *p2;
|
||||||
|
|
||||||
|
live = audio_pcm_hw_get_live_in (hw);
|
||||||
|
dead = hw->samples - live;
|
||||||
|
if (!dead) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_pos = FSOUND_Record_GetPosition ();
|
||||||
|
if (new_pos < 0) {
|
||||||
|
fmod_logerr ("Could not get recording position\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = audio_ring_dist (new_pos, hw->wpos, hw->samples);
|
||||||
|
if (!len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
len = audio_MIN (len, dead);
|
||||||
|
|
||||||
|
if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
|
||||||
|
hw->wpos, len,
|
||||||
|
&p1, &p2,
|
||||||
|
&blen1, &blen2)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
len1 = blen1 >> hwshift;
|
||||||
|
len2 = blen2 >> hwshift;
|
||||||
|
decr = len1 + len2;
|
||||||
|
|
||||||
|
if (p1 && blen1) {
|
||||||
|
hw->conv (hw->conv_buf + hw->wpos, p1, len1);
|
||||||
|
}
|
||||||
|
if (p2 && len2) {
|
||||||
|
hw->conv (hw->conv_buf, p2, len2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
|
||||||
|
hw->wpos = (hw->wpos + decr) % hw->samples;
|
||||||
|
return decr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const char *name;
|
||||||
|
int type;
|
||||||
|
} drvtab[] = {
|
||||||
|
{ .name = "none", .type = FSOUND_OUTPUT_NOSOUND },
|
||||||
|
#ifdef _WIN32
|
||||||
|
{ .name = "winmm", .type = FSOUND_OUTPUT_WINMM },
|
||||||
|
{ .name = "dsound", .type = FSOUND_OUTPUT_DSOUND },
|
||||||
|
{ .name = "a3d", .type = FSOUND_OUTPUT_A3D },
|
||||||
|
{ .name = "asio", .type = FSOUND_OUTPUT_ASIO },
|
||||||
|
#endif
|
||||||
|
#ifdef __linux__
|
||||||
|
{ .name = "oss", .type = FSOUND_OUTPUT_OSS },
|
||||||
|
{ .name = "alsa", .type = FSOUND_OUTPUT_ALSA },
|
||||||
|
{ .name = "esd", .type = FSOUND_OUTPUT_ESD },
|
||||||
|
#endif
|
||||||
|
#ifdef __APPLE__
|
||||||
|
{ .name = "mac", .type = FSOUND_OUTPUT_MAC },
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
{ .name = "xbox", .type = FSOUND_OUTPUT_XBOX },
|
||||||
|
{ .name = "ps2", .type = FSOUND_OUTPUT_PS2 },
|
||||||
|
{ .name = "gcube", .type = FSOUND_OUTPUT_GC },
|
||||||
|
#endif
|
||||||
|
{ .name = "none-realtime", .type = FSOUND_OUTPUT_NOSOUND_NONREALTIME }
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *fmod_audio_init (void)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
double ver;
|
||||||
|
int status;
|
||||||
|
int output_type = -1;
|
||||||
|
const char *drv = conf.drvname;
|
||||||
|
|
||||||
|
ver = FSOUND_GetVersion ();
|
||||||
|
if (ver < FMOD_VERSION) {
|
||||||
|
dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
if (ver < 3.75) {
|
||||||
|
dolog ("FMOD before 3.75 has bug preventing ADC from working\n"
|
||||||
|
"ADC will be disabled.\n");
|
||||||
|
conf.broken_adc = 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (drv) {
|
||||||
|
int found = 0;
|
||||||
|
for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
|
||||||
|
if (!strcmp (drv, drvtab[i].name)) {
|
||||||
|
output_type = drvtab[i].type;
|
||||||
|
found = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
dolog ("Unknown FMOD driver `%s'\n", drv);
|
||||||
|
dolog ("Valid drivers:\n");
|
||||||
|
for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
|
||||||
|
dolog (" %s\n", drvtab[i].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_type != -1) {
|
||||||
|
status = FSOUND_SetOutput (output_type);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf.bufsize) {
|
||||||
|
status = FSOUND_SetBufferSize (conf.bufsize);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = FSOUND_Init (conf.freq, conf.nb_channels, 0);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("FSOUND_Init failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_read (SWVoiceIn *sw, void *buf, int size)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_read (sw, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case VOICE_ENABLE:
|
||||||
|
status = FSOUND_Record_StartSample (fmd->fmod_sample, 1);
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to start recording\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case VOICE_DISABLE:
|
||||||
|
status = FSOUND_Record_Stop ();
|
||||||
|
if (!status) {
|
||||||
|
fmod_logerr ("Failed to stop recording\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmod_audio_fini (void *opaque)
|
||||||
|
{
|
||||||
|
(void) opaque;
|
||||||
|
FSOUND_Close ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct audio_option fmod_options[] = {
|
||||||
|
{
|
||||||
|
.name = "DRV",
|
||||||
|
.tag = AUD_OPT_STR,
|
||||||
|
.valp = &conf.drvname,
|
||||||
|
.descr = "FMOD driver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "FREQ",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.freq,
|
||||||
|
.descr = "Default frequency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "SAMPLES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.nb_samples,
|
||||||
|
.descr = "Buffer size in samples"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "CHANNELS",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.nb_channels,
|
||||||
|
.descr = "Number of default channels (1 - mono, 2 - stereo)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "BUFSIZE",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.bufsize,
|
||||||
|
.descr = "(undocumented)"
|
||||||
|
},
|
||||||
|
{ /* End of list */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct audio_pcm_ops fmod_pcm_ops = {
|
||||||
|
.init_out = fmod_init_out,
|
||||||
|
.fini_out = fmod_fini_out,
|
||||||
|
.run_out = fmod_run_out,
|
||||||
|
.write = fmod_write,
|
||||||
|
.ctl_out = fmod_ctl_out,
|
||||||
|
|
||||||
|
.init_in = fmod_init_in,
|
||||||
|
.fini_in = fmod_fini_in,
|
||||||
|
.run_in = fmod_run_in,
|
||||||
|
.read = fmod_read,
|
||||||
|
.ctl_in = fmod_ctl_in
|
||||||
|
};
|
||||||
|
|
||||||
|
struct audio_driver fmod_audio_driver = {
|
||||||
|
.name = "fmod",
|
||||||
|
.descr = "FMOD 3.xx http://www.fmod.org",
|
||||||
|
.options = fmod_options,
|
||||||
|
.init = fmod_audio_init,
|
||||||
|
.fini = fmod_audio_fini,
|
||||||
|
.pcm_ops = &fmod_pcm_ops,
|
||||||
|
.can_be_default = 1,
|
||||||
|
.max_voices_out = INT_MAX,
|
||||||
|
.max_voices_in = INT_MAX,
|
||||||
|
.voice_size_out = sizeof (FMODVoiceOut),
|
||||||
|
.voice_size_in = sizeof (FMODVoiceIn)
|
||||||
|
};
|
||||||
@@ -22,10 +22,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/bswap.h"
|
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
#define AUDIO_CAP "mixeng"
|
#define AUDIO_CAP "mixeng"
|
||||||
@@ -268,42 +265,11 @@ f_sample *mixeng_clip[2][2][2][3] = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void audio_sample_to_uint64(void *samples, int pos,
|
|
||||||
uint64_t *left, uint64_t *right)
|
|
||||||
{
|
|
||||||
struct st_sample *sample = samples;
|
|
||||||
sample += pos;
|
|
||||||
#ifdef FLOAT_MIXENG
|
|
||||||
error_report(
|
|
||||||
"Coreaudio and floating point samples are not supported by replay yet");
|
|
||||||
abort();
|
|
||||||
#else
|
|
||||||
*left = sample->l;
|
|
||||||
*right = sample->r;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_sample_from_uint64(void *samples, int pos,
|
|
||||||
uint64_t left, uint64_t right)
|
|
||||||
{
|
|
||||||
struct st_sample *sample = samples;
|
|
||||||
sample += pos;
|
|
||||||
#ifdef FLOAT_MIXENG
|
|
||||||
error_report(
|
|
||||||
"Coreaudio and floating point samples are not supported by replay yet");
|
|
||||||
abort();
|
|
||||||
#else
|
|
||||||
sample->l = left;
|
|
||||||
sample->r = right;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* August 21, 1998
|
* August 21, 1998
|
||||||
* Copyright 1998 Fabrice Bellard.
|
* Copyright 1998 Fabrice Bellard.
|
||||||
*
|
*
|
||||||
* [Rewrote completely the code of Lance Norskog And Sundry
|
* [Rewrote completly the code of Lance Norskog And Sundry
|
||||||
* Contributors with a more efficient algorithm.]
|
* Contributors with a more efficient algorithm.]
|
||||||
*
|
*
|
||||||
* This source code is freely redistributable and may be used for
|
* This source code is freely redistributable and may be used for
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef QEMU_MIXENG_H
|
#ifndef QEMU_MIXENG_H
|
||||||
#define QEMU_MIXENG_H
|
#define QEMU_MIXENG_H
|
||||||
|
|
||||||
@@ -49,4 +48,4 @@ void st_rate_stop (void *opaque);
|
|||||||
void mixeng_clear (struct st_sample *buf, int len);
|
void mixeng_clear (struct st_sample *buf, int len);
|
||||||
void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol);
|
void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol);
|
||||||
|
|
||||||
#endif /* QEMU_MIXENG_H */
|
#endif /* mixeng.h */
|
||||||
|
|||||||
@@ -21,9 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/host-utils.h"
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
|
|
||||||
@@ -50,8 +48,8 @@ static int no_run_out (HWVoiceOut *hw, int live)
|
|||||||
|
|
||||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
ticks = now - no->old_ticks;
|
ticks = now - no->old_ticks;
|
||||||
bytes = muldiv64(ticks, hw->info.bytes_per_second, NANOSECONDS_PER_SECOND);
|
bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
bytes = audio_MIN(bytes, INT_MAX);
|
bytes = audio_MIN (bytes, INT_MAX);
|
||||||
samples = bytes >> hw->info.shift;
|
samples = bytes >> hw->info.shift;
|
||||||
|
|
||||||
no->old_ticks = now;
|
no->old_ticks = now;
|
||||||
@@ -62,10 +60,10 @@ static int no_run_out (HWVoiceOut *hw, int live)
|
|||||||
|
|
||||||
static int no_write (SWVoiceOut *sw, void *buf, int len)
|
static int no_write (SWVoiceOut *sw, void *buf, int len)
|
||||||
{
|
{
|
||||||
return audio_pcm_sw_write(sw, buf, len);
|
return audio_pcm_sw_write (sw, buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int no_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque)
|
static int no_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
audio_pcm_init_info (&hw->info, as);
|
audio_pcm_init_info (&hw->info, as);
|
||||||
hw->samples = 1024;
|
hw->samples = 1024;
|
||||||
@@ -84,7 +82,7 @@ static int no_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int no_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
static int no_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
audio_pcm_init_info (&hw->info, as);
|
audio_pcm_init_info (&hw->info, as);
|
||||||
hw->samples = 1024;
|
hw->samples = 1024;
|
||||||
@@ -107,7 +105,7 @@ static int no_run_in (HWVoiceIn *hw)
|
|||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
int64_t ticks = now - no->old_ticks;
|
int64_t ticks = now - no->old_ticks;
|
||||||
int64_t bytes =
|
int64_t bytes =
|
||||||
muldiv64(ticks, hw->info.bytes_per_second, NANOSECONDS_PER_SECOND);
|
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
|
|
||||||
no->old_ticks = now;
|
no->old_ticks = now;
|
||||||
bytes = audio_MIN (bytes, INT_MAX);
|
bytes = audio_MIN (bytes, INT_MAX);
|
||||||
|
|||||||
147
audio/ossaudio.c
147
audio/ossaudio.c
@@ -21,14 +21,15 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/soundcard.h>
|
#include <sys/soundcard.h>
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
#include "qemu/host-utils.h"
|
#include "qemu/host-utils.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "trace.h"
|
|
||||||
|
|
||||||
#define AUDIO_CAP "oss"
|
#define AUDIO_CAP "oss"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
@@ -37,16 +38,6 @@
|
|||||||
#define USE_DSP_POLICY
|
#define USE_DSP_POLICY
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct OSSConf {
|
|
||||||
int try_mmap;
|
|
||||||
int nfrags;
|
|
||||||
int fragsize;
|
|
||||||
const char *devpath_out;
|
|
||||||
const char *devpath_in;
|
|
||||||
int exclusive;
|
|
||||||
int policy;
|
|
||||||
} OSSConf;
|
|
||||||
|
|
||||||
typedef struct OSSVoiceOut {
|
typedef struct OSSVoiceOut {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
void *pcm_buf;
|
void *pcm_buf;
|
||||||
@@ -56,7 +47,6 @@ typedef struct OSSVoiceOut {
|
|||||||
int fragsize;
|
int fragsize;
|
||||||
int mmapped;
|
int mmapped;
|
||||||
int pending;
|
int pending;
|
||||||
OSSConf *conf;
|
|
||||||
} OSSVoiceOut;
|
} OSSVoiceOut;
|
||||||
|
|
||||||
typedef struct OSSVoiceIn {
|
typedef struct OSSVoiceIn {
|
||||||
@@ -65,9 +55,28 @@ typedef struct OSSVoiceIn {
|
|||||||
int fd;
|
int fd;
|
||||||
int nfrags;
|
int nfrags;
|
||||||
int fragsize;
|
int fragsize;
|
||||||
OSSConf *conf;
|
|
||||||
} OSSVoiceIn;
|
} OSSVoiceIn;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int try_mmap;
|
||||||
|
int nfrags;
|
||||||
|
int fragsize;
|
||||||
|
const char *devpath_out;
|
||||||
|
const char *devpath_in;
|
||||||
|
int debug;
|
||||||
|
int exclusive;
|
||||||
|
int policy;
|
||||||
|
} conf = {
|
||||||
|
.try_mmap = 0,
|
||||||
|
.nfrags = 4,
|
||||||
|
.fragsize = 4096,
|
||||||
|
.devpath_out = "/dev/dsp",
|
||||||
|
.devpath_in = "/dev/dsp",
|
||||||
|
.debug = 0,
|
||||||
|
.exclusive = 0,
|
||||||
|
.policy = 5
|
||||||
|
};
|
||||||
|
|
||||||
struct oss_params {
|
struct oss_params {
|
||||||
int freq;
|
int freq;
|
||||||
audfmt_e fmt;
|
audfmt_e fmt;
|
||||||
@@ -129,18 +138,18 @@ static void oss_helper_poll_in (void *opaque)
|
|||||||
audio_run ("oss_poll_in");
|
audio_run ("oss_poll_in");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void oss_poll_out (HWVoiceOut *hw)
|
static int oss_poll_out (HWVoiceOut *hw)
|
||||||
{
|
{
|
||||||
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
||||||
|
|
||||||
qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL);
|
return qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void oss_poll_in (HWVoiceIn *hw)
|
static int oss_poll_in (HWVoiceIn *hw)
|
||||||
{
|
{
|
||||||
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
||||||
|
|
||||||
qemu_set_fd_handler (oss->fd, oss_helper_poll_in, NULL, NULL);
|
return qemu_set_fd_handler (oss->fd, oss_helper_poll_in, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int oss_write (SWVoiceOut *sw, void *buf, int len)
|
static int oss_write (SWVoiceOut *sw, void *buf, int len)
|
||||||
@@ -263,18 +272,18 @@ static int oss_get_version (int fd, int *version, const char *typ)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static int oss_open (int in, struct oss_params *req,
|
static int oss_open (int in, struct oss_params *req,
|
||||||
struct oss_params *obt, int *pfd, OSSConf* conf)
|
struct oss_params *obt, int *pfd)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
int oflags = conf->exclusive ? O_EXCL : 0;
|
int oflags = conf.exclusive ? O_EXCL : 0;
|
||||||
audio_buf_info abinfo;
|
audio_buf_info abinfo;
|
||||||
int fmt, freq, nchannels;
|
int fmt, freq, nchannels;
|
||||||
int setfragment = 1;
|
int setfragment = 1;
|
||||||
const char *dspname = in ? conf->devpath_in : conf->devpath_out;
|
const char *dspname = in ? conf.devpath_in : conf.devpath_out;
|
||||||
const char *typ = in ? "ADC" : "DAC";
|
const char *typ = in ? "ADC" : "DAC";
|
||||||
|
|
||||||
/* Kludge needed to have working mmap on Linux */
|
/* Kludge needed to have working mmap on Linux */
|
||||||
oflags |= conf->try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY);
|
oflags |= conf.try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY);
|
||||||
|
|
||||||
fd = open (dspname, oflags | O_NONBLOCK);
|
fd = open (dspname, oflags | O_NONBLOCK);
|
||||||
if (-1 == fd) {
|
if (-1 == fd) {
|
||||||
@@ -308,18 +317,20 @@ static int oss_open (int in, struct oss_params *req,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_DSP_POLICY
|
#ifdef USE_DSP_POLICY
|
||||||
if (conf->policy >= 0) {
|
if (conf.policy >= 0) {
|
||||||
int version;
|
int version;
|
||||||
|
|
||||||
if (!oss_get_version (fd, &version, typ)) {
|
if (!oss_get_version (fd, &version, typ)) {
|
||||||
trace_oss_version(version);
|
if (conf.debug) {
|
||||||
|
dolog ("OSS version = %#x\n", version);
|
||||||
|
}
|
||||||
|
|
||||||
if (version >= 0x040000) {
|
if (version >= 0x040000) {
|
||||||
int policy = conf->policy;
|
int policy = conf.policy;
|
||||||
if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) {
|
if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) {
|
||||||
oss_logerr2 (errno, typ,
|
oss_logerr2 (errno, typ,
|
||||||
"Failed to set timing policy to %d\n",
|
"Failed to set timing policy to %d\n",
|
||||||
conf->policy);
|
conf.policy);
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
setfragment = 0;
|
setfragment = 0;
|
||||||
@@ -447,12 +458,19 @@ static int oss_run_out (HWVoiceOut *hw, int live)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (abinfo.bytes > bufsize) {
|
if (abinfo.bytes > bufsize) {
|
||||||
trace_oss_invalid_available_size(abinfo.bytes, bufsize);
|
if (conf.debug) {
|
||||||
|
dolog ("warning: Invalid available size, size=%d bufsize=%d\n"
|
||||||
|
"please report your OS/audio hw to av1474@comtv.ru\n",
|
||||||
|
abinfo.bytes, bufsize);
|
||||||
|
}
|
||||||
abinfo.bytes = bufsize;
|
abinfo.bytes = bufsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abinfo.bytes < 0) {
|
if (abinfo.bytes < 0) {
|
||||||
trace_oss_invalid_available_size(abinfo.bytes, bufsize);
|
if (conf.debug) {
|
||||||
|
dolog ("warning: Invalid available size, size=%d bufsize=%d\n",
|
||||||
|
abinfo.bytes, bufsize);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,8 +510,7 @@ static void oss_fini_out (HWVoiceOut *hw)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int oss_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
OSSVoiceOut *oss = (OSSVoiceOut *) hw;
|
||||||
struct oss_params req, obt;
|
struct oss_params req, obt;
|
||||||
@@ -502,17 +519,16 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
int fd;
|
int fd;
|
||||||
audfmt_e effective_fmt;
|
audfmt_e effective_fmt;
|
||||||
struct audsettings obt_as;
|
struct audsettings obt_as;
|
||||||
OSSConf *conf = drv_opaque;
|
|
||||||
|
|
||||||
oss->fd = -1;
|
oss->fd = -1;
|
||||||
|
|
||||||
req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
|
req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
|
||||||
req.freq = as->freq;
|
req.freq = as->freq;
|
||||||
req.nchannels = as->nchannels;
|
req.nchannels = as->nchannels;
|
||||||
req.fragsize = conf->fragsize;
|
req.fragsize = conf.fragsize;
|
||||||
req.nfrags = conf->nfrags;
|
req.nfrags = conf.nfrags;
|
||||||
|
|
||||||
if (oss_open (0, &req, &obt, &fd, conf)) {
|
if (oss_open (0, &req, &obt, &fd)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +555,7 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
|
hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift;
|
||||||
|
|
||||||
oss->mmapped = 0;
|
oss->mmapped = 0;
|
||||||
if (conf->try_mmap) {
|
if (conf.try_mmap) {
|
||||||
oss->pcm_buf = mmap (
|
oss->pcm_buf = mmap (
|
||||||
NULL,
|
NULL,
|
||||||
hw->samples << hw->info.shift,
|
hw->samples << hw->info.shift,
|
||||||
@@ -599,7 +615,6 @@ static int oss_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
oss->fd = fd;
|
oss->fd = fd;
|
||||||
oss->conf = conf;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,8 +634,7 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
va_end (ap);
|
va_end (ap);
|
||||||
|
|
||||||
ldebug ("enabling voice\n");
|
ldebug ("enabling voice\n");
|
||||||
if (poll_mode) {
|
if (poll_mode && oss_poll_out (hw)) {
|
||||||
oss_poll_out (hw);
|
|
||||||
poll_mode = 0;
|
poll_mode = 0;
|
||||||
}
|
}
|
||||||
hw->poll_mode = poll_mode;
|
hw->poll_mode = poll_mode;
|
||||||
@@ -662,7 +676,7 @@ static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
static int oss_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
OSSVoiceIn *oss = (OSSVoiceIn *) hw;
|
||||||
struct oss_params req, obt;
|
struct oss_params req, obt;
|
||||||
@@ -671,16 +685,15 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
|||||||
int fd;
|
int fd;
|
||||||
audfmt_e effective_fmt;
|
audfmt_e effective_fmt;
|
||||||
struct audsettings obt_as;
|
struct audsettings obt_as;
|
||||||
OSSConf *conf = drv_opaque;
|
|
||||||
|
|
||||||
oss->fd = -1;
|
oss->fd = -1;
|
||||||
|
|
||||||
req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
|
req.fmt = aud_to_ossfmt (as->fmt, as->endianness);
|
||||||
req.freq = as->freq;
|
req.freq = as->freq;
|
||||||
req.nchannels = as->nchannels;
|
req.nchannels = as->nchannels;
|
||||||
req.fragsize = conf->fragsize;
|
req.fragsize = conf.fragsize;
|
||||||
req.nfrags = conf->nfrags;
|
req.nfrags = conf.nfrags;
|
||||||
if (oss_open (1, &req, &obt, &fd, conf)) {
|
if (oss_open (1, &req, &obt, &fd)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +727,6 @@ static int oss_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
oss->fd = fd;
|
oss->fd = fd;
|
||||||
oss->conf = conf;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,8 +828,7 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
poll_mode = va_arg (ap, int);
|
poll_mode = va_arg (ap, int);
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
|
|
||||||
if (poll_mode) {
|
if (poll_mode && oss_poll_in (hw)) {
|
||||||
oss_poll_in (hw);
|
|
||||||
poll_mode = 0;
|
poll_mode = 0;
|
||||||
}
|
}
|
||||||
hw->poll_mode = poll_mode;
|
hw->poll_mode = poll_mode;
|
||||||
@@ -834,79 +845,71 @@ static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static OSSConf glob_conf = {
|
|
||||||
.try_mmap = 0,
|
|
||||||
.nfrags = 4,
|
|
||||||
.fragsize = 4096,
|
|
||||||
.devpath_out = "/dev/dsp",
|
|
||||||
.devpath_in = "/dev/dsp",
|
|
||||||
.exclusive = 0,
|
|
||||||
.policy = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *oss_audio_init (void)
|
static void *oss_audio_init (void)
|
||||||
{
|
{
|
||||||
OSSConf *conf = g_malloc(sizeof(OSSConf));
|
if (access(conf.devpath_in, R_OK | W_OK) < 0 ||
|
||||||
*conf = glob_conf;
|
access(conf.devpath_out, R_OK | W_OK) < 0) {
|
||||||
|
|
||||||
if (access(conf->devpath_in, R_OK | W_OK) < 0 ||
|
|
||||||
access(conf->devpath_out, R_OK | W_OK) < 0) {
|
|
||||||
g_free(conf);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return conf;
|
return &conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void oss_audio_fini (void *opaque)
|
static void oss_audio_fini (void *opaque)
|
||||||
{
|
{
|
||||||
g_free(opaque);
|
(void) opaque;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option oss_options[] = {
|
static struct audio_option oss_options[] = {
|
||||||
{
|
{
|
||||||
.name = "FRAGSIZE",
|
.name = "FRAGSIZE",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.fragsize,
|
.valp = &conf.fragsize,
|
||||||
.descr = "Fragment size in bytes"
|
.descr = "Fragment size in bytes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "NFRAGS",
|
.name = "NFRAGS",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.nfrags,
|
.valp = &conf.nfrags,
|
||||||
.descr = "Number of fragments"
|
.descr = "Number of fragments"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "MMAP",
|
.name = "MMAP",
|
||||||
.tag = AUD_OPT_BOOL,
|
.tag = AUD_OPT_BOOL,
|
||||||
.valp = &glob_conf.try_mmap,
|
.valp = &conf.try_mmap,
|
||||||
.descr = "Try using memory mapped access"
|
.descr = "Try using memory mapped access"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "DAC_DEV",
|
.name = "DAC_DEV",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.devpath_out,
|
.valp = &conf.devpath_out,
|
||||||
.descr = "Path to DAC device"
|
.descr = "Path to DAC device"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "ADC_DEV",
|
.name = "ADC_DEV",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.devpath_in,
|
.valp = &conf.devpath_in,
|
||||||
.descr = "Path to ADC device"
|
.descr = "Path to ADC device"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "EXCLUSIVE",
|
.name = "EXCLUSIVE",
|
||||||
.tag = AUD_OPT_BOOL,
|
.tag = AUD_OPT_BOOL,
|
||||||
.valp = &glob_conf.exclusive,
|
.valp = &conf.exclusive,
|
||||||
.descr = "Open device in exclusive mode (vmix won't work)"
|
.descr = "Open device in exclusive mode (vmix wont work)"
|
||||||
},
|
},
|
||||||
#ifdef USE_DSP_POLICY
|
#ifdef USE_DSP_POLICY
|
||||||
{
|
{
|
||||||
.name = "POLICY",
|
.name = "POLICY",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.policy,
|
.valp = &conf.policy,
|
||||||
.descr = "Set the timing policy of the device, -1 to use fragment mode",
|
.descr = "Set the timing policy of the device, -1 to use fragment mode",
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
{
|
||||||
|
.name = "DEBUG",
|
||||||
|
.tag = AUD_OPT_BOOL,
|
||||||
|
.valp = &conf.debug,
|
||||||
|
.descr = "Turn on some debugging messages"
|
||||||
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
117
audio/paaudio.c
117
audio/paaudio.c
@@ -1,5 +1,4 @@
|
|||||||
/* public domain */
|
/* public domain */
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
@@ -9,19 +8,6 @@
|
|||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
#include "audio_pt_int.h"
|
#include "audio_pt_int.h"
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int samples;
|
|
||||||
char *server;
|
|
||||||
char *sink;
|
|
||||||
char *source;
|
|
||||||
} PAConf;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PAConf conf;
|
|
||||||
pa_threaded_mainloop *mainloop;
|
|
||||||
pa_context *context;
|
|
||||||
} paaudio;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
int done;
|
int done;
|
||||||
@@ -31,7 +17,6 @@ typedef struct {
|
|||||||
pa_stream *stream;
|
pa_stream *stream;
|
||||||
void *pcm_buf;
|
void *pcm_buf;
|
||||||
struct audio_pt pt;
|
struct audio_pt pt;
|
||||||
paaudio *g;
|
|
||||||
} PAVoiceOut;
|
} PAVoiceOut;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -45,10 +30,20 @@ typedef struct {
|
|||||||
struct audio_pt pt;
|
struct audio_pt pt;
|
||||||
const void *read_data;
|
const void *read_data;
|
||||||
size_t read_index, read_length;
|
size_t read_index, read_length;
|
||||||
paaudio *g;
|
|
||||||
} PAVoiceIn;
|
} PAVoiceIn;
|
||||||
|
|
||||||
static void qpa_audio_fini(void *opaque);
|
typedef struct {
|
||||||
|
int samples;
|
||||||
|
char *server;
|
||||||
|
char *sink;
|
||||||
|
char *source;
|
||||||
|
pa_threaded_mainloop *mainloop;
|
||||||
|
pa_context *context;
|
||||||
|
} paaudio;
|
||||||
|
|
||||||
|
static paaudio glob_paaudio = {
|
||||||
|
.samples = 4096,
|
||||||
|
};
|
||||||
|
|
||||||
static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...)
|
static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
@@ -111,7 +106,7 @@ static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x)
|
|||||||
|
|
||||||
static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror)
|
static int qpa_simple_read (PAVoiceIn *p, void *data, size_t length, int *rerror)
|
||||||
{
|
{
|
||||||
paaudio *g = p->g;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
pa_threaded_mainloop_lock (g->mainloop);
|
pa_threaded_mainloop_lock (g->mainloop);
|
||||||
|
|
||||||
@@ -165,7 +160,7 @@ unlock_and_fail:
|
|||||||
|
|
||||||
static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror)
|
static int qpa_simple_write (PAVoiceOut *p, const void *data, size_t length, int *rerror)
|
||||||
{
|
{
|
||||||
paaudio *g = p->g;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
pa_threaded_mainloop_lock (g->mainloop);
|
pa_threaded_mainloop_lock (g->mainloop);
|
||||||
|
|
||||||
@@ -227,7 +222,7 @@ static void *qpa_thread_out (void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decr = to_mix = audio_MIN (pa->live, pa->g->conf.samples >> 2);
|
decr = to_mix = audio_MIN (pa->live, glob_paaudio.samples >> 2);
|
||||||
rpos = pa->rpos;
|
rpos = pa->rpos;
|
||||||
|
|
||||||
if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
|
if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
|
||||||
@@ -319,7 +314,7 @@ static void *qpa_thread_in (void *arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
incr = to_grab = audio_MIN (pa->dead, pa->g->conf.samples >> 2);
|
incr = to_grab = audio_MIN (pa->dead, glob_paaudio.samples >> 2);
|
||||||
wpos = pa->wpos;
|
wpos = pa->wpos;
|
||||||
|
|
||||||
if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
|
if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) {
|
||||||
@@ -435,7 +430,7 @@ static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness)
|
|||||||
|
|
||||||
static void context_state_cb (pa_context *c, void *userdata)
|
static void context_state_cb (pa_context *c, void *userdata)
|
||||||
{
|
{
|
||||||
paaudio *g = userdata;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
switch (pa_context_get_state(c)) {
|
switch (pa_context_get_state(c)) {
|
||||||
case PA_CONTEXT_READY:
|
case PA_CONTEXT_READY:
|
||||||
@@ -454,7 +449,7 @@ static void context_state_cb (pa_context *c, void *userdata)
|
|||||||
|
|
||||||
static void stream_state_cb (pa_stream *s, void * userdata)
|
static void stream_state_cb (pa_stream *s, void * userdata)
|
||||||
{
|
{
|
||||||
paaudio *g = userdata;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
switch (pa_stream_get_state (s)) {
|
switch (pa_stream_get_state (s)) {
|
||||||
|
|
||||||
@@ -472,21 +467,23 @@ static void stream_state_cb (pa_stream *s, void * userdata)
|
|||||||
|
|
||||||
static void stream_request_cb (pa_stream *s, size_t length, void *userdata)
|
static void stream_request_cb (pa_stream *s, size_t length, void *userdata)
|
||||||
{
|
{
|
||||||
paaudio *g = userdata;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
pa_threaded_mainloop_signal (g->mainloop, 0);
|
pa_threaded_mainloop_signal (g->mainloop, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static pa_stream *qpa_simple_new (
|
static pa_stream *qpa_simple_new (
|
||||||
paaudio *g,
|
const char *server,
|
||||||
const char *name,
|
const char *name,
|
||||||
pa_stream_direction_t dir,
|
pa_stream_direction_t dir,
|
||||||
const char *dev,
|
const char *dev,
|
||||||
|
const char *stream_name,
|
||||||
const pa_sample_spec *ss,
|
const pa_sample_spec *ss,
|
||||||
const pa_channel_map *map,
|
const pa_channel_map *map,
|
||||||
const pa_buffer_attr *attr,
|
const pa_buffer_attr *attr,
|
||||||
int *rerror)
|
int *rerror)
|
||||||
{
|
{
|
||||||
|
paaudio *g = &glob_paaudio;
|
||||||
int r;
|
int r;
|
||||||
pa_stream *stream;
|
pa_stream *stream;
|
||||||
|
|
||||||
@@ -537,15 +534,13 @@ fail:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int qpa_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
pa_sample_spec ss;
|
static pa_sample_spec ss;
|
||||||
pa_buffer_attr ba;
|
static pa_buffer_attr ba;
|
||||||
struct audsettings obt_as = *as;
|
struct audsettings obt_as = *as;
|
||||||
PAVoiceOut *pa = (PAVoiceOut *) hw;
|
PAVoiceOut *pa = (PAVoiceOut *) hw;
|
||||||
paaudio *g = pa->g = drv_opaque;
|
|
||||||
|
|
||||||
ss.format = audfmt_to_pa (as->fmt, as->endianness);
|
ss.format = audfmt_to_pa (as->fmt, as->endianness);
|
||||||
ss.channels = as->nchannels;
|
ss.channels = as->nchannels;
|
||||||
@@ -563,10 +558,11 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
|
obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
|
||||||
|
|
||||||
pa->stream = qpa_simple_new (
|
pa->stream = qpa_simple_new (
|
||||||
g,
|
glob_paaudio.server,
|
||||||
"qemu",
|
"qemu",
|
||||||
PA_STREAM_PLAYBACK,
|
PA_STREAM_PLAYBACK,
|
||||||
g->conf.sink,
|
glob_paaudio.sink,
|
||||||
|
"pcm.playback",
|
||||||
&ss,
|
&ss,
|
||||||
NULL, /* channel map */
|
NULL, /* channel map */
|
||||||
&ba, /* buffering attributes */
|
&ba, /* buffering attributes */
|
||||||
@@ -578,7 +574,7 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
}
|
}
|
||||||
|
|
||||||
audio_pcm_init_info (&hw->info, &obt_as);
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
hw->samples = g->conf.samples;
|
hw->samples = glob_paaudio.samples;
|
||||||
pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
||||||
pa->rpos = hw->rpos;
|
pa->rpos = hw->rpos;
|
||||||
if (!pa->pcm_buf) {
|
if (!pa->pcm_buf) {
|
||||||
@@ -605,13 +601,12 @@ static int qpa_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
static int qpa_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
pa_sample_spec ss;
|
static pa_sample_spec ss;
|
||||||
struct audsettings obt_as = *as;
|
struct audsettings obt_as = *as;
|
||||||
PAVoiceIn *pa = (PAVoiceIn *) hw;
|
PAVoiceIn *pa = (PAVoiceIn *) hw;
|
||||||
paaudio *g = pa->g = drv_opaque;
|
|
||||||
|
|
||||||
ss.format = audfmt_to_pa (as->fmt, as->endianness);
|
ss.format = audfmt_to_pa (as->fmt, as->endianness);
|
||||||
ss.channels = as->nchannels;
|
ss.channels = as->nchannels;
|
||||||
@@ -620,10 +615,11 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
|||||||
obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
|
obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness);
|
||||||
|
|
||||||
pa->stream = qpa_simple_new (
|
pa->stream = qpa_simple_new (
|
||||||
g,
|
glob_paaudio.server,
|
||||||
"qemu",
|
"qemu",
|
||||||
PA_STREAM_RECORD,
|
PA_STREAM_RECORD,
|
||||||
g->conf.source,
|
glob_paaudio.source,
|
||||||
|
"pcm.capture",
|
||||||
&ss,
|
&ss,
|
||||||
NULL, /* channel map */
|
NULL, /* channel map */
|
||||||
NULL, /* buffering attributes */
|
NULL, /* buffering attributes */
|
||||||
@@ -635,7 +631,7 @@ static int qpa_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
|||||||
}
|
}
|
||||||
|
|
||||||
audio_pcm_init_info (&hw->info, &obt_as);
|
audio_pcm_init_info (&hw->info, &obt_as);
|
||||||
hw->samples = g->conf.samples;
|
hw->samples = glob_paaudio.samples;
|
||||||
pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
|
||||||
pa->wpos = hw->wpos;
|
pa->wpos = hw->wpos;
|
||||||
if (!pa->pcm_buf) {
|
if (!pa->pcm_buf) {
|
||||||
@@ -707,7 +703,7 @@ static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
PAVoiceOut *pa = (PAVoiceOut *) hw;
|
PAVoiceOut *pa = (PAVoiceOut *) hw;
|
||||||
pa_operation *op;
|
pa_operation *op;
|
||||||
pa_cvolume v;
|
pa_cvolume v;
|
||||||
paaudio *g = pa->g;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */
|
#ifdef PA_CHECK_VERSION /* macro is present in 0.9.16+ */
|
||||||
pa_cvolume_init (&v); /* function is present in 0.9.13+ */
|
pa_cvolume_init (&v); /* function is present in 0.9.13+ */
|
||||||
@@ -759,7 +755,7 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
PAVoiceIn *pa = (PAVoiceIn *) hw;
|
PAVoiceIn *pa = (PAVoiceIn *) hw;
|
||||||
pa_operation *op;
|
pa_operation *op;
|
||||||
pa_cvolume v;
|
pa_cvolume v;
|
||||||
paaudio *g = pa->g;
|
paaudio *g = &glob_paaudio;
|
||||||
|
|
||||||
#ifdef PA_CHECK_VERSION
|
#ifdef PA_CHECK_VERSION
|
||||||
pa_cvolume_init (&v);
|
pa_cvolume_init (&v);
|
||||||
@@ -781,22 +777,23 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
|
|
||||||
pa_threaded_mainloop_lock (g->mainloop);
|
pa_threaded_mainloop_lock (g->mainloop);
|
||||||
|
|
||||||
op = pa_context_set_source_output_volume (g->context,
|
/* FIXME: use the upcoming "set_source_output_{volume,mute}" */
|
||||||
pa_stream_get_index (pa->stream),
|
op = pa_context_set_source_volume_by_index (g->context,
|
||||||
|
pa_stream_get_device_index (pa->stream),
|
||||||
&v, NULL, NULL);
|
&v, NULL, NULL);
|
||||||
if (!op) {
|
if (!op) {
|
||||||
qpa_logerr (pa_context_errno (g->context),
|
qpa_logerr (pa_context_errno (g->context),
|
||||||
"set_source_output_volume() failed\n");
|
"set_source_volume() failed\n");
|
||||||
} else {
|
} else {
|
||||||
pa_operation_unref(op);
|
pa_operation_unref(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
op = pa_context_set_source_output_mute (g->context,
|
op = pa_context_set_source_mute_by_index (g->context,
|
||||||
pa_stream_get_index (pa->stream),
|
pa_stream_get_index (pa->stream),
|
||||||
sw->vol.mute, NULL, NULL);
|
sw->vol.mute, NULL, NULL);
|
||||||
if (!op) {
|
if (!op) {
|
||||||
qpa_logerr (pa_context_errno (g->context),
|
qpa_logerr (pa_context_errno (g->context),
|
||||||
"set_source_output_mute() failed\n");
|
"set_source_mute() failed\n");
|
||||||
} else {
|
} else {
|
||||||
pa_operation_unref (op);
|
pa_operation_unref (op);
|
||||||
}
|
}
|
||||||
@@ -808,31 +805,23 @@ static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* common */
|
/* common */
|
||||||
static PAConf glob_conf = {
|
|
||||||
.samples = 4096,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *qpa_audio_init (void)
|
static void *qpa_audio_init (void)
|
||||||
{
|
{
|
||||||
paaudio *g = g_malloc(sizeof(paaudio));
|
paaudio *g = &glob_paaudio;
|
||||||
g->conf = glob_conf;
|
|
||||||
g->mainloop = NULL;
|
|
||||||
g->context = NULL;
|
|
||||||
|
|
||||||
g->mainloop = pa_threaded_mainloop_new ();
|
g->mainloop = pa_threaded_mainloop_new ();
|
||||||
if (!g->mainloop) {
|
if (!g->mainloop) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop),
|
g->context = pa_context_new (pa_threaded_mainloop_get_api (g->mainloop), glob_paaudio.server);
|
||||||
g->conf.server);
|
|
||||||
if (!g->context) {
|
if (!g->context) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
pa_context_set_state_callback (g->context, context_state_cb, g);
|
pa_context_set_state_callback (g->context, context_state_cb, g);
|
||||||
|
|
||||||
if (pa_context_connect (g->context, g->conf.server, 0, NULL) < 0) {
|
if (pa_context_connect (g->context, glob_paaudio.server, 0, NULL) < 0) {
|
||||||
qpa_logerr (pa_context_errno (g->context),
|
qpa_logerr (pa_context_errno (g->context),
|
||||||
"pa_context_connect() failed\n");
|
"pa_context_connect() failed\n");
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -865,13 +854,12 @@ static void *qpa_audio_init (void)
|
|||||||
|
|
||||||
pa_threaded_mainloop_unlock (g->mainloop);
|
pa_threaded_mainloop_unlock (g->mainloop);
|
||||||
|
|
||||||
return g;
|
return &glob_paaudio;
|
||||||
|
|
||||||
unlock_and_fail:
|
unlock_and_fail:
|
||||||
pa_threaded_mainloop_unlock (g->mainloop);
|
pa_threaded_mainloop_unlock (g->mainloop);
|
||||||
fail:
|
fail:
|
||||||
AUD_log (AUDIO_CAP, "Failed to initialize PA context");
|
AUD_log (AUDIO_CAP, "Failed to initialize PA context");
|
||||||
qpa_audio_fini(g);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,38 +874,39 @@ static void qpa_audio_fini (void *opaque)
|
|||||||
if (g->context) {
|
if (g->context) {
|
||||||
pa_context_disconnect (g->context);
|
pa_context_disconnect (g->context);
|
||||||
pa_context_unref (g->context);
|
pa_context_unref (g->context);
|
||||||
|
g->context = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g->mainloop) {
|
if (g->mainloop) {
|
||||||
pa_threaded_mainloop_free (g->mainloop);
|
pa_threaded_mainloop_free (g->mainloop);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(g);
|
g->mainloop = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct audio_option qpa_options[] = {
|
struct audio_option qpa_options[] = {
|
||||||
{
|
{
|
||||||
.name = "SAMPLES",
|
.name = "SAMPLES",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.samples,
|
.valp = &glob_paaudio.samples,
|
||||||
.descr = "buffer size in samples"
|
.descr = "buffer size in samples"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "SERVER",
|
.name = "SERVER",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.server,
|
.valp = &glob_paaudio.server,
|
||||||
.descr = "server address"
|
.descr = "server address"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "SINK",
|
.name = "SINK",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.sink,
|
.valp = &glob_paaudio.sink,
|
||||||
.descr = "sink device name"
|
.descr = "sink device name"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "SOURCE",
|
.name = "SOURCE",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.source,
|
.valp = &glob_paaudio.source,
|
||||||
.descr = "source device name"
|
.descr = "source device name"
|
||||||
},
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_thread.h>
|
#include <SDL_thread.h>
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
@@ -38,14 +37,10 @@
|
|||||||
#define AUDIO_CAP "sdl"
|
#define AUDIO_CAP "sdl"
|
||||||
#include "audio_int.h"
|
#include "audio_int.h"
|
||||||
|
|
||||||
#define USE_SEMAPHORE (SDL_MAJOR_VERSION < 2)
|
|
||||||
|
|
||||||
typedef struct SDLVoiceOut {
|
typedef struct SDLVoiceOut {
|
||||||
HWVoiceOut hw;
|
HWVoiceOut hw;
|
||||||
int live;
|
int live;
|
||||||
#if USE_SEMAPHORE
|
|
||||||
int rpos;
|
int rpos;
|
||||||
#endif
|
|
||||||
int decr;
|
int decr;
|
||||||
} SDLVoiceOut;
|
} SDLVoiceOut;
|
||||||
|
|
||||||
@@ -57,12 +52,9 @@ static struct {
|
|||||||
|
|
||||||
static struct SDLAudioState {
|
static struct SDLAudioState {
|
||||||
int exit;
|
int exit;
|
||||||
#if USE_SEMAPHORE
|
|
||||||
SDL_mutex *mutex;
|
SDL_mutex *mutex;
|
||||||
SDL_sem *sem;
|
SDL_sem *sem;
|
||||||
#endif
|
|
||||||
int initialized;
|
int initialized;
|
||||||
bool driver_created;
|
|
||||||
} glob_sdl;
|
} glob_sdl;
|
||||||
typedef struct SDLAudioState SDLAudioState;
|
typedef struct SDLAudioState SDLAudioState;
|
||||||
|
|
||||||
@@ -79,45 +71,31 @@ static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...)
|
|||||||
|
|
||||||
static int sdl_lock (SDLAudioState *s, const char *forfn)
|
static int sdl_lock (SDLAudioState *s, const char *forfn)
|
||||||
{
|
{
|
||||||
#if USE_SEMAPHORE
|
|
||||||
if (SDL_LockMutex (s->mutex)) {
|
if (SDL_LockMutex (s->mutex)) {
|
||||||
sdl_logerr ("SDL_LockMutex for %s failed\n", forfn);
|
sdl_logerr ("SDL_LockMutex for %s failed\n", forfn);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
SDL_LockAudio();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sdl_unlock (SDLAudioState *s, const char *forfn)
|
static int sdl_unlock (SDLAudioState *s, const char *forfn)
|
||||||
{
|
{
|
||||||
#if USE_SEMAPHORE
|
|
||||||
if (SDL_UnlockMutex (s->mutex)) {
|
if (SDL_UnlockMutex (s->mutex)) {
|
||||||
sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn);
|
sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
SDL_UnlockAudio();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sdl_post (SDLAudioState *s, const char *forfn)
|
static int sdl_post (SDLAudioState *s, const char *forfn)
|
||||||
{
|
{
|
||||||
#if USE_SEMAPHORE
|
|
||||||
if (SDL_SemPost (s->sem)) {
|
if (SDL_SemPost (s->sem)) {
|
||||||
sdl_logerr ("SDL_SemPost for %s failed\n", forfn);
|
sdl_logerr ("SDL_SemPost for %s failed\n", forfn);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if USE_SEMAPHORE
|
|
||||||
static int sdl_wait (SDLAudioState *s, const char *forfn)
|
static int sdl_wait (SDLAudioState *s, const char *forfn)
|
||||||
{
|
{
|
||||||
if (SDL_SemWait (s->sem)) {
|
if (SDL_SemWait (s->sem)) {
|
||||||
@@ -126,7 +104,6 @@ static int sdl_wait (SDLAudioState *s, const char *forfn)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn)
|
static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn)
|
||||||
{
|
{
|
||||||
@@ -267,7 +244,6 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
|||||||
int to_mix, decr;
|
int to_mix, decr;
|
||||||
|
|
||||||
/* dolog ("in callback samples=%d\n", samples); */
|
/* dolog ("in callback samples=%d\n", samples); */
|
||||||
#if USE_SEMAPHORE
|
|
||||||
sdl_wait (s, "sdl_callback");
|
sdl_wait (s, "sdl_callback");
|
||||||
if (s->exit) {
|
if (s->exit) {
|
||||||
return;
|
return;
|
||||||
@@ -286,11 +262,6 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
|||||||
if (!sdl->live) {
|
if (!sdl->live) {
|
||||||
goto again;
|
goto again;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
if (s->exit || !sdl->live) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* dolog ("in callback live=%d\n", live); */
|
/* dolog ("in callback live=%d\n", live); */
|
||||||
to_mix = audio_MIN (samples, sdl->live);
|
to_mix = audio_MIN (samples, sdl->live);
|
||||||
@@ -301,11 +272,7 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
|||||||
|
|
||||||
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
|
/* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */
|
||||||
hw->clip (buf, src, chunk);
|
hw->clip (buf, src, chunk);
|
||||||
#if USE_SEMAPHORE
|
|
||||||
sdl->rpos = (sdl->rpos + chunk) % hw->samples;
|
sdl->rpos = (sdl->rpos + chunk) % hw->samples;
|
||||||
#else
|
|
||||||
hw->rpos = (hw->rpos + chunk) % hw->samples;
|
|
||||||
#endif
|
|
||||||
to_mix -= chunk;
|
to_mix -= chunk;
|
||||||
buf += chunk << hw->info.shift;
|
buf += chunk << hw->info.shift;
|
||||||
}
|
}
|
||||||
@@ -313,21 +280,12 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len)
|
|||||||
sdl->live -= decr;
|
sdl->live -= decr;
|
||||||
sdl->decr += decr;
|
sdl->decr += decr;
|
||||||
|
|
||||||
#if USE_SEMAPHORE
|
|
||||||
again:
|
again:
|
||||||
if (sdl_unlock (s, "sdl_callback")) {
|
if (sdl_unlock (s, "sdl_callback")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
/* dolog ("done len=%d\n", len); */
|
/* dolog ("done len=%d\n", len); */
|
||||||
|
|
||||||
#if (SDL_MAJOR_VERSION >= 2)
|
|
||||||
/* SDL2 does not clear the remaining buffer for us, so do it on our own */
|
|
||||||
if (samples) {
|
|
||||||
memset(buf, 0, samples << hw->info.shift);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sdl_write_out (SWVoiceOut *sw, void *buf, int len)
|
static int sdl_write_out (SWVoiceOut *sw, void *buf, int len)
|
||||||
@@ -355,12 +313,8 @@ static int sdl_run_out (HWVoiceOut *hw, int live)
|
|||||||
decr = audio_MIN (sdl->decr, live);
|
decr = audio_MIN (sdl->decr, live);
|
||||||
sdl->decr -= decr;
|
sdl->decr -= decr;
|
||||||
|
|
||||||
#if USE_SEMAPHORE
|
|
||||||
sdl->live = live - decr;
|
sdl->live = live - decr;
|
||||||
hw->rpos = sdl->rpos;
|
hw->rpos = sdl->rpos;
|
||||||
#else
|
|
||||||
sdl->live = live;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (sdl->live > 0) {
|
if (sdl->live > 0) {
|
||||||
sdl_unlock_and_post (s, "sdl_run_out");
|
sdl_unlock_and_post (s, "sdl_run_out");
|
||||||
@@ -378,8 +332,7 @@ static void sdl_fini_out (HWVoiceOut *hw)
|
|||||||
sdl_close (&glob_sdl);
|
sdl_close (&glob_sdl);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sdl_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int sdl_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
|
SDLVoiceOut *sdl = (SDLVoiceOut *) hw;
|
||||||
SDLAudioState *s = &glob_sdl;
|
SDLAudioState *s = &glob_sdl;
|
||||||
@@ -439,17 +392,12 @@ static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
static void *sdl_audio_init (void)
|
static void *sdl_audio_init (void)
|
||||||
{
|
{
|
||||||
SDLAudioState *s = &glob_sdl;
|
SDLAudioState *s = &glob_sdl;
|
||||||
if (s->driver_created) {
|
|
||||||
sdl_logerr("Can't create multiple sdl backends\n");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
|
if (SDL_InitSubSystem (SDL_INIT_AUDIO)) {
|
||||||
sdl_logerr ("SDL failed to initialize audio subsystem\n");
|
sdl_logerr ("SDL failed to initialize audio subsystem\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if USE_SEMAPHORE
|
|
||||||
s->mutex = SDL_CreateMutex ();
|
s->mutex = SDL_CreateMutex ();
|
||||||
if (!s->mutex) {
|
if (!s->mutex) {
|
||||||
sdl_logerr ("Failed to create SDL mutex\n");
|
sdl_logerr ("Failed to create SDL mutex\n");
|
||||||
@@ -464,9 +412,7 @@ static void *sdl_audio_init (void)
|
|||||||
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
s->driver_created = true;
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,12 +420,9 @@ static void sdl_audio_fini (void *opaque)
|
|||||||
{
|
{
|
||||||
SDLAudioState *s = opaque;
|
SDLAudioState *s = opaque;
|
||||||
sdl_close (s);
|
sdl_close (s);
|
||||||
#if USE_SEMAPHORE
|
|
||||||
SDL_DestroySemaphore (s->sem);
|
SDL_DestroySemaphore (s->sem);
|
||||||
SDL_DestroyMutex (s->mutex);
|
SDL_DestroyMutex (s->mutex);
|
||||||
#endif
|
|
||||||
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
SDL_QuitSubSystem (SDL_INIT_AUDIO);
|
||||||
s->driver_created = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option sdl_options[] = {
|
static struct audio_option sdl_options[] = {
|
||||||
|
|||||||
@@ -17,10 +17,7 @@
|
|||||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
#include "qemu/host-utils.h"
|
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "ui/qemu-spice.h"
|
#include "ui/qemu-spice.h"
|
||||||
|
|
||||||
@@ -105,11 +102,11 @@ static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
|
|||||||
|
|
||||||
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
ticks = now - rate->start_ticks;
|
ticks = now - rate->start_ticks;
|
||||||
bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);
|
bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ());
|
||||||
samples = (bytes - rate->bytes_sent) >> info->shift;
|
samples = (bytes - rate->bytes_sent) >> info->shift;
|
||||||
if (samples < 0 || samples > 65536) {
|
if (samples < 0 || samples > 65536) {
|
||||||
error_report("Resetting rate control (%" PRId64 " samples)", samples);
|
error_report("Resetting rate control (%" PRId64 " samples)", samples);
|
||||||
rate_start(rate);
|
rate_start (rate);
|
||||||
samples = 0;
|
samples = 0;
|
||||||
}
|
}
|
||||||
rate->bytes_sent += samples << info->shift;
|
rate->bytes_sent += samples << info->shift;
|
||||||
@@ -118,8 +115,7 @@ static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate)
|
|||||||
|
|
||||||
/* playback */
|
/* playback */
|
||||||
|
|
||||||
static int line_out_init(HWVoiceOut *hw, struct audsettings *as,
|
static int line_out_init (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
|
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
@@ -247,7 +243,7 @@ static int line_out_ctl (HWVoiceOut *hw, int cmd, ...)
|
|||||||
|
|
||||||
/* record */
|
/* record */
|
||||||
|
|
||||||
static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
|
static int line_in_init (HWVoiceIn *hw, struct audsettings *as)
|
||||||
{
|
{
|
||||||
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
|
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# See docs/tracing.txt for syntax documentation.
|
|
||||||
|
|
||||||
# audio/alsaaudio.c
|
|
||||||
alsa_revents(int revents) "revents = %d"
|
|
||||||
alsa_pollout(int i, int fd) "i = %d fd = %d"
|
|
||||||
alsa_set_handler(int events, int index, int fd, int err) "events=%#x index=%d fd=%d err=%d"
|
|
||||||
alsa_wrote_zero(int len) "Failed to write %d frames (wrote zero)"
|
|
||||||
alsa_read_zero(long len) "Failed to read %ld frames (read zero)"
|
|
||||||
alsa_xrun_out(void) "Recovering from playback xrun"
|
|
||||||
alsa_xrun_in(void) "Recovering from capture xrun"
|
|
||||||
alsa_resume_out(void) "Resuming suspended output stream"
|
|
||||||
alsa_resume_in(void) "Resuming suspended input stream"
|
|
||||||
alsa_no_frames(int state) "No frames available and ALSA state is %d"
|
|
||||||
|
|
||||||
# audio/ossaudio.c
|
|
||||||
oss_version(int version) "OSS version = %#x"
|
|
||||||
oss_invalid_available_size(int size, int bufsize) "Invalid available size, size=%d bufsize=%d"
|
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
#include "hw/hw.h"
|
||||||
#include "qemu/host-utils.h"
|
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
@@ -37,10 +36,15 @@ typedef struct WAVVoiceOut {
|
|||||||
int total_samples;
|
int total_samples;
|
||||||
} WAVVoiceOut;
|
} WAVVoiceOut;
|
||||||
|
|
||||||
typedef struct {
|
static struct {
|
||||||
struct audsettings settings;
|
struct audsettings settings;
|
||||||
const char *wav_path;
|
const char *wav_path;
|
||||||
} WAVConf;
|
} conf = {
|
||||||
|
.settings.freq = 44100,
|
||||||
|
.settings.nchannels = 2,
|
||||||
|
.settings.fmt = AUD_FMT_S16,
|
||||||
|
.wav_path = "qemu.wav"
|
||||||
|
};
|
||||||
|
|
||||||
static int wav_run_out (HWVoiceOut *hw, int live)
|
static int wav_run_out (HWVoiceOut *hw, int live)
|
||||||
{
|
{
|
||||||
@@ -51,7 +55,7 @@ static int wav_run_out (HWVoiceOut *hw, int live)
|
|||||||
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||||
int64_t ticks = now - wav->old_ticks;
|
int64_t ticks = now - wav->old_ticks;
|
||||||
int64_t bytes =
|
int64_t bytes =
|
||||||
muldiv64(ticks, hw->info.bytes_per_second, NANOSECONDS_PER_SECOND);
|
muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ());
|
||||||
|
|
||||||
if (bytes > INT_MAX) {
|
if (bytes > INT_MAX) {
|
||||||
samples = INT_MAX >> hw->info.shift;
|
samples = INT_MAX >> hw->info.shift;
|
||||||
@@ -101,8 +105,7 @@ static void le_store (uint8_t *buf, uint32_t val, int len)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
|
static int wav_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
void *drv_opaque)
|
|
||||||
{
|
{
|
||||||
WAVVoiceOut *wav = (WAVVoiceOut *) hw;
|
WAVVoiceOut *wav = (WAVVoiceOut *) hw;
|
||||||
int bits16 = 0, stereo = 0;
|
int bits16 = 0, stereo = 0;
|
||||||
@@ -112,8 +115,9 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
|
0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04,
|
||||||
0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
|
0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
WAVConf *conf = drv_opaque;
|
struct audsettings wav_as = conf.settings;
|
||||||
struct audsettings wav_as = conf->settings;
|
|
||||||
|
(void) as;
|
||||||
|
|
||||||
stereo = wav_as.nchannels == 2;
|
stereo = wav_as.nchannels == 2;
|
||||||
switch (wav_as.fmt) {
|
switch (wav_as.fmt) {
|
||||||
@@ -151,10 +155,10 @@ static int wav_init_out(HWVoiceOut *hw, struct audsettings *as,
|
|||||||
le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
|
le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4);
|
||||||
le_store (hdr + 32, 1 << (bits16 + stereo), 2);
|
le_store (hdr + 32, 1 << (bits16 + stereo), 2);
|
||||||
|
|
||||||
wav->f = fopen (conf->wav_path, "wb");
|
wav->f = fopen (conf.wav_path, "wb");
|
||||||
if (!wav->f) {
|
if (!wav->f) {
|
||||||
dolog ("Failed to open wave file `%s'\nReason: %s\n",
|
dolog ("Failed to open wave file `%s'\nReason: %s\n",
|
||||||
conf->wav_path, strerror (errno));
|
conf.wav_path, strerror (errno));
|
||||||
g_free (wav->pcm_buf);
|
g_free (wav->pcm_buf);
|
||||||
wav->pcm_buf = NULL;
|
wav->pcm_buf = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
@@ -222,49 +226,40 @@ static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static WAVConf glob_conf = {
|
|
||||||
.settings.freq = 44100,
|
|
||||||
.settings.nchannels = 2,
|
|
||||||
.settings.fmt = AUD_FMT_S16,
|
|
||||||
.wav_path = "qemu.wav"
|
|
||||||
};
|
|
||||||
|
|
||||||
static void *wav_audio_init (void)
|
static void *wav_audio_init (void)
|
||||||
{
|
{
|
||||||
WAVConf *conf = g_malloc(sizeof(WAVConf));
|
return &conf;
|
||||||
*conf = glob_conf;
|
|
||||||
return conf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wav_audio_fini (void *opaque)
|
static void wav_audio_fini (void *opaque)
|
||||||
{
|
{
|
||||||
|
(void) opaque;
|
||||||
ldebug ("wav_fini");
|
ldebug ("wav_fini");
|
||||||
g_free(opaque);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct audio_option wav_options[] = {
|
static struct audio_option wav_options[] = {
|
||||||
{
|
{
|
||||||
.name = "FREQUENCY",
|
.name = "FREQUENCY",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.settings.freq,
|
.valp = &conf.settings.freq,
|
||||||
.descr = "Frequency"
|
.descr = "Frequency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "FORMAT",
|
.name = "FORMAT",
|
||||||
.tag = AUD_OPT_FMT,
|
.tag = AUD_OPT_FMT,
|
||||||
.valp = &glob_conf.settings.fmt,
|
.valp = &conf.settings.fmt,
|
||||||
.descr = "Format"
|
.descr = "Format"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "DAC_FIXED_CHANNELS",
|
.name = "DAC_FIXED_CHANNELS",
|
||||||
.tag = AUD_OPT_INT,
|
.tag = AUD_OPT_INT,
|
||||||
.valp = &glob_conf.settings.nchannels,
|
.valp = &conf.settings.nchannels,
|
||||||
.descr = "Number of channels (1 - mono, 2 - stereo)"
|
.descr = "Number of channels (1 - mono, 2 - stereo)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "PATH",
|
.name = "PATH",
|
||||||
.tag = AUD_OPT_STR,
|
.tag = AUD_OPT_STR,
|
||||||
.valp = &glob_conf.wav_path,
|
.valp = &conf.wav_path,
|
||||||
.descr = "Path to wave file"
|
.descr = "Path to wave file"
|
||||||
},
|
},
|
||||||
{ /* End of list */ }
|
{ /* End of list */ }
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "qemu/osdep.h"
|
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
#include "monitor/monitor.h"
|
#include "monitor/monitor.h"
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -88,7 +86,6 @@ static void wav_capture_destroy (void *opaque)
|
|||||||
WAVState *wav = opaque;
|
WAVState *wav = opaque;
|
||||||
|
|
||||||
AUD_del_capture (wav->cap, wav);
|
AUD_del_capture (wav->cap, wav);
|
||||||
g_free (wav);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wav_capture_info (void *opaque)
|
static void wav_capture_info (void *opaque)
|
||||||
|
|||||||
717
audio/winwaveaudio.c
Normal file
717
audio/winwaveaudio.c
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
/* public domain */
|
||||||
|
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "sysemu/sysemu.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
#define AUDIO_CAP "winwave"
|
||||||
|
#include "audio_int.h"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <mmsystem.h>
|
||||||
|
|
||||||
|
#include "audio_win_int.h"
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int dac_headers;
|
||||||
|
int dac_samples;
|
||||||
|
int adc_headers;
|
||||||
|
int adc_samples;
|
||||||
|
} conf = {
|
||||||
|
.dac_headers = 4,
|
||||||
|
.dac_samples = 1024,
|
||||||
|
.adc_headers = 4,
|
||||||
|
.adc_samples = 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HWVoiceOut hw;
|
||||||
|
HWAVEOUT hwo;
|
||||||
|
WAVEHDR *hdrs;
|
||||||
|
HANDLE event;
|
||||||
|
void *pcm_buf;
|
||||||
|
int avail;
|
||||||
|
int pending;
|
||||||
|
int curhdr;
|
||||||
|
int paused;
|
||||||
|
CRITICAL_SECTION crit_sect;
|
||||||
|
} WaveVoiceOut;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HWVoiceIn hw;
|
||||||
|
HWAVEIN hwi;
|
||||||
|
WAVEHDR *hdrs;
|
||||||
|
HANDLE event;
|
||||||
|
void *pcm_buf;
|
||||||
|
int curhdr;
|
||||||
|
int paused;
|
||||||
|
int rpos;
|
||||||
|
int avail;
|
||||||
|
CRITICAL_SECTION crit_sect;
|
||||||
|
} WaveVoiceIn;
|
||||||
|
|
||||||
|
static void winwave_log_mmresult (MMRESULT mr)
|
||||||
|
{
|
||||||
|
const char *str = "BUG";
|
||||||
|
|
||||||
|
switch (mr) {
|
||||||
|
case MMSYSERR_NOERROR:
|
||||||
|
str = "Success";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MMSYSERR_INVALHANDLE:
|
||||||
|
str = "Specified device handle is invalid";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MMSYSERR_BADDEVICEID:
|
||||||
|
str = "Specified device id is out of range";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MMSYSERR_NODRIVER:
|
||||||
|
str = "No device driver is present";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MMSYSERR_NOMEM:
|
||||||
|
str = "Unable to allocate or lock memory";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAVERR_SYNC:
|
||||||
|
str = "Device is synchronous but waveOutOpen was called "
|
||||||
|
"without using the WINWAVE_ALLOWSYNC flag";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAVERR_UNPREPARED:
|
||||||
|
str = "The data block pointed to by the pwh parameter "
|
||||||
|
"hasn't been prepared";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WAVERR_STILLPLAYING:
|
||||||
|
str = "There are still buffers in the queue";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dolog ("Reason: Unknown (MMRESULT %#x)\n", mr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dolog ("Reason: %s\n", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GCC_FMT_ATTR (2, 3) winwave_logerr (
|
||||||
|
MMRESULT mr,
|
||||||
|
const char *fmt,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start (ap, fmt);
|
||||||
|
AUD_vlog (AUDIO_CAP, fmt, ap);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
AUD_log (NULL, " failed\n");
|
||||||
|
winwave_log_mmresult (mr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_anal_close_out (WaveVoiceOut *wave)
|
||||||
|
{
|
||||||
|
MMRESULT mr;
|
||||||
|
|
||||||
|
mr = waveOutClose (wave->hwo);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutClose");
|
||||||
|
}
|
||||||
|
wave->hwo = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CALLBACK winwave_callback_out (
|
||||||
|
HWAVEOUT hwo,
|
||||||
|
UINT msg,
|
||||||
|
DWORD_PTR dwInstance,
|
||||||
|
DWORD_PTR dwParam1,
|
||||||
|
DWORD_PTR dwParam2
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WaveVoiceOut *wave = (WaveVoiceOut *) dwInstance;
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
case WOM_DONE:
|
||||||
|
{
|
||||||
|
WAVEHDR *h = (WAVEHDR *) dwParam1;
|
||||||
|
if (!h->dwUser) {
|
||||||
|
h->dwUser = 1;
|
||||||
|
EnterCriticalSection (&wave->crit_sect);
|
||||||
|
{
|
||||||
|
wave->avail += conf.dac_samples;
|
||||||
|
}
|
||||||
|
LeaveCriticalSection (&wave->crit_sect);
|
||||||
|
if (wave->hw.poll_mode) {
|
||||||
|
if (!SetEvent (wave->event)) {
|
||||||
|
dolog ("DAC SetEvent failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WOM_CLOSE:
|
||||||
|
case WOM_OPEN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dolog ("unknown wave out callback msg %x\n", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_init_out (HWVoiceOut *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int err;
|
||||||
|
MMRESULT mr;
|
||||||
|
WAVEFORMATEX wfx;
|
||||||
|
WaveVoiceOut *wave;
|
||||||
|
|
||||||
|
wave = (WaveVoiceOut *) hw;
|
||||||
|
|
||||||
|
InitializeCriticalSection (&wave->crit_sect);
|
||||||
|
|
||||||
|
err = waveformat_from_audio_settings (&wfx, as);
|
||||||
|
if (err) {
|
||||||
|
goto err0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mr = waveOutOpen (&wave->hwo, WAVE_MAPPER, &wfx,
|
||||||
|
(DWORD_PTR) winwave_callback_out,
|
||||||
|
(DWORD_PTR) wave, CALLBACK_FUNCTION);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutOpen");
|
||||||
|
goto err1;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers,
|
||||||
|
sizeof (*wave->hdrs));
|
||||||
|
if (!wave->hdrs) {
|
||||||
|
goto err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_pcm_init_info (&hw->info, as);
|
||||||
|
hw->samples = conf.dac_samples * conf.dac_headers;
|
||||||
|
wave->avail = hw->samples;
|
||||||
|
|
||||||
|
wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.dac_samples,
|
||||||
|
conf.dac_headers << hw->info.shift);
|
||||||
|
if (!wave->pcm_buf) {
|
||||||
|
goto err3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < conf.dac_headers; ++i) {
|
||||||
|
WAVEHDR *h = &wave->hdrs[i];
|
||||||
|
|
||||||
|
h->dwUser = 0;
|
||||||
|
h->dwBufferLength = conf.dac_samples << hw->info.shift;
|
||||||
|
h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength);
|
||||||
|
h->dwFlags = 0;
|
||||||
|
|
||||||
|
mr = waveOutPrepareHeader (wave->hwo, h, sizeof (*h));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutPrepareHeader(%d)", i);
|
||||||
|
goto err4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err4:
|
||||||
|
g_free (wave->pcm_buf);
|
||||||
|
err3:
|
||||||
|
g_free (wave->hdrs);
|
||||||
|
err2:
|
||||||
|
winwave_anal_close_out (wave);
|
||||||
|
err1:
|
||||||
|
err0:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_write (SWVoiceOut *sw, void *buf, int len)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_write (sw, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_run_out (HWVoiceOut *hw, int live)
|
||||||
|
{
|
||||||
|
WaveVoiceOut *wave = (WaveVoiceOut *) hw;
|
||||||
|
int decr;
|
||||||
|
int doreset;
|
||||||
|
|
||||||
|
EnterCriticalSection (&wave->crit_sect);
|
||||||
|
{
|
||||||
|
decr = audio_MIN (live, wave->avail);
|
||||||
|
decr = audio_pcm_hw_clip_out (hw, wave->pcm_buf, decr, wave->pending);
|
||||||
|
wave->pending += decr;
|
||||||
|
wave->avail -= decr;
|
||||||
|
}
|
||||||
|
LeaveCriticalSection (&wave->crit_sect);
|
||||||
|
|
||||||
|
doreset = hw->poll_mode && (wave->pending >= conf.dac_samples);
|
||||||
|
if (doreset && !ResetEvent (wave->event)) {
|
||||||
|
dolog ("DAC ResetEvent failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
|
||||||
|
while (wave->pending >= conf.dac_samples) {
|
||||||
|
MMRESULT mr;
|
||||||
|
WAVEHDR *h = &wave->hdrs[wave->curhdr];
|
||||||
|
|
||||||
|
h->dwUser = 0;
|
||||||
|
mr = waveOutWrite (wave->hwo, h, sizeof (*h));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutWrite(%d)", wave->curhdr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave->pending -= conf.dac_samples;
|
||||||
|
wave->curhdr = (wave->curhdr + 1) % conf.dac_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_poll (void *opaque)
|
||||||
|
{
|
||||||
|
(void) opaque;
|
||||||
|
audio_run ("winwave_poll");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_fini_out (HWVoiceOut *hw)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
MMRESULT mr;
|
||||||
|
WaveVoiceOut *wave = (WaveVoiceOut *) hw;
|
||||||
|
|
||||||
|
mr = waveOutReset (wave->hwo);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutReset");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < conf.dac_headers; ++i) {
|
||||||
|
mr = waveOutUnprepareHeader (wave->hwo, &wave->hdrs[i],
|
||||||
|
sizeof (wave->hdrs[i]));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutUnprepareHeader(%d)", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winwave_anal_close_out (wave);
|
||||||
|
|
||||||
|
if (wave->event) {
|
||||||
|
qemu_del_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
if (!CloseHandle (wave->event)) {
|
||||||
|
dolog ("DAC CloseHandle failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
wave->event = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (wave->pcm_buf);
|
||||||
|
wave->pcm_buf = NULL;
|
||||||
|
|
||||||
|
g_free (wave->hdrs);
|
||||||
|
wave->hdrs = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_ctl_out (HWVoiceOut *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
MMRESULT mr;
|
||||||
|
WaveVoiceOut *wave = (WaveVoiceOut *) hw;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case VOICE_ENABLE:
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int poll_mode;
|
||||||
|
|
||||||
|
va_start (ap, cmd);
|
||||||
|
poll_mode = va_arg (ap, int);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
if (poll_mode && !wave->event) {
|
||||||
|
wave->event = CreateEvent (NULL, TRUE, TRUE, NULL);
|
||||||
|
if (!wave->event) {
|
||||||
|
dolog ("DAC CreateEvent: %lx, poll mode will be disabled\n",
|
||||||
|
GetLastError ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wave->event) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = qemu_add_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
hw->poll_mode = (ret == 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hw->poll_mode = 0;
|
||||||
|
}
|
||||||
|
wave->paused = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case VOICE_DISABLE:
|
||||||
|
if (!wave->paused) {
|
||||||
|
mr = waveOutReset (wave->hwo);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveOutReset");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave->paused = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wave->event) {
|
||||||
|
qemu_del_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_anal_close_in (WaveVoiceIn *wave)
|
||||||
|
{
|
||||||
|
MMRESULT mr;
|
||||||
|
|
||||||
|
mr = waveInClose (wave->hwi);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInClose");
|
||||||
|
}
|
||||||
|
wave->hwi = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CALLBACK winwave_callback_in (
|
||||||
|
HWAVEIN *hwi,
|
||||||
|
UINT msg,
|
||||||
|
DWORD_PTR dwInstance,
|
||||||
|
DWORD_PTR dwParam1,
|
||||||
|
DWORD_PTR dwParam2
|
||||||
|
)
|
||||||
|
{
|
||||||
|
WaveVoiceIn *wave = (WaveVoiceIn *) dwInstance;
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
case WIM_DATA:
|
||||||
|
{
|
||||||
|
WAVEHDR *h = (WAVEHDR *) dwParam1;
|
||||||
|
if (!h->dwUser) {
|
||||||
|
h->dwUser = 1;
|
||||||
|
EnterCriticalSection (&wave->crit_sect);
|
||||||
|
{
|
||||||
|
wave->avail += conf.adc_samples;
|
||||||
|
}
|
||||||
|
LeaveCriticalSection (&wave->crit_sect);
|
||||||
|
if (wave->hw.poll_mode) {
|
||||||
|
if (!SetEvent (wave->event)) {
|
||||||
|
dolog ("ADC SetEvent failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WIM_CLOSE:
|
||||||
|
case WIM_OPEN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dolog ("unknown wave in callback msg %x\n", msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_add_buffers (WaveVoiceIn *wave, int samples)
|
||||||
|
{
|
||||||
|
int doreset;
|
||||||
|
|
||||||
|
doreset = wave->hw.poll_mode && (samples >= conf.adc_samples);
|
||||||
|
if (doreset && !ResetEvent (wave->event)) {
|
||||||
|
dolog ("ADC ResetEvent failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
|
||||||
|
while (samples >= conf.adc_samples) {
|
||||||
|
MMRESULT mr;
|
||||||
|
WAVEHDR *h = &wave->hdrs[wave->curhdr];
|
||||||
|
|
||||||
|
h->dwUser = 0;
|
||||||
|
mr = waveInAddBuffer (wave->hwi, h, sizeof (*h));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInAddBuffer(%d)", wave->curhdr);
|
||||||
|
}
|
||||||
|
wave->curhdr = (wave->curhdr + 1) % conf.adc_headers;
|
||||||
|
samples -= conf.adc_samples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_init_in (HWVoiceIn *hw, struct audsettings *as)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int err;
|
||||||
|
MMRESULT mr;
|
||||||
|
WAVEFORMATEX wfx;
|
||||||
|
WaveVoiceIn *wave;
|
||||||
|
|
||||||
|
wave = (WaveVoiceIn *) hw;
|
||||||
|
|
||||||
|
InitializeCriticalSection (&wave->crit_sect);
|
||||||
|
|
||||||
|
err = waveformat_from_audio_settings (&wfx, as);
|
||||||
|
if (err) {
|
||||||
|
goto err0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mr = waveInOpen (&wave->hwi, WAVE_MAPPER, &wfx,
|
||||||
|
(DWORD_PTR) winwave_callback_in,
|
||||||
|
(DWORD_PTR) wave, CALLBACK_FUNCTION);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInOpen");
|
||||||
|
goto err1;
|
||||||
|
}
|
||||||
|
|
||||||
|
wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers,
|
||||||
|
sizeof (*wave->hdrs));
|
||||||
|
if (!wave->hdrs) {
|
||||||
|
goto err2;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_pcm_init_info (&hw->info, as);
|
||||||
|
hw->samples = conf.adc_samples * conf.adc_headers;
|
||||||
|
wave->avail = 0;
|
||||||
|
|
||||||
|
wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.adc_samples,
|
||||||
|
conf.adc_headers << hw->info.shift);
|
||||||
|
if (!wave->pcm_buf) {
|
||||||
|
goto err3;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < conf.adc_headers; ++i) {
|
||||||
|
WAVEHDR *h = &wave->hdrs[i];
|
||||||
|
|
||||||
|
h->dwUser = 0;
|
||||||
|
h->dwBufferLength = conf.adc_samples << hw->info.shift;
|
||||||
|
h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength);
|
||||||
|
h->dwFlags = 0;
|
||||||
|
|
||||||
|
mr = waveInPrepareHeader (wave->hwi, h, sizeof (*h));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInPrepareHeader(%d)", i);
|
||||||
|
goto err4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wave->paused = 1;
|
||||||
|
winwave_add_buffers (wave, hw->samples);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err4:
|
||||||
|
g_free (wave->pcm_buf);
|
||||||
|
err3:
|
||||||
|
g_free (wave->hdrs);
|
||||||
|
err2:
|
||||||
|
winwave_anal_close_in (wave);
|
||||||
|
err1:
|
||||||
|
err0:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_fini_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
MMRESULT mr;
|
||||||
|
WaveVoiceIn *wave = (WaveVoiceIn *) hw;
|
||||||
|
|
||||||
|
mr = waveInReset (wave->hwi);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInReset");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < conf.adc_headers; ++i) {
|
||||||
|
mr = waveInUnprepareHeader (wave->hwi, &wave->hdrs[i],
|
||||||
|
sizeof (wave->hdrs[i]));
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInUnprepareHeader(%d)", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winwave_anal_close_in (wave);
|
||||||
|
|
||||||
|
if (wave->event) {
|
||||||
|
qemu_del_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
if (!CloseHandle (wave->event)) {
|
||||||
|
dolog ("ADC CloseHandle failed %lx\n", GetLastError ());
|
||||||
|
}
|
||||||
|
wave->event = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (wave->pcm_buf);
|
||||||
|
wave->pcm_buf = NULL;
|
||||||
|
|
||||||
|
g_free (wave->hdrs);
|
||||||
|
wave->hdrs = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_run_in (HWVoiceIn *hw)
|
||||||
|
{
|
||||||
|
WaveVoiceIn *wave = (WaveVoiceIn *) hw;
|
||||||
|
int live = audio_pcm_hw_get_live_in (hw);
|
||||||
|
int dead = hw->samples - live;
|
||||||
|
int decr, ret;
|
||||||
|
|
||||||
|
if (!dead) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnterCriticalSection (&wave->crit_sect);
|
||||||
|
{
|
||||||
|
decr = audio_MIN (dead, wave->avail);
|
||||||
|
wave->avail -= decr;
|
||||||
|
}
|
||||||
|
LeaveCriticalSection (&wave->crit_sect);
|
||||||
|
|
||||||
|
ret = decr;
|
||||||
|
while (decr) {
|
||||||
|
int left = hw->samples - hw->wpos;
|
||||||
|
int conv = audio_MIN (left, decr);
|
||||||
|
hw->conv (hw->conv_buf + hw->wpos,
|
||||||
|
advance (wave->pcm_buf, wave->rpos << hw->info.shift),
|
||||||
|
conv);
|
||||||
|
|
||||||
|
wave->rpos = (wave->rpos + conv) % hw->samples;
|
||||||
|
hw->wpos = (hw->wpos + conv) % hw->samples;
|
||||||
|
decr -= conv;
|
||||||
|
}
|
||||||
|
|
||||||
|
winwave_add_buffers (wave, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_read (SWVoiceIn *sw, void *buf, int size)
|
||||||
|
{
|
||||||
|
return audio_pcm_sw_read (sw, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int winwave_ctl_in (HWVoiceIn *hw, int cmd, ...)
|
||||||
|
{
|
||||||
|
MMRESULT mr;
|
||||||
|
WaveVoiceIn *wave = (WaveVoiceIn *) hw;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case VOICE_ENABLE:
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int poll_mode;
|
||||||
|
|
||||||
|
va_start (ap, cmd);
|
||||||
|
poll_mode = va_arg (ap, int);
|
||||||
|
va_end (ap);
|
||||||
|
|
||||||
|
if (poll_mode && !wave->event) {
|
||||||
|
wave->event = CreateEvent (NULL, TRUE, TRUE, NULL);
|
||||||
|
if (!wave->event) {
|
||||||
|
dolog ("ADC CreateEvent: %lx, poll mode will be disabled\n",
|
||||||
|
GetLastError ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wave->event) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = qemu_add_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
hw->poll_mode = (ret == 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hw->poll_mode = 0;
|
||||||
|
}
|
||||||
|
if (wave->paused) {
|
||||||
|
mr = waveInStart (wave->hwi);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInStart");
|
||||||
|
}
|
||||||
|
wave->paused = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case VOICE_DISABLE:
|
||||||
|
if (!wave->paused) {
|
||||||
|
mr = waveInStop (wave->hwi);
|
||||||
|
if (mr != MMSYSERR_NOERROR) {
|
||||||
|
winwave_logerr (mr, "waveInStop");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave->paused = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wave->event) {
|
||||||
|
qemu_del_wait_object (wave->event, winwave_poll, wave);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *winwave_audio_init (void)
|
||||||
|
{
|
||||||
|
return &conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void winwave_audio_fini (void *opaque)
|
||||||
|
{
|
||||||
|
(void) opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct audio_option winwave_options[] = {
|
||||||
|
{
|
||||||
|
.name = "DAC_HEADERS",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.dac_headers,
|
||||||
|
.descr = "DAC number of headers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "DAC_SAMPLES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.dac_samples,
|
||||||
|
.descr = "DAC number of samples per header",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "ADC_HEADERS",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.adc_headers,
|
||||||
|
.descr = "ADC number of headers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "ADC_SAMPLES",
|
||||||
|
.tag = AUD_OPT_INT,
|
||||||
|
.valp = &conf.adc_samples,
|
||||||
|
.descr = "ADC number of samples per header",
|
||||||
|
},
|
||||||
|
{ /* End of list */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct audio_pcm_ops winwave_pcm_ops = {
|
||||||
|
.init_out = winwave_init_out,
|
||||||
|
.fini_out = winwave_fini_out,
|
||||||
|
.run_out = winwave_run_out,
|
||||||
|
.write = winwave_write,
|
||||||
|
.ctl_out = winwave_ctl_out,
|
||||||
|
.init_in = winwave_init_in,
|
||||||
|
.fini_in = winwave_fini_in,
|
||||||
|
.run_in = winwave_run_in,
|
||||||
|
.read = winwave_read,
|
||||||
|
.ctl_in = winwave_ctl_in
|
||||||
|
};
|
||||||
|
|
||||||
|
struct audio_driver winwave_audio_driver = {
|
||||||
|
.name = "winwave",
|
||||||
|
.descr = "Windows Waveform Audio http://msdn.microsoft.com",
|
||||||
|
.options = winwave_options,
|
||||||
|
.init = winwave_audio_init,
|
||||||
|
.fini = winwave_audio_fini,
|
||||||
|
.pcm_ops = &winwave_pcm_ops,
|
||||||
|
.can_be_default = 1,
|
||||||
|
.max_voices_out = INT_MAX,
|
||||||
|
.max_voices_in = INT_MAX,
|
||||||
|
.voice_size_out = sizeof (WaveVoiceOut),
|
||||||
|
.voice_size_in = sizeof (WaveVoiceIn)
|
||||||
|
};
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
common-obj-y += rng.o rng-egd.o
|
common-obj-y += rng.o rng-egd.o
|
||||||
common-obj-$(CONFIG_POSIX) += rng-random.o
|
common-obj-$(CONFIG_POSIX) += rng-random.o
|
||||||
|
|
||||||
|
common-obj-y += msmouse.o testdev.o
|
||||||
|
common-obj-$(CONFIG_BRLAPI) += baum.o
|
||||||
|
baum.o-cflags := $(SDL_CFLAGS)
|
||||||
|
|
||||||
common-obj-$(CONFIG_TPM) += tpm.o
|
common-obj-$(CONFIG_TPM) += tpm.o
|
||||||
|
|
||||||
common-obj-y += hostmem.o hostmem-ram.o
|
common-obj-y += hostmem.o hostmem-ram.o
|
||||||
common-obj-$(CONFIG_LINUX) += hostmem-file.o
|
common-obj-$(CONFIG_LINUX) += hostmem-file.o
|
||||||
|
|
||||||
common-obj-y += cryptodev.o
|
|
||||||
common-obj-y += cryptodev-builtin.o
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* QEMU Baum Braille Device
|
* QEMU Baum Braille Device
|
||||||
*
|
*
|
||||||
* Copyright (c) 2008, 2010-2011, 2016 Samuel Thibault
|
* Copyright (c) 2008 Samuel Thibault
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,16 +21,16 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "chardev/char.h"
|
#include "sysemu/char.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "hw/usb.h"
|
#include "hw/usb.h"
|
||||||
#include "ui/console.h"
|
|
||||||
#include <brlapi.h>
|
#include <brlapi.h>
|
||||||
#include <brlapi_constants.h>
|
#include <brlapi_constants.h>
|
||||||
#include <brlapi_keycodes.h>
|
#include <brlapi_keycodes.h>
|
||||||
|
#ifdef CONFIG_SDL
|
||||||
|
#include <SDL_syswm.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#define DPRINTF(fmt, ...) \
|
#define DPRINTF(fmt, ...) \
|
||||||
@@ -85,12 +85,11 @@
|
|||||||
#define BUF_SIZE 256
|
#define BUF_SIZE 256
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Chardev parent;
|
CharDriverState *chr;
|
||||||
|
|
||||||
brlapi_handle_t *brlapi;
|
brlapi_handle_t *brlapi;
|
||||||
int brlapi_fd;
|
int brlapi_fd;
|
||||||
unsigned int x, y;
|
unsigned int x, y;
|
||||||
bool deferred_init;
|
|
||||||
|
|
||||||
uint8_t in_buf[BUF_SIZE];
|
uint8_t in_buf[BUF_SIZE];
|
||||||
uint8_t in_buf_used;
|
uint8_t in_buf_used;
|
||||||
@@ -98,17 +97,11 @@ typedef struct {
|
|||||||
uint8_t out_buf_used, out_buf_ptr;
|
uint8_t out_buf_used, out_buf_ptr;
|
||||||
|
|
||||||
QEMUTimer *cellCount_timer;
|
QEMUTimer *cellCount_timer;
|
||||||
} BaumChardev;
|
} BaumDriverState;
|
||||||
|
|
||||||
#define TYPE_CHARDEV_BRAILLE "chardev-braille"
|
|
||||||
#define BAUM_CHARDEV(obj) OBJECT_CHECK(BaumChardev, (obj), TYPE_CHARDEV_BRAILLE)
|
|
||||||
|
|
||||||
/* Let's assume NABCC by default */
|
/* Let's assume NABCC by default */
|
||||||
enum way {
|
static const uint8_t nabcc_translation[256] = {
|
||||||
DOTS2ASCII,
|
[0] = ' ',
|
||||||
ASCII2DOTS
|
|
||||||
};
|
|
||||||
static const uint8_t nabcc_translation[2][256] = {
|
|
||||||
#ifndef BRLAPI_DOTS
|
#ifndef BRLAPI_DOTS
|
||||||
#define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \
|
#define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \
|
||||||
((d1?BRLAPI_DOT1:0)|\
|
((d1?BRLAPI_DOT1:0)|\
|
||||||
@@ -120,145 +113,111 @@ static const uint8_t nabcc_translation[2][256] = {
|
|||||||
(d7?BRLAPI_DOT7:0)|\
|
(d7?BRLAPI_DOT7:0)|\
|
||||||
(d8?BRLAPI_DOT8:0))
|
(d8?BRLAPI_DOT8:0))
|
||||||
#endif
|
#endif
|
||||||
#define DO(dots, ascii) \
|
[BRLAPI_DOTS(1,0,0,0,0,0,0,0)] = 'a',
|
||||||
[DOTS2ASCII][dots] = ascii, \
|
[BRLAPI_DOTS(1,1,0,0,0,0,0,0)] = 'b',
|
||||||
[ASCII2DOTS][ascii] = dots
|
[BRLAPI_DOTS(1,0,0,1,0,0,0,0)] = 'c',
|
||||||
DO(0, ' '),
|
[BRLAPI_DOTS(1,0,0,1,1,0,0,0)] = 'd',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 0, 0), 'a'),
|
[BRLAPI_DOTS(1,0,0,0,1,0,0,0)] = 'e',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 0, 0), 'b'),
|
[BRLAPI_DOTS(1,1,0,1,0,0,0,0)] = 'f',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 0, 0), 'c'),
|
[BRLAPI_DOTS(1,1,0,1,1,0,0,0)] = 'g',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 0, 0), 'd'),
|
[BRLAPI_DOTS(1,1,0,0,1,0,0,0)] = 'h',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 0, 0), 'e'),
|
[BRLAPI_DOTS(0,1,0,1,0,0,0,0)] = 'i',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 0, 0), 'f'),
|
[BRLAPI_DOTS(0,1,0,1,1,0,0,0)] = 'j',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 0, 0), 'g'),
|
[BRLAPI_DOTS(1,0,1,0,0,0,0,0)] = 'k',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 0, 0), 'h'),
|
[BRLAPI_DOTS(1,1,1,0,0,0,0,0)] = 'l',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 0, 0), 'i'),
|
[BRLAPI_DOTS(1,0,1,1,0,0,0,0)] = 'm',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 0, 0), 'j'),
|
[BRLAPI_DOTS(1,0,1,1,1,0,0,0)] = 'n',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 0, 0), 'k'),
|
[BRLAPI_DOTS(1,0,1,0,1,0,0,0)] = 'o',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 0, 0), 'l'),
|
[BRLAPI_DOTS(1,1,1,1,0,0,0,0)] = 'p',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 0, 0), 'm'),
|
[BRLAPI_DOTS(1,1,1,1,1,0,0,0)] = 'q',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 0, 0), 'n'),
|
[BRLAPI_DOTS(1,1,1,0,1,0,0,0)] = 'r',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 0, 0), 'o'),
|
[BRLAPI_DOTS(0,1,1,1,0,0,0,0)] = 's',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 0, 0), 'p'),
|
[BRLAPI_DOTS(0,1,1,1,1,0,0,0)] = 't',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 0, 0), 'q'),
|
[BRLAPI_DOTS(1,0,1,0,0,1,0,0)] = 'u',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 0, 0), 'r'),
|
[BRLAPI_DOTS(1,1,1,0,0,1,0,0)] = 'v',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 0, 0), 's'),
|
[BRLAPI_DOTS(0,1,0,1,1,1,0,0)] = 'w',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 0, 0), 't'),
|
[BRLAPI_DOTS(1,0,1,1,0,1,0,0)] = 'x',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 0, 0), 'u'),
|
[BRLAPI_DOTS(1,0,1,1,1,1,0,0)] = 'y',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 0, 0), 'v'),
|
[BRLAPI_DOTS(1,0,1,0,1,1,0,0)] = 'z',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 0, 0), 'w'),
|
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 0, 0), 'x'),
|
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 0, 0), 'y'),
|
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 0, 0), 'z'),
|
|
||||||
|
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 1, 0), 'A'),
|
[BRLAPI_DOTS(1,0,0,0,0,0,1,0)] = 'A',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 1, 0), 'B'),
|
[BRLAPI_DOTS(1,1,0,0,0,0,1,0)] = 'B',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 1, 0), 'C'),
|
[BRLAPI_DOTS(1,0,0,1,0,0,1,0)] = 'C',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 1, 0), 'D'),
|
[BRLAPI_DOTS(1,0,0,1,1,0,1,0)] = 'D',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 1, 0), 'E'),
|
[BRLAPI_DOTS(1,0,0,0,1,0,1,0)] = 'E',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 1, 0), 'F'),
|
[BRLAPI_DOTS(1,1,0,1,0,0,1,0)] = 'F',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 1, 0), 'G'),
|
[BRLAPI_DOTS(1,1,0,1,1,0,1,0)] = 'G',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 1, 0), 'H'),
|
[BRLAPI_DOTS(1,1,0,0,1,0,1,0)] = 'H',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 1, 0), 'I'),
|
[BRLAPI_DOTS(0,1,0,1,0,0,1,0)] = 'I',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 1, 0), 'J'),
|
[BRLAPI_DOTS(0,1,0,1,1,0,1,0)] = 'J',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 1, 0), 'K'),
|
[BRLAPI_DOTS(1,0,1,0,0,0,1,0)] = 'K',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 1, 0), 'L'),
|
[BRLAPI_DOTS(1,1,1,0,0,0,1,0)] = 'L',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 1, 0), 'M'),
|
[BRLAPI_DOTS(1,0,1,1,0,0,1,0)] = 'M',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 1, 0), 'N'),
|
[BRLAPI_DOTS(1,0,1,1,1,0,1,0)] = 'N',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 1, 0), 'O'),
|
[BRLAPI_DOTS(1,0,1,0,1,0,1,0)] = 'O',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 1, 0), 'P'),
|
[BRLAPI_DOTS(1,1,1,1,0,0,1,0)] = 'P',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 1, 0), 'Q'),
|
[BRLAPI_DOTS(1,1,1,1,1,0,1,0)] = 'Q',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 1, 0), 'R'),
|
[BRLAPI_DOTS(1,1,1,0,1,0,1,0)] = 'R',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 1, 0), 'S'),
|
[BRLAPI_DOTS(0,1,1,1,0,0,1,0)] = 'S',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 1, 0), 'T'),
|
[BRLAPI_DOTS(0,1,1,1,1,0,1,0)] = 'T',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 1, 0), 'U'),
|
[BRLAPI_DOTS(1,0,1,0,0,1,1,0)] = 'U',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 1, 0), 'V'),
|
[BRLAPI_DOTS(1,1,1,0,0,1,1,0)] = 'V',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 1, 0), 'W'),
|
[BRLAPI_DOTS(0,1,0,1,1,1,1,0)] = 'W',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 1, 0), 'X'),
|
[BRLAPI_DOTS(1,0,1,1,0,1,1,0)] = 'X',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 1, 0), 'Y'),
|
[BRLAPI_DOTS(1,0,1,1,1,1,1,0)] = 'Y',
|
||||||
DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 1, 0), 'Z'),
|
[BRLAPI_DOTS(1,0,1,0,1,1,1,0)] = 'Z',
|
||||||
|
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 1, 0, 0), '0'),
|
[BRLAPI_DOTS(0,0,1,0,1,1,0,0)] = '0',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 0, 0, 0), '1'),
|
[BRLAPI_DOTS(0,1,0,0,0,0,0,0)] = '1',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 0, 0, 0), '2'),
|
[BRLAPI_DOTS(0,1,1,0,0,0,0,0)] = '2',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 0, 0, 0), '3'),
|
[BRLAPI_DOTS(0,1,0,0,1,0,0,0)] = '3',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 1, 0, 0), '4'),
|
[BRLAPI_DOTS(0,1,0,0,1,1,0,0)] = '4',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 1, 0, 0), '5'),
|
[BRLAPI_DOTS(0,1,0,0,0,1,0,0)] = '5',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 0, 0, 0), '6'),
|
[BRLAPI_DOTS(0,1,1,0,1,0,0,0)] = '6',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 1, 0, 0), '7'),
|
[BRLAPI_DOTS(0,1,1,0,1,1,0,0)] = '7',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 1, 0, 0), '8'),
|
[BRLAPI_DOTS(0,1,1,0,0,1,0,0)] = '8',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 0, 0, 0), '9'),
|
[BRLAPI_DOTS(0,0,1,0,1,0,0,0)] = '9',
|
||||||
|
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 1, 0, 0), '.'),
|
[BRLAPI_DOTS(0,0,0,1,0,1,0,0)] = '.',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 1, 0, 0), '+'),
|
[BRLAPI_DOTS(0,0,1,1,0,1,0,0)] = '+',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 1, 0, 0), '-'),
|
[BRLAPI_DOTS(0,0,1,0,0,1,0,0)] = '-',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 1, 0, 0), '*'),
|
[BRLAPI_DOTS(1,0,0,0,0,1,0,0)] = '*',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 0, 0, 0), '/'),
|
[BRLAPI_DOTS(0,0,1,1,0,0,0,0)] = '/',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 1, 0, 0), '('),
|
[BRLAPI_DOTS(1,1,1,0,1,1,0,0)] = '(',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 1, 0, 0), ')'),
|
[BRLAPI_DOTS(0,1,1,1,1,1,0,0)] = ')',
|
||||||
|
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 1, 0, 0), '&'),
|
[BRLAPI_DOTS(1,1,1,1,0,1,0,0)] = '&',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 1, 0, 0), '#'),
|
[BRLAPI_DOTS(0,0,1,1,1,1,0,0)] = '#',
|
||||||
|
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 0, 1, 0, 0), ','),
|
[BRLAPI_DOTS(0,0,0,0,0,1,0,0)] = ',',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 1, 0, 0), ';'),
|
[BRLAPI_DOTS(0,0,0,0,1,1,0,0)] = ';',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 1, 0, 0), ':'),
|
[BRLAPI_DOTS(1,0,0,0,1,1,0,0)] = ':',
|
||||||
DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 1, 0, 0), '!'),
|
[BRLAPI_DOTS(0,1,1,1,0,1,0,0)] = '!',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 1, 0, 0), '?'),
|
[BRLAPI_DOTS(1,0,0,1,1,1,0,0)] = '?',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 0, 0, 0), '"'),
|
[BRLAPI_DOTS(0,0,0,0,1,0,0,0)] = '"',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 0, 0, 0), '\''),
|
[BRLAPI_DOTS(0,0,1,0,0,0,0,0)] ='\'',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 0, 0), '`'),
|
[BRLAPI_DOTS(0,0,0,1,0,0,0,0)] = '`',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 1, 0), '^'),
|
[BRLAPI_DOTS(0,0,0,1,1,0,1,0)] = '^',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 0, 0), '~'),
|
[BRLAPI_DOTS(0,0,0,1,1,0,0,0)] = '~',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 1, 0), '['),
|
[BRLAPI_DOTS(0,1,0,1,0,1,1,0)] = '[',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 1, 0), ']'),
|
[BRLAPI_DOTS(1,1,0,1,1,1,1,0)] = ']',
|
||||||
DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 0, 0), '{'),
|
[BRLAPI_DOTS(0,1,0,1,0,1,0,0)] = '{',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 0, 0), '}'),
|
[BRLAPI_DOTS(1,1,0,1,1,1,0,0)] = '}',
|
||||||
DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 1, 0, 0), '='),
|
[BRLAPI_DOTS(1,1,1,1,1,1,0,0)] = '=',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 1, 0, 0), '<'),
|
[BRLAPI_DOTS(1,1,0,0,0,1,0,0)] = '<',
|
||||||
DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 0, 0, 0), '>'),
|
[BRLAPI_DOTS(0,0,1,1,1,0,0,0)] = '>',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 1, 0, 0), '$'),
|
[BRLAPI_DOTS(1,1,0,1,0,1,0,0)] = '$',
|
||||||
DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 1, 0, 0), '%'),
|
[BRLAPI_DOTS(1,0,0,1,0,1,0,0)] = '%',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 1, 0), '@'),
|
[BRLAPI_DOTS(0,0,0,1,0,0,1,0)] = '@',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 0, 0), '|'),
|
[BRLAPI_DOTS(1,1,0,0,1,1,0,0)] = '|',
|
||||||
DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 1, 0), '\\'),
|
[BRLAPI_DOTS(1,1,0,0,1,1,1,0)] ='\\',
|
||||||
DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 1, 0, 0), '_'),
|
[BRLAPI_DOTS(0,0,0,1,1,1,0,0)] = '_',
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The guest OS has started discussing with us, finish initializing BrlAPI */
|
|
||||||
static int baum_deferred_init(BaumChardev *baum)
|
|
||||||
{
|
|
||||||
int tty = BRLAPI_TTY_DEFAULT;
|
|
||||||
QemuConsole *con;
|
|
||||||
|
|
||||||
if (baum->deferred_init) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (brlapi__getDisplaySize(baum->brlapi, &baum->x, &baum->y) == -1) {
|
|
||||||
brlapi_perror("baum: brlapi__getDisplaySize");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
con = qemu_console_lookup_by_index(0);
|
|
||||||
if (con && qemu_console_is_graphic(con)) {
|
|
||||||
tty = qemu_console_get_window_id(con);
|
|
||||||
if (tty == -1)
|
|
||||||
tty = BRLAPI_TTY_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (brlapi__enterTtyMode(baum->brlapi, tty, NULL) == -1) {
|
|
||||||
brlapi_perror("baum: brlapi__enterTtyMode");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
baum->deferred_init = 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The serial port can receive more of our data */
|
/* The serial port can receive more of our data */
|
||||||
static void baum_chr_accept_input(struct Chardev *chr)
|
static void baum_accept_input(struct CharDriverState *chr)
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
BaumDriverState *baum = chr->opaque;
|
||||||
int room, first;
|
int room, first;
|
||||||
|
|
||||||
if (!baum->out_buf_used)
|
if (!baum->out_buf_used)
|
||||||
@@ -282,25 +241,24 @@ static void baum_chr_accept_input(struct Chardev *chr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* We want to send a packet */
|
/* We want to send a packet */
|
||||||
static void baum_write_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
static void baum_write_packet(BaumDriverState *baum, const uint8_t *buf, int len)
|
||||||
{
|
{
|
||||||
Chardev *chr = CHARDEV(baum);
|
|
||||||
uint8_t io_buf[1 + 2 * len], *cur = io_buf;
|
uint8_t io_buf[1 + 2 * len], *cur = io_buf;
|
||||||
int room;
|
int room;
|
||||||
*cur++ = ESC;
|
*cur++ = ESC;
|
||||||
while (len--)
|
while (len--)
|
||||||
if ((*cur++ = *buf++) == ESC)
|
if ((*cur++ = *buf++) == ESC)
|
||||||
*cur++ = ESC;
|
*cur++ = ESC;
|
||||||
room = qemu_chr_be_can_write(chr);
|
room = qemu_chr_be_can_write(baum->chr);
|
||||||
len = cur - io_buf;
|
len = cur - io_buf;
|
||||||
if (len <= room) {
|
if (len <= room) {
|
||||||
/* Fits */
|
/* Fits */
|
||||||
qemu_chr_be_write(chr, io_buf, len);
|
qemu_chr_be_write(baum->chr, io_buf, len);
|
||||||
} else {
|
} else {
|
||||||
int first;
|
int first;
|
||||||
uint8_t out;
|
uint8_t out;
|
||||||
/* Can't fit all, send what can be, and store the rest. */
|
/* Can't fit all, send what can be, and store the rest. */
|
||||||
qemu_chr_be_write(chr, io_buf, room);
|
qemu_chr_be_write(baum->chr, io_buf, room);
|
||||||
len -= room;
|
len -= room;
|
||||||
cur = io_buf + room;
|
cur = io_buf + room;
|
||||||
if (len > BUF_SIZE - baum->out_buf_used) {
|
if (len > BUF_SIZE - baum->out_buf_used) {
|
||||||
@@ -325,14 +283,14 @@ static void baum_write_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
|||||||
/* Called when the other end seems to have a wrong idea of our display size */
|
/* Called when the other end seems to have a wrong idea of our display size */
|
||||||
static void baum_cellCount_timer_cb(void *opaque)
|
static void baum_cellCount_timer_cb(void *opaque)
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(opaque);
|
BaumDriverState *baum = opaque;
|
||||||
uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y };
|
uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y };
|
||||||
DPRINTF("Timeout waiting for DisplayData, sending cell count\n");
|
DPRINTF("Timeout waiting for DisplayData, sending cell count\n");
|
||||||
baum_write_packet(baum, cell_count, sizeof(cell_count));
|
baum_write_packet(baum, cell_count, sizeof(cell_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try to interpret a whole incoming packet */
|
/* Try to interpret a whole incoming packet */
|
||||||
static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
static int baum_eat_packet(BaumDriverState *baum, const uint8_t *buf, int len)
|
||||||
{
|
{
|
||||||
const uint8_t *cur = buf;
|
const uint8_t *cur = buf;
|
||||||
uint8_t req = 0;
|
uint8_t req = 0;
|
||||||
@@ -345,7 +303,7 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
|||||||
return 0;
|
return 0;
|
||||||
cur++;
|
cur++;
|
||||||
}
|
}
|
||||||
DPRINTF("Dropped %td bytes!\n", cur - buf);
|
DPRINTF("Dropped %d bytes!\n", cur - buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define EAT(c) do {\
|
#define EAT(c) do {\
|
||||||
@@ -377,7 +335,7 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
|||||||
|
|
||||||
/* Allow 100ms to complete the DisplayData packet */
|
/* Allow 100ms to complete the DisplayData packet */
|
||||||
timer_mod(baum->cellCount_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
timer_mod(baum->cellCount_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
|
||||||
NANOSECONDS_PER_SECOND / 10);
|
get_ticks_per_sec() / 10);
|
||||||
for (i = 0; i < baum->x * baum->y ; i++) {
|
for (i = 0; i < baum->x * baum->y ; i++) {
|
||||||
EAT(c);
|
EAT(c);
|
||||||
cells[i] = c;
|
cells[i] = c;
|
||||||
@@ -386,10 +344,8 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
|||||||
cursor = i + 1;
|
cursor = i + 1;
|
||||||
c &= ~(BRLAPI_DOT7|BRLAPI_DOT8);
|
c &= ~(BRLAPI_DOT7|BRLAPI_DOT8);
|
||||||
}
|
}
|
||||||
c = nabcc_translation[DOTS2ASCII][c];
|
if (!(c = nabcc_translation[c]))
|
||||||
if (!c) {
|
|
||||||
c = '?';
|
c = '?';
|
||||||
}
|
|
||||||
text[i] = c;
|
text[i] = c;
|
||||||
}
|
}
|
||||||
timer_del(baum->cellCount_timer);
|
timer_del(baum->cellCount_timer);
|
||||||
@@ -473,17 +429,15 @@ static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The other end is writing some data. Store it and try to interpret */
|
/* The other end is writing some data. Store it and try to interpret */
|
||||||
static int baum_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
static int baum_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
BaumDriverState *baum = chr->opaque;
|
||||||
int tocopy, cur, eaten, orig_len = len;
|
int tocopy, cur, eaten, orig_len = len;
|
||||||
|
|
||||||
if (!len)
|
if (!len)
|
||||||
return 0;
|
return 0;
|
||||||
if (!baum->brlapi)
|
if (!baum->brlapi)
|
||||||
return len;
|
return len;
|
||||||
if (!baum_deferred_init(baum))
|
|
||||||
return len;
|
|
||||||
|
|
||||||
while (len) {
|
while (len) {
|
||||||
/* Complete our buffer as much as possible */
|
/* Complete our buffer as much as possible */
|
||||||
@@ -514,31 +468,20 @@ static int baum_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Send the key code to the other end */
|
/* Send the key code to the other end */
|
||||||
static void baum_send_key(BaumChardev *baum, uint8_t type, uint8_t value)
|
static void baum_send_key(BaumDriverState *baum, uint8_t type, uint8_t value) {
|
||||||
{
|
|
||||||
uint8_t packet[] = { type, value };
|
uint8_t packet[] = { type, value };
|
||||||
DPRINTF("writing key %x %x\n", type, value);
|
DPRINTF("writing key %x %x\n", type, value);
|
||||||
baum_write_packet(baum, packet, sizeof(packet));
|
baum_write_packet(baum, packet, sizeof(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void baum_send_key2(BaumChardev *baum, uint8_t type, uint8_t value,
|
|
||||||
uint8_t value2)
|
|
||||||
{
|
|
||||||
uint8_t packet[] = { type, value, value2 };
|
|
||||||
DPRINTF("writing key %x %x\n", type, value);
|
|
||||||
baum_write_packet(baum, packet, sizeof(packet));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We got some data on the BrlAPI socket */
|
/* We got some data on the BrlAPI socket */
|
||||||
static void baum_chr_read(void *opaque)
|
static void baum_chr_read(void *opaque)
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(opaque);
|
BaumDriverState *baum = opaque;
|
||||||
brlapi_keyCode_t code;
|
brlapi_keyCode_t code;
|
||||||
int ret;
|
int ret;
|
||||||
if (!baum->brlapi)
|
if (!baum->brlapi)
|
||||||
return;
|
return;
|
||||||
if (!baum_deferred_init(baum))
|
|
||||||
return;
|
|
||||||
while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) {
|
while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) {
|
||||||
DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code);
|
DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code);
|
||||||
/* Emulate */
|
/* Emulate */
|
||||||
@@ -595,19 +538,9 @@ static void baum_chr_read(void *opaque)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BRLAPI_KEY_TYPE_SYM:
|
case BRLAPI_KEY_TYPE_SYM:
|
||||||
{
|
|
||||||
brlapi_keyCode_t keysym = code & BRLAPI_KEY_CODE_MASK;
|
|
||||||
if (keysym < 0x100) {
|
|
||||||
uint8_t dots = nabcc_translation[ASCII2DOTS][keysym];
|
|
||||||
if (dots) {
|
|
||||||
baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, dots);
|
|
||||||
baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) {
|
if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) {
|
||||||
brlapi_perror("baum: brlapi_readKey");
|
brlapi_perror("baum: brlapi_readKey");
|
||||||
brlapi__closeConnection(baum->brlapi);
|
brlapi__closeConnection(baum->brlapi);
|
||||||
@@ -616,62 +549,87 @@ static void baum_chr_read(void *opaque)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void char_braille_finalize(Object *obj)
|
static void baum_close(struct CharDriverState *chr)
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(obj);
|
BaumDriverState *baum = chr->opaque;
|
||||||
|
|
||||||
timer_free(baum->cellCount_timer);
|
timer_free(baum->cellCount_timer);
|
||||||
if (baum->brlapi) {
|
if (baum->brlapi) {
|
||||||
brlapi__closeConnection(baum->brlapi);
|
brlapi__closeConnection(baum->brlapi);
|
||||||
g_free(baum->brlapi);
|
g_free(baum->brlapi);
|
||||||
}
|
}
|
||||||
|
g_free(baum);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void baum_chr_open(Chardev *chr,
|
CharDriverState *chr_baum_init(void)
|
||||||
ChardevBackend *backend,
|
|
||||||
bool *be_opened,
|
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
BaumChardev *baum = BAUM_CHARDEV(chr);
|
BaumDriverState *baum;
|
||||||
|
CharDriverState *chr;
|
||||||
brlapi_handle_t *handle;
|
brlapi_handle_t *handle;
|
||||||
|
#if defined(CONFIG_SDL)
|
||||||
|
#if SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 0)
|
||||||
|
SDL_SysWMinfo info;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
int tty;
|
||||||
|
|
||||||
|
baum = g_malloc0(sizeof(BaumDriverState));
|
||||||
|
baum->chr = chr = qemu_chr_alloc();
|
||||||
|
|
||||||
|
chr->opaque = baum;
|
||||||
|
chr->chr_write = baum_write;
|
||||||
|
chr->chr_accept_input = baum_accept_input;
|
||||||
|
chr->chr_close = baum_close;
|
||||||
|
|
||||||
handle = g_malloc0(brlapi_getHandleSize());
|
handle = g_malloc0(brlapi_getHandleSize());
|
||||||
baum->brlapi = handle;
|
baum->brlapi = handle;
|
||||||
|
|
||||||
baum->brlapi_fd = brlapi__openConnection(handle, NULL, NULL);
|
baum->brlapi_fd = brlapi__openConnection(handle, NULL, NULL);
|
||||||
if (baum->brlapi_fd == -1) {
|
if (baum->brlapi_fd == -1) {
|
||||||
error_setg(errp, "brlapi__openConnection: %s",
|
brlapi_perror("baum_init: brlapi_openConnection");
|
||||||
brlapi_strerror(brlapi_error_location()));
|
goto fail_handle;
|
||||||
g_free(handle);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
baum->deferred_init = 0;
|
|
||||||
|
|
||||||
baum->cellCount_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, baum_cellCount_timer_cb, baum);
|
baum->cellCount_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, baum_cellCount_timer_cb, baum);
|
||||||
|
|
||||||
|
if (brlapi__getDisplaySize(handle, &baum->x, &baum->y) == -1) {
|
||||||
|
brlapi_perror("baum_init: brlapi_getDisplaySize");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_SDL)
|
||||||
|
#if SDL_COMPILEDVERSION < SDL_VERSIONNUM(2, 0, 0)
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
SDL_VERSION(&info.version);
|
||||||
|
if (SDL_GetWMInfo(&info))
|
||||||
|
tty = info.info.x11.wmwindow;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
tty = BRLAPI_TTY_DEFAULT;
|
||||||
|
|
||||||
|
if (brlapi__enterTtyMode(handle, tty, NULL) == -1) {
|
||||||
|
brlapi_perror("baum_init: brlapi_enterTtyMode");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum);
|
qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum);
|
||||||
|
|
||||||
|
return chr;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
timer_free(baum->cellCount_timer);
|
||||||
|
brlapi__closeConnection(handle);
|
||||||
|
fail_handle:
|
||||||
|
g_free(handle);
|
||||||
|
g_free(chr);
|
||||||
|
g_free(baum);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void char_braille_class_init(ObjectClass *oc, void *data)
|
|
||||||
{
|
|
||||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
|
||||||
|
|
||||||
cc->open = baum_chr_open;
|
|
||||||
cc->chr_write = baum_chr_write;
|
|
||||||
cc->chr_accept_input = baum_chr_accept_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TypeInfo char_braille_type_info = {
|
|
||||||
.name = TYPE_CHARDEV_BRAILLE,
|
|
||||||
.parent = TYPE_CHARDEV,
|
|
||||||
.instance_size = sizeof(BaumChardev),
|
|
||||||
.instance_finalize = char_braille_finalize,
|
|
||||||
.class_init = char_braille_class_init,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void register_types(void)
|
static void register_types(void)
|
||||||
{
|
{
|
||||||
type_register_static(&char_braille_type_info);
|
register_char_driver("braille", CHARDEV_BACKEND_KIND_BRAILLE, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
type_init(register_types);
|
type_init(register_types);
|
||||||
@@ -1,400 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU Cryptodev backend for QEMU cipher APIs
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Gonglei <arei.gonglei@huawei.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/cryptodev.h"
|
|
||||||
#include "hw/boards.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "standard-headers/linux/virtio_crypto.h"
|
|
||||||
#include "crypto/cipher.h"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @TYPE_CRYPTODEV_BACKEND_BUILTIN:
|
|
||||||
* name of backend that uses QEMU cipher API
|
|
||||||
*/
|
|
||||||
#define TYPE_CRYPTODEV_BACKEND_BUILTIN "cryptodev-backend-builtin"
|
|
||||||
|
|
||||||
#define CRYPTODEV_BACKEND_BUILTIN(obj) \
|
|
||||||
OBJECT_CHECK(CryptoDevBackendBuiltin, \
|
|
||||||
(obj), TYPE_CRYPTODEV_BACKEND_BUILTIN)
|
|
||||||
|
|
||||||
typedef struct CryptoDevBackendBuiltin
|
|
||||||
CryptoDevBackendBuiltin;
|
|
||||||
|
|
||||||
typedef struct CryptoDevBackendBuiltinSession {
|
|
||||||
QCryptoCipher *cipher;
|
|
||||||
uint8_t direction; /* encryption or decryption */
|
|
||||||
uint8_t type; /* cipher? hash? aead? */
|
|
||||||
QTAILQ_ENTRY(CryptoDevBackendBuiltinSession) next;
|
|
||||||
} CryptoDevBackendBuiltinSession;
|
|
||||||
|
|
||||||
/* Max number of symmetric sessions */
|
|
||||||
#define MAX_NUM_SESSIONS 256
|
|
||||||
|
|
||||||
#define CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN 512
|
|
||||||
#define CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN 64
|
|
||||||
|
|
||||||
struct CryptoDevBackendBuiltin {
|
|
||||||
CryptoDevBackend parent_obj;
|
|
||||||
|
|
||||||
CryptoDevBackendBuiltinSession *sessions[MAX_NUM_SESSIONS];
|
|
||||||
};
|
|
||||||
|
|
||||||
static void cryptodev_builtin_init(
|
|
||||||
CryptoDevBackend *backend, Error **errp)
|
|
||||||
{
|
|
||||||
/* Only support one queue */
|
|
||||||
int queues = backend->conf.peers.queues;
|
|
||||||
CryptoDevBackendClient *cc;
|
|
||||||
|
|
||||||
if (queues != 1) {
|
|
||||||
error_setg(errp,
|
|
||||||
"Only support one queue in cryptdov-builtin backend");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cc = cryptodev_backend_new_client(
|
|
||||||
"cryptodev-builtin", NULL);
|
|
||||||
cc->info_str = g_strdup_printf("cryptodev-builtin0");
|
|
||||||
cc->queue_index = 0;
|
|
||||||
backend->conf.peers.ccs[0] = cc;
|
|
||||||
|
|
||||||
backend->conf.crypto_services =
|
|
||||||
1u << VIRTIO_CRYPTO_SERVICE_CIPHER |
|
|
||||||
1u << VIRTIO_CRYPTO_SERVICE_HASH |
|
|
||||||
1u << VIRTIO_CRYPTO_SERVICE_MAC;
|
|
||||||
backend->conf.cipher_algo_l = 1u << VIRTIO_CRYPTO_CIPHER_AES_CBC;
|
|
||||||
backend->conf.hash_algo = 1u << VIRTIO_CRYPTO_HASH_SHA1;
|
|
||||||
/*
|
|
||||||
* Set the Maximum length of crypto request.
|
|
||||||
* Why this value? Just avoid to overflow when
|
|
||||||
* memory allocation for each crypto request.
|
|
||||||
*/
|
|
||||||
backend->conf.max_size = LONG_MAX - sizeof(CryptoDevBackendSymOpInfo);
|
|
||||||
backend->conf.max_cipher_key_len = CRYPTODEV_BUITLIN_MAX_CIPHER_KEY_LEN;
|
|
||||||
backend->conf.max_auth_key_len = CRYPTODEV_BUITLIN_MAX_AUTH_KEY_LEN;
|
|
||||||
|
|
||||||
cryptodev_backend_set_ready(backend, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
cryptodev_builtin_get_unused_session_index(
|
|
||||||
CryptoDevBackendBuiltin *builtin)
|
|
||||||
{
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NUM_SESSIONS; i++) {
|
|
||||||
if (builtin->sessions[i] == NULL) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define AES_KEYSIZE_128 16
|
|
||||||
#define AES_KEYSIZE_192 24
|
|
||||||
#define AES_KEYSIZE_256 32
|
|
||||||
#define AES_KEYSIZE_128_XTS AES_KEYSIZE_256
|
|
||||||
#define AES_KEYSIZE_256_XTS 64
|
|
||||||
|
|
||||||
static int
|
|
||||||
cryptodev_builtin_get_aes_algo(uint32_t key_len, int mode, Error **errp)
|
|
||||||
{
|
|
||||||
int algo;
|
|
||||||
|
|
||||||
if (key_len == AES_KEYSIZE_128) {
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_AES_128;
|
|
||||||
} else if (key_len == AES_KEYSIZE_192) {
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_AES_192;
|
|
||||||
} else if (key_len == AES_KEYSIZE_256) { /* equals AES_KEYSIZE_128_XTS */
|
|
||||||
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_AES_128;
|
|
||||||
} else {
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_AES_256;
|
|
||||||
}
|
|
||||||
} else if (key_len == AES_KEYSIZE_256_XTS) {
|
|
||||||
if (mode == QCRYPTO_CIPHER_MODE_XTS) {
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_AES_256;
|
|
||||||
} else {
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return algo;
|
|
||||||
|
|
||||||
err:
|
|
||||||
error_setg(errp, "Unsupported key length :%u", key_len);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cryptodev_builtin_create_cipher_session(
|
|
||||||
CryptoDevBackendBuiltin *builtin,
|
|
||||||
CryptoDevBackendSymSessionInfo *sess_info,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int algo;
|
|
||||||
int mode;
|
|
||||||
QCryptoCipher *cipher;
|
|
||||||
int index;
|
|
||||||
CryptoDevBackendBuiltinSession *sess;
|
|
||||||
|
|
||||||
if (sess_info->op_type != VIRTIO_CRYPTO_SYM_OP_CIPHER) {
|
|
||||||
error_setg(errp, "Unsupported optype :%u", sess_info->op_type);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = cryptodev_builtin_get_unused_session_index(builtin);
|
|
||||||
if (index < 0) {
|
|
||||||
error_setg(errp, "Total number of sessions created exceeds %u",
|
|
||||||
MAX_NUM_SESSIONS);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (sess_info->cipher_alg) {
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_AES_ECB:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_ECB;
|
|
||||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
|
||||||
mode, errp);
|
|
||||||
if (algo < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_AES_CBC:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_CBC;
|
|
||||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
|
||||||
mode, errp);
|
|
||||||
if (algo < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_AES_CTR:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_CTR;
|
|
||||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
|
||||||
mode, errp);
|
|
||||||
if (algo < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_AES_XTS:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_XTS;
|
|
||||||
algo = cryptodev_builtin_get_aes_algo(sess_info->key_len,
|
|
||||||
mode, errp);
|
|
||||||
if (algo < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_3DES_ECB:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_ECB;
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_3DES_CBC:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_CBC;
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_3DES_CTR:
|
|
||||||
mode = QCRYPTO_CIPHER_MODE_CTR;
|
|
||||||
algo = QCRYPTO_CIPHER_ALG_3DES;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
error_setg(errp, "Unsupported cipher alg :%u",
|
|
||||||
sess_info->cipher_alg);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cipher = qcrypto_cipher_new(algo, mode,
|
|
||||||
sess_info->cipher_key,
|
|
||||||
sess_info->key_len,
|
|
||||||
errp);
|
|
||||||
if (!cipher) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sess = g_new0(CryptoDevBackendBuiltinSession, 1);
|
|
||||||
sess->cipher = cipher;
|
|
||||||
sess->direction = sess_info->direction;
|
|
||||||
sess->type = sess_info->op_type;
|
|
||||||
|
|
||||||
builtin->sessions[index] = sess;
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t cryptodev_builtin_sym_create_session(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
CryptoDevBackendSymSessionInfo *sess_info,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendBuiltin *builtin =
|
|
||||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
|
||||||
int64_t session_id = -1;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
switch (sess_info->op_code) {
|
|
||||||
case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION:
|
|
||||||
ret = cryptodev_builtin_create_cipher_session(
|
|
||||||
builtin, sess_info, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
} else {
|
|
||||||
session_id = ret;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case VIRTIO_CRYPTO_HASH_CREATE_SESSION:
|
|
||||||
case VIRTIO_CRYPTO_MAC_CREATE_SESSION:
|
|
||||||
default:
|
|
||||||
error_setg(errp, "Unsupported opcode :%" PRIu32 "",
|
|
||||||
sess_info->op_code);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return session_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cryptodev_builtin_sym_close_session(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
uint64_t session_id,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendBuiltin *builtin =
|
|
||||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
|
||||||
|
|
||||||
if (session_id >= MAX_NUM_SESSIONS ||
|
|
||||||
builtin->sessions[session_id] == NULL) {
|
|
||||||
error_setg(errp, "Cannot find a valid session id: %" PRIu64 "",
|
|
||||||
session_id);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
qcrypto_cipher_free(builtin->sessions[session_id]->cipher);
|
|
||||||
g_free(builtin->sessions[session_id]);
|
|
||||||
builtin->sessions[session_id] = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cryptodev_builtin_sym_operation(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
CryptoDevBackendSymOpInfo *op_info,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendBuiltin *builtin =
|
|
||||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
|
||||||
CryptoDevBackendBuiltinSession *sess;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (op_info->session_id >= MAX_NUM_SESSIONS ||
|
|
||||||
builtin->sessions[op_info->session_id] == NULL) {
|
|
||||||
error_setg(errp, "Cannot find a valid session id: %" PRIu64 "",
|
|
||||||
op_info->session_id);
|
|
||||||
return -VIRTIO_CRYPTO_INVSESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op_info->op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) {
|
|
||||||
error_setg(errp,
|
|
||||||
"Algorithm chain is unsupported for cryptdoev-builtin");
|
|
||||||
return -VIRTIO_CRYPTO_NOTSUPP;
|
|
||||||
}
|
|
||||||
|
|
||||||
sess = builtin->sessions[op_info->session_id];
|
|
||||||
|
|
||||||
if (op_info->iv_len > 0) {
|
|
||||||
ret = qcrypto_cipher_setiv(sess->cipher, op_info->iv,
|
|
||||||
op_info->iv_len, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return -VIRTIO_CRYPTO_ERR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sess->direction == VIRTIO_CRYPTO_OP_ENCRYPT) {
|
|
||||||
ret = qcrypto_cipher_encrypt(sess->cipher, op_info->src,
|
|
||||||
op_info->dst, op_info->src_len, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return -VIRTIO_CRYPTO_ERR;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = qcrypto_cipher_decrypt(sess->cipher, op_info->src,
|
|
||||||
op_info->dst, op_info->src_len, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return -VIRTIO_CRYPTO_ERR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return VIRTIO_CRYPTO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cryptodev_builtin_cleanup(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendBuiltin *builtin =
|
|
||||||
CRYPTODEV_BACKEND_BUILTIN(backend);
|
|
||||||
size_t i;
|
|
||||||
int queues = backend->conf.peers.queues;
|
|
||||||
CryptoDevBackendClient *cc;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NUM_SESSIONS; i++) {
|
|
||||||
if (builtin->sessions[i] != NULL) {
|
|
||||||
cryptodev_builtin_sym_close_session(
|
|
||||||
backend, i, 0, errp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < queues; i++) {
|
|
||||||
cc = backend->conf.peers.ccs[i];
|
|
||||||
if (cc) {
|
|
||||||
cryptodev_backend_free_client(cc);
|
|
||||||
backend->conf.peers.ccs[i] = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptodev_backend_set_ready(backend, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_builtin_class_init(ObjectClass *oc, void *data)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClass *bc = CRYPTODEV_BACKEND_CLASS(oc);
|
|
||||||
|
|
||||||
bc->init = cryptodev_builtin_init;
|
|
||||||
bc->cleanup = cryptodev_builtin_cleanup;
|
|
||||||
bc->create_session = cryptodev_builtin_sym_create_session;
|
|
||||||
bc->close_session = cryptodev_builtin_sym_close_session;
|
|
||||||
bc->do_sym_op = cryptodev_builtin_sym_operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TypeInfo cryptodev_builtin_info = {
|
|
||||||
.name = TYPE_CRYPTODEV_BACKEND_BUILTIN,
|
|
||||||
.parent = TYPE_CRYPTODEV_BACKEND,
|
|
||||||
.class_init = cryptodev_builtin_class_init,
|
|
||||||
.instance_size = sizeof(CryptoDevBackendBuiltin),
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_builtin_register_types(void)
|
|
||||||
{
|
|
||||||
type_register_static(&cryptodev_builtin_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_init(cryptodev_builtin_register_types);
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU Crypto Device Implementation
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Gonglei <arei.gonglei@huawei.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/cryptodev.h"
|
|
||||||
#include "hw/boards.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/visitor.h"
|
|
||||||
#include "qapi-types.h"
|
|
||||||
#include "qapi-visit.h"
|
|
||||||
#include "qemu/config-file.h"
|
|
||||||
#include "qom/object_interfaces.h"
|
|
||||||
#include "hw/virtio/virtio-crypto.h"
|
|
||||||
|
|
||||||
|
|
||||||
static QTAILQ_HEAD(, CryptoDevBackendClient) crypto_clients;
|
|
||||||
|
|
||||||
|
|
||||||
CryptoDevBackendClient *
|
|
||||||
cryptodev_backend_new_client(const char *model,
|
|
||||||
const char *name)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClient *cc;
|
|
||||||
|
|
||||||
cc = g_malloc0(sizeof(CryptoDevBackendClient));
|
|
||||||
cc->model = g_strdup(model);
|
|
||||||
if (name) {
|
|
||||||
cc->name = g_strdup(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTAILQ_INSERT_TAIL(&crypto_clients, cc, next);
|
|
||||||
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cryptodev_backend_free_client(
|
|
||||||
CryptoDevBackendClient *cc)
|
|
||||||
{
|
|
||||||
QTAILQ_REMOVE(&crypto_clients, cc, next);
|
|
||||||
g_free(cc->name);
|
|
||||||
g_free(cc->model);
|
|
||||||
g_free(cc->info_str);
|
|
||||||
g_free(cc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cryptodev_backend_cleanup(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClass *bc =
|
|
||||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
|
||||||
|
|
||||||
if (bc->cleanup) {
|
|
||||||
bc->cleanup(backend, errp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t cryptodev_backend_sym_create_session(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
CryptoDevBackendSymSessionInfo *sess_info,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClass *bc =
|
|
||||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
|
||||||
|
|
||||||
if (bc->create_session) {
|
|
||||||
return bc->create_session(backend, sess_info, queue_index, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cryptodev_backend_sym_close_session(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
uint64_t session_id,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClass *bc =
|
|
||||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
|
||||||
|
|
||||||
if (bc->close_session) {
|
|
||||||
return bc->close_session(backend, session_id, queue_index, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cryptodev_backend_sym_operation(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
CryptoDevBackendSymOpInfo *op_info,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackendClass *bc =
|
|
||||||
CRYPTODEV_BACKEND_GET_CLASS(backend);
|
|
||||||
|
|
||||||
if (bc->do_sym_op) {
|
|
||||||
return bc->do_sym_op(backend, op_info, queue_index, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return -VIRTIO_CRYPTO_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cryptodev_backend_crypto_operation(
|
|
||||||
CryptoDevBackend *backend,
|
|
||||||
void *opaque,
|
|
||||||
uint32_t queue_index, Error **errp)
|
|
||||||
{
|
|
||||||
VirtIOCryptoReq *req = opaque;
|
|
||||||
|
|
||||||
if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {
|
|
||||||
CryptoDevBackendSymOpInfo *op_info;
|
|
||||||
op_info = req->u.sym_op_info;
|
|
||||||
|
|
||||||
return cryptodev_backend_sym_operation(backend,
|
|
||||||
op_info, queue_index, errp);
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "Unsupported cryptodev alg type: %" PRIu32 "",
|
|
||||||
req->flags);
|
|
||||||
return -VIRTIO_CRYPTO_NOTSUPP;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -VIRTIO_CRYPTO_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_backend_get_queues(Object *obj, Visitor *v, const char *name,
|
|
||||||
void *opaque, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
|
||||||
uint32_t value = backend->conf.peers.queues;
|
|
||||||
|
|
||||||
visit_type_uint32(v, name, &value, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_backend_set_queues(Object *obj, Visitor *v, const char *name,
|
|
||||||
void *opaque, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
|
||||||
Error *local_err = NULL;
|
|
||||||
uint32_t value;
|
|
||||||
|
|
||||||
visit_type_uint32(v, name, &value, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (!value) {
|
|
||||||
error_setg(&local_err, "Property '%s.%s' doesn't take value '%"
|
|
||||||
PRIu32 "'", object_get_typename(obj), name, value);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
backend->conf.peers.queues = value;
|
|
||||||
out:
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_backend_complete(UserCreatable *uc, Error **errp)
|
|
||||||
{
|
|
||||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(uc);
|
|
||||||
CryptoDevBackendClass *bc = CRYPTODEV_BACKEND_GET_CLASS(uc);
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
if (bc->init) {
|
|
||||||
bc->init(backend, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
out:
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cryptodev_backend_set_used(CryptoDevBackend *backend, bool used)
|
|
||||||
{
|
|
||||||
backend->is_used = used;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cryptodev_backend_is_used(CryptoDevBackend *backend)
|
|
||||||
{
|
|
||||||
return backend->is_used;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cryptodev_backend_set_ready(CryptoDevBackend *backend, bool ready)
|
|
||||||
{
|
|
||||||
backend->ready = ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cryptodev_backend_is_ready(CryptoDevBackend *backend)
|
|
||||||
{
|
|
||||||
return backend->ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
cryptodev_backend_can_be_deleted(UserCreatable *uc, Error **errp)
|
|
||||||
{
|
|
||||||
return !cryptodev_backend_is_used(CRYPTODEV_BACKEND(uc));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cryptodev_backend_instance_init(Object *obj)
|
|
||||||
{
|
|
||||||
object_property_add(obj, "queues", "uint32",
|
|
||||||
cryptodev_backend_get_queues,
|
|
||||||
cryptodev_backend_set_queues,
|
|
||||||
NULL, NULL, NULL);
|
|
||||||
/* Initialize devices' queues property to 1 */
|
|
||||||
object_property_set_int(obj, 1, "queues", NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cryptodev_backend_finalize(Object *obj)
|
|
||||||
{
|
|
||||||
CryptoDevBackend *backend = CRYPTODEV_BACKEND(obj);
|
|
||||||
|
|
||||||
cryptodev_backend_cleanup(backend, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_backend_class_init(ObjectClass *oc, void *data)
|
|
||||||
{
|
|
||||||
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
|
||||||
|
|
||||||
ucc->complete = cryptodev_backend_complete;
|
|
||||||
ucc->can_be_deleted = cryptodev_backend_can_be_deleted;
|
|
||||||
|
|
||||||
QTAILQ_INIT(&crypto_clients);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TypeInfo cryptodev_backend_info = {
|
|
||||||
.name = TYPE_CRYPTODEV_BACKEND,
|
|
||||||
.parent = TYPE_OBJECT,
|
|
||||||
.instance_size = sizeof(CryptoDevBackend),
|
|
||||||
.instance_init = cryptodev_backend_instance_init,
|
|
||||||
.instance_finalize = cryptodev_backend_finalize,
|
|
||||||
.class_size = sizeof(CryptoDevBackendClass),
|
|
||||||
.class_init = cryptodev_backend_class_init,
|
|
||||||
.interfaces = (InterfaceInfo[]) {
|
|
||||||
{ TYPE_USER_CREATABLE },
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
cryptodev_backend_register_types(void)
|
|
||||||
{
|
|
||||||
type_register_static(&cryptodev_backend_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
type_init(cryptodev_backend_register_types);
|
|
||||||
@@ -9,8 +9,6 @@
|
|||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "sysemu/hostmem.h"
|
#include "sysemu/hostmem.h"
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
@@ -45,25 +43,30 @@ file_backend_memory_alloc(HostMemoryBackend *backend, Error **errp)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!fb->mem_path) {
|
if (!fb->mem_path) {
|
||||||
error_setg(errp, "mem-path property not set");
|
error_setg(errp, "mem_path property not set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifndef CONFIG_LINUX
|
#ifndef CONFIG_LINUX
|
||||||
error_setg(errp, "-mem-path not supported on this host");
|
error_setg(errp, "-mem-path not supported on this host");
|
||||||
#else
|
#else
|
||||||
if (!host_memory_backend_mr_inited(backend)) {
|
if (!memory_region_size(&backend->mr)) {
|
||||||
gchar *path;
|
|
||||||
backend->force_prealloc = mem_prealloc;
|
backend->force_prealloc = mem_prealloc;
|
||||||
path = object_get_canonical_path(OBJECT(backend));
|
|
||||||
memory_region_init_ram_from_file(&backend->mr, OBJECT(backend),
|
memory_region_init_ram_from_file(&backend->mr, OBJECT(backend),
|
||||||
path,
|
object_get_canonical_path(OBJECT(backend)),
|
||||||
backend->size, fb->share,
|
backend->size, fb->share,
|
||||||
fb->mem_path, errp);
|
fb->mem_path, errp);
|
||||||
g_free(path);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
file_backend_class_init(ObjectClass *oc, void *data)
|
||||||
|
{
|
||||||
|
HostMemoryBackendClass *bc = MEMORY_BACKEND_CLASS(oc);
|
||||||
|
|
||||||
|
bc->alloc = file_backend_memory_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
static char *get_mem_path(Object *o, Error **errp)
|
static char *get_mem_path(Object *o, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||||
@@ -76,11 +79,13 @@ static void set_mem_path(Object *o, const char *str, Error **errp)
|
|||||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||||
|
|
||||||
if (host_memory_backend_mr_inited(backend)) {
|
if (memory_region_size(&backend->mr)) {
|
||||||
error_setg(errp, "cannot change property value");
|
error_setg(errp, "cannot change property value");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (fb->mem_path) {
|
||||||
g_free(fb->mem_path);
|
g_free(fb->mem_path);
|
||||||
|
}
|
||||||
fb->mem_path = g_strdup(str);
|
fb->mem_path = g_strdup(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +101,7 @@ static void file_memory_backend_set_share(Object *o, bool value, Error **errp)
|
|||||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
||||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
||||||
|
|
||||||
if (host_memory_backend_mr_inited(backend)) {
|
if (memory_region_size(&backend->mr)) {
|
||||||
error_setg(errp, "cannot change property value");
|
error_setg(errp, "cannot change property value");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -104,32 +109,20 @@ static void file_memory_backend_set_share(Object *o, bool value, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
file_backend_class_init(ObjectClass *oc, void *data)
|
file_backend_instance_init(Object *o)
|
||||||
{
|
{
|
||||||
HostMemoryBackendClass *bc = MEMORY_BACKEND_CLASS(oc);
|
object_property_add_bool(o, "share",
|
||||||
|
file_memory_backend_get_share,
|
||||||
bc->alloc = file_backend_memory_alloc;
|
file_memory_backend_set_share, NULL);
|
||||||
|
object_property_add_str(o, "mem-path", get_mem_path,
|
||||||
object_class_property_add_bool(oc, "share",
|
set_mem_path, NULL);
|
||||||
file_memory_backend_get_share, file_memory_backend_set_share,
|
|
||||||
&error_abort);
|
|
||||||
object_class_property_add_str(oc, "mem-path",
|
|
||||||
get_mem_path, set_mem_path,
|
|
||||||
&error_abort);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void file_backend_instance_finalize(Object *o)
|
|
||||||
{
|
|
||||||
HostMemoryBackendFile *fb = MEMORY_BACKEND_FILE(o);
|
|
||||||
|
|
||||||
g_free(fb->mem_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const TypeInfo file_backend_info = {
|
static const TypeInfo file_backend_info = {
|
||||||
.name = TYPE_MEMORY_BACKEND_FILE,
|
.name = TYPE_MEMORY_BACKEND_FILE,
|
||||||
.parent = TYPE_MEMORY_BACKEND,
|
.parent = TYPE_MEMORY_BACKEND,
|
||||||
.class_init = file_backend_class_init,
|
.class_init = file_backend_class_init,
|
||||||
.instance_finalize = file_backend_instance_finalize,
|
.instance_init = file_backend_instance_init,
|
||||||
.instance_size = sizeof(HostMemoryBackendFile),
|
.instance_size = sizeof(HostMemoryBackendFile),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/hostmem.h"
|
#include "sysemu/hostmem.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qom/object_interfaces.h"
|
#include "qom/object_interfaces.h"
|
||||||
|
|
||||||
#define TYPE_MEMORY_BACKEND_RAM "memory-backend-ram"
|
#define TYPE_MEMORY_BACKEND_RAM "memory-backend-ram"
|
||||||
|
|||||||
@@ -9,13 +9,11 @@
|
|||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/hostmem.h"
|
#include "sysemu/hostmem.h"
|
||||||
#include "hw/boards.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/visitor.h"
|
#include "qapi/visitor.h"
|
||||||
#include "qapi-types.h"
|
#include "qapi-types.h"
|
||||||
#include "qapi-visit.h"
|
#include "qapi-visit.h"
|
||||||
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "qom/object_interfaces.h"
|
#include "qom/object_interfaces.h"
|
||||||
|
|
||||||
@@ -28,29 +26,29 @@ QEMU_BUILD_BUG_ON(HOST_MEM_POLICY_INTERLEAVE != MPOL_INTERLEAVE);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_get_size(Object *obj, Visitor *v, const char *name,
|
host_memory_backend_get_size(Object *obj, Visitor *v, void *opaque,
|
||||||
void *opaque, Error **errp)
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
uint64_t value = backend->size;
|
uint64_t value = backend->size;
|
||||||
|
|
||||||
visit_type_size(v, name, &value, errp);
|
visit_type_size(v, &value, name, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_set_size(Object *obj, Visitor *v, const char *name,
|
host_memory_backend_set_size(Object *obj, Visitor *v, void *opaque,
|
||||||
void *opaque, Error **errp)
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
uint64_t value;
|
uint64_t value;
|
||||||
|
|
||||||
if (host_memory_backend_mr_inited(backend)) {
|
if (memory_region_size(&backend->mr)) {
|
||||||
error_setg(&local_err, "cannot change property value");
|
error_setg(&local_err, "cannot change property value");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
visit_type_size(v, name, &value, &local_err);
|
visit_type_size(v, &value, name, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -65,8 +63,8 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_get_host_nodes(Object *obj, Visitor *v, const char *name,
|
host_memory_backend_get_host_nodes(Object *obj, Visitor *v, void *opaque,
|
||||||
void *opaque, Error **errp)
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
uint16List *host_nodes = NULL;
|
uint16List *host_nodes = NULL;
|
||||||
@@ -93,18 +91,18 @@ host_memory_backend_get_host_nodes(Object *obj, Visitor *v, const char *name,
|
|||||||
node = &(*node)->next;
|
node = &(*node)->next;
|
||||||
} while (true);
|
} while (true);
|
||||||
|
|
||||||
visit_type_uint16List(v, name, &host_nodes, errp);
|
visit_type_uint16List(v, &host_nodes, name, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_set_host_nodes(Object *obj, Visitor *v, const char *name,
|
host_memory_backend_set_host_nodes(Object *obj, Visitor *v, void *opaque,
|
||||||
void *opaque, Error **errp)
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_NUMA
|
#ifdef CONFIG_NUMA
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
uint16List *l = NULL;
|
uint16List *l = NULL;
|
||||||
|
|
||||||
visit_type_uint16List(v, name, &l, errp);
|
visit_type_uint16List(v, &l, name, errp);
|
||||||
|
|
||||||
while (l) {
|
while (l) {
|
||||||
bitmap_set(backend->host_nodes, l->value, 1);
|
bitmap_set(backend->host_nodes, l->value, 1);
|
||||||
@@ -115,17 +113,24 @@ host_memory_backend_set_host_nodes(Object *obj, Visitor *v, const char *name,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static void
|
||||||
host_memory_backend_get_policy(Object *obj, Error **errp G_GNUC_UNUSED)
|
host_memory_backend_get_policy(Object *obj, Visitor *v, void *opaque,
|
||||||
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
return backend->policy;
|
int policy = backend->policy;
|
||||||
|
|
||||||
|
visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_set_policy(Object *obj, int policy, Error **errp)
|
host_memory_backend_set_policy(Object *obj, Visitor *v, void *opaque,
|
||||||
|
const char *name, Error **errp)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
|
int policy;
|
||||||
|
|
||||||
|
visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
|
||||||
backend->policy = policy;
|
backend->policy = policy;
|
||||||
|
|
||||||
#ifndef CONFIG_NUMA
|
#ifndef CONFIG_NUMA
|
||||||
@@ -146,7 +151,7 @@ static void host_memory_backend_set_merge(Object *obj, bool value, Error **errp)
|
|||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
|
|
||||||
if (!host_memory_backend_mr_inited(backend)) {
|
if (!memory_region_size(&backend->mr)) {
|
||||||
backend->merge = value;
|
backend->merge = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -172,7 +177,7 @@ static void host_memory_backend_set_dump(Object *obj, bool value, Error **errp)
|
|||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
|
|
||||||
if (!host_memory_backend_mr_inited(backend)) {
|
if (!memory_region_size(&backend->mr)) {
|
||||||
backend->dump = value;
|
backend->dump = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -197,7 +202,6 @@ static bool host_memory_backend_get_prealloc(Object *obj, Error **errp)
|
|||||||
static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
Error *local_err = NULL;
|
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
|
|
||||||
if (backend->force_prealloc) {
|
if (backend->force_prealloc) {
|
||||||
@@ -208,7 +212,7 @@ static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!host_memory_backend_mr_inited(backend)) {
|
if (!memory_region_size(&backend->mr)) {
|
||||||
backend->prealloc = value;
|
backend->prealloc = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,11 +222,7 @@ static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
|||||||
void *ptr = memory_region_get_ram_ptr(&backend->mr);
|
void *ptr = memory_region_get_ram_ptr(&backend->mr);
|
||||||
uint64_t sz = memory_region_size(&backend->mr);
|
uint64_t sz = memory_region_size(&backend->mr);
|
||||||
|
|
||||||
os_mem_prealloc(fd, ptr, sz, smp_cpus, &local_err);
|
os_mem_prealloc(fd, ptr, sz);
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
backend->prealloc = true;
|
backend->prealloc = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,36 +230,37 @@ static void host_memory_backend_set_prealloc(Object *obj, bool value,
|
|||||||
static void host_memory_backend_init(Object *obj)
|
static void host_memory_backend_init(Object *obj)
|
||||||
{
|
{
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
HostMemoryBackend *backend = MEMORY_BACKEND(obj);
|
||||||
MachineState *machine = MACHINE(qdev_get_machine());
|
|
||||||
|
|
||||||
backend->merge = machine_mem_merge(machine);
|
backend->merge = qemu_opt_get_bool(qemu_get_machine_opts(),
|
||||||
backend->dump = machine_dump_guest_core(machine);
|
"mem-merge", true);
|
||||||
|
backend->dump = qemu_opt_get_bool(qemu_get_machine_opts(),
|
||||||
|
"dump-guest-core", true);
|
||||||
backend->prealloc = mem_prealloc;
|
backend->prealloc = mem_prealloc;
|
||||||
}
|
|
||||||
|
|
||||||
bool host_memory_backend_mr_inited(HostMemoryBackend *backend)
|
object_property_add_bool(obj, "merge",
|
||||||
{
|
host_memory_backend_get_merge,
|
||||||
/*
|
host_memory_backend_set_merge, NULL);
|
||||||
* NOTE: We forbid zero-length memory backend, so here zero means
|
object_property_add_bool(obj, "dump",
|
||||||
* "we haven't inited the backend memory region yet".
|
host_memory_backend_get_dump,
|
||||||
*/
|
host_memory_backend_set_dump, NULL);
|
||||||
return memory_region_size(&backend->mr) != 0;
|
object_property_add_bool(obj, "prealloc",
|
||||||
|
host_memory_backend_get_prealloc,
|
||||||
|
host_memory_backend_set_prealloc, NULL);
|
||||||
|
object_property_add(obj, "size", "int",
|
||||||
|
host_memory_backend_get_size,
|
||||||
|
host_memory_backend_set_size, NULL, NULL, NULL);
|
||||||
|
object_property_add(obj, "host-nodes", "int",
|
||||||
|
host_memory_backend_get_host_nodes,
|
||||||
|
host_memory_backend_set_host_nodes, NULL, NULL, NULL);
|
||||||
|
object_property_add(obj, "policy", "str",
|
||||||
|
host_memory_backend_get_policy,
|
||||||
|
host_memory_backend_set_policy, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryRegion *
|
MemoryRegion *
|
||||||
host_memory_backend_get_memory(HostMemoryBackend *backend, Error **errp)
|
host_memory_backend_get_memory(HostMemoryBackend *backend, Error **errp)
|
||||||
{
|
{
|
||||||
return host_memory_backend_mr_inited(backend) ? &backend->mr : NULL;
|
return memory_region_size(&backend->mr) ? &backend->mr : NULL;
|
||||||
}
|
|
||||||
|
|
||||||
void host_memory_backend_set_mapped(HostMemoryBackend *backend, bool mapped)
|
|
||||||
{
|
|
||||||
backend->is_mapped = mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool host_memory_backend_is_mapped(HostMemoryBackend *backend)
|
|
||||||
{
|
|
||||||
return backend->is_mapped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -274,7 +275,8 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp)
|
|||||||
if (bc->alloc) {
|
if (bc->alloc) {
|
||||||
bc->alloc(backend, &local_err);
|
bc->alloc(backend, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
goto out;
|
error_propagate(errp, local_err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = memory_region_get_ram_ptr(&backend->mr);
|
ptr = memory_region_get_ram_ptr(&backend->mr);
|
||||||
@@ -318,57 +320,34 @@ host_memory_backend_memory_complete(UserCreatable *uc, Error **errp)
|
|||||||
assert(maxnode <= MAX_NODES);
|
assert(maxnode <= MAX_NODES);
|
||||||
if (mbind(ptr, sz, backend->policy,
|
if (mbind(ptr, sz, backend->policy,
|
||||||
maxnode ? backend->host_nodes : NULL, maxnode + 1, flags)) {
|
maxnode ? backend->host_nodes : NULL, maxnode + 1, flags)) {
|
||||||
if (backend->policy != MPOL_DEFAULT || errno != ENOSYS) {
|
|
||||||
error_setg_errno(errp, errno,
|
error_setg_errno(errp, errno,
|
||||||
"cannot bind memory to host NUMA nodes");
|
"cannot bind memory to host NUMA nodes");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
/* Preallocate memory after the NUMA policy has been instantiated.
|
/* Preallocate memory after the NUMA policy has been instantiated.
|
||||||
* This is necessary to guarantee memory is allocated with
|
* This is necessary to guarantee memory is allocated with
|
||||||
* specified NUMA policy in place.
|
* specified NUMA policy in place.
|
||||||
*/
|
*/
|
||||||
if (backend->prealloc) {
|
if (backend->prealloc) {
|
||||||
os_mem_prealloc(memory_region_get_fd(&backend->mr), ptr, sz,
|
os_mem_prealloc(memory_region_get_fd(&backend->mr), ptr, sz);
|
||||||
smp_cpus, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
out:
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
host_memory_backend_can_be_deleted(UserCreatable *uc, Error **errp)
|
host_memory_backend_can_be_deleted(UserCreatable *uc, Error **errp)
|
||||||
{
|
{
|
||||||
if (host_memory_backend_is_mapped(MEMORY_BACKEND(uc))) {
|
MemoryRegion *mr;
|
||||||
|
|
||||||
|
mr = host_memory_backend_get_memory(MEMORY_BACKEND(uc), errp);
|
||||||
|
if (memory_region_is_mapped(mr)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *get_id(Object *o, Error **errp)
|
|
||||||
{
|
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
|
||||||
|
|
||||||
return g_strdup(backend->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_id(Object *o, const char *str, Error **errp)
|
|
||||||
{
|
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
|
||||||
|
|
||||||
if (backend->id) {
|
|
||||||
error_setg(errp, "cannot change property value");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
backend->id = g_strdup(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
host_memory_backend_class_init(ObjectClass *oc, void *data)
|
host_memory_backend_class_init(ObjectClass *oc, void *data)
|
||||||
{
|
{
|
||||||
@@ -376,35 +355,6 @@ host_memory_backend_class_init(ObjectClass *oc, void *data)
|
|||||||
|
|
||||||
ucc->complete = host_memory_backend_memory_complete;
|
ucc->complete = host_memory_backend_memory_complete;
|
||||||
ucc->can_be_deleted = host_memory_backend_can_be_deleted;
|
ucc->can_be_deleted = host_memory_backend_can_be_deleted;
|
||||||
|
|
||||||
object_class_property_add_bool(oc, "merge",
|
|
||||||
host_memory_backend_get_merge,
|
|
||||||
host_memory_backend_set_merge, &error_abort);
|
|
||||||
object_class_property_add_bool(oc, "dump",
|
|
||||||
host_memory_backend_get_dump,
|
|
||||||
host_memory_backend_set_dump, &error_abort);
|
|
||||||
object_class_property_add_bool(oc, "prealloc",
|
|
||||||
host_memory_backend_get_prealloc,
|
|
||||||
host_memory_backend_set_prealloc, &error_abort);
|
|
||||||
object_class_property_add(oc, "size", "int",
|
|
||||||
host_memory_backend_get_size,
|
|
||||||
host_memory_backend_set_size,
|
|
||||||
NULL, NULL, &error_abort);
|
|
||||||
object_class_property_add(oc, "host-nodes", "int",
|
|
||||||
host_memory_backend_get_host_nodes,
|
|
||||||
host_memory_backend_set_host_nodes,
|
|
||||||
NULL, NULL, &error_abort);
|
|
||||||
object_class_property_add_enum(oc, "policy", "HostMemPolicy",
|
|
||||||
HostMemPolicy_lookup,
|
|
||||||
host_memory_backend_get_policy,
|
|
||||||
host_memory_backend_set_policy, &error_abort);
|
|
||||||
object_class_property_add_str(oc, "id", get_id, set_id, &error_abort);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void host_memory_backend_finalize(Object *o)
|
|
||||||
{
|
|
||||||
HostMemoryBackend *backend = MEMORY_BACKEND(o);
|
|
||||||
g_free(backend->id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const TypeInfo host_memory_backend_info = {
|
static const TypeInfo host_memory_backend_info = {
|
||||||
@@ -415,7 +365,6 @@ static const TypeInfo host_memory_backend_info = {
|
|||||||
.class_init = host_memory_backend_class_init,
|
.class_init = host_memory_backend_class_init,
|
||||||
.instance_size = sizeof(HostMemoryBackend),
|
.instance_size = sizeof(HostMemoryBackend),
|
||||||
.instance_init = host_memory_backend_init,
|
.instance_init = host_memory_backend_init,
|
||||||
.instance_finalize = host_memory_backend_finalize,
|
|
||||||
.interfaces = (InterfaceInfo[]) {
|
.interfaces = (InterfaceInfo[]) {
|
||||||
{ TYPE_USER_CREATABLE },
|
{ TYPE_USER_CREATABLE },
|
||||||
{ }
|
{ }
|
||||||
|
|||||||
85
backends/msmouse.c
Normal file
85
backends/msmouse.c
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* QEMU Microsoft serial mouse emulation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2008 Lubomir Rintel
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "sysemu/char.h"
|
||||||
|
#include "ui/console.h"
|
||||||
|
|
||||||
|
#define MSMOUSE_LO6(n) ((n) & 0x3f)
|
||||||
|
#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6)
|
||||||
|
|
||||||
|
static void msmouse_event(void *opaque,
|
||||||
|
int dx, int dy, int dz, int buttons_state)
|
||||||
|
{
|
||||||
|
CharDriverState *chr = (CharDriverState *)opaque;
|
||||||
|
|
||||||
|
unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 };
|
||||||
|
|
||||||
|
/* Movement deltas */
|
||||||
|
bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx);
|
||||||
|
bytes[1] |= MSMOUSE_LO6(dx);
|
||||||
|
bytes[2] |= MSMOUSE_LO6(dy);
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
bytes[0] |= (buttons_state & 0x01 ? 0x20 : 0x00);
|
||||||
|
bytes[0] |= (buttons_state & 0x02 ? 0x10 : 0x00);
|
||||||
|
bytes[3] |= (buttons_state & 0x04 ? 0x20 : 0x00);
|
||||||
|
|
||||||
|
/* We always send the packet of, so that we do not have to keep track
|
||||||
|
of previous state of the middle button. This can potentially confuse
|
||||||
|
some very old drivers for two button mice though. */
|
||||||
|
qemu_chr_be_write(chr, bytes, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int msmouse_chr_write (struct CharDriverState *s, const uint8_t *buf, int len)
|
||||||
|
{
|
||||||
|
/* Ignore writes to mouse port */
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void msmouse_chr_close (struct CharDriverState *chr)
|
||||||
|
{
|
||||||
|
g_free (chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CharDriverState *qemu_chr_open_msmouse(void)
|
||||||
|
{
|
||||||
|
CharDriverState *chr;
|
||||||
|
|
||||||
|
chr = qemu_chr_alloc();
|
||||||
|
chr->chr_write = msmouse_chr_write;
|
||||||
|
chr->chr_close = msmouse_chr_close;
|
||||||
|
chr->explicit_be_open = true;
|
||||||
|
|
||||||
|
qemu_add_mouse_event_handler(msmouse_event, chr, 0, "QEMU Microsoft Mouse");
|
||||||
|
|
||||||
|
return chr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void register_types(void)
|
||||||
|
{
|
||||||
|
register_char_driver("msmouse", CHARDEV_BACKEND_KIND_MSMOUSE, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
type_init(register_types);
|
||||||
@@ -10,11 +10,10 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/rng.h"
|
#include "sysemu/rng.h"
|
||||||
#include "chardev/char-fe.h"
|
#include "sysemu/char.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
|
#include "hw/qdev.h" /* just for DEFINE_PROP_CHR */
|
||||||
|
|
||||||
#define TYPE_RNG_EGD "rng-egd"
|
#define TYPE_RNG_EGD "rng-egd"
|
||||||
#define RNG_EGD(obj) OBJECT_CHECK(RngEgd, (obj), TYPE_RNG_EGD)
|
#define RNG_EGD(obj) OBJECT_CHECK(RngEgd, (obj), TYPE_RNG_EGD)
|
||||||
@@ -23,14 +22,35 @@ typedef struct RngEgd
|
|||||||
{
|
{
|
||||||
RngBackend parent;
|
RngBackend parent;
|
||||||
|
|
||||||
CharBackend chr;
|
CharDriverState *chr;
|
||||||
char *chr_name;
|
char *chr_name;
|
||||||
|
|
||||||
|
GSList *requests;
|
||||||
} RngEgd;
|
} RngEgd;
|
||||||
|
|
||||||
static void rng_egd_request_entropy(RngBackend *b, RngRequest *req)
|
typedef struct RngRequest
|
||||||
|
{
|
||||||
|
EntropyReceiveFunc *receive_entropy;
|
||||||
|
uint8_t *data;
|
||||||
|
void *opaque;
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
} RngRequest;
|
||||||
|
|
||||||
|
static void rng_egd_request_entropy(RngBackend *b, size_t size,
|
||||||
|
EntropyReceiveFunc *receive_entropy,
|
||||||
|
void *opaque)
|
||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(b);
|
RngEgd *s = RNG_EGD(b);
|
||||||
size_t size = req->size;
|
RngRequest *req;
|
||||||
|
|
||||||
|
req = g_malloc(sizeof(*req));
|
||||||
|
|
||||||
|
req->offset = 0;
|
||||||
|
req->size = size;
|
||||||
|
req->receive_entropy = receive_entropy;
|
||||||
|
req->opaque = opaque;
|
||||||
|
req->data = g_malloc(req->size);
|
||||||
|
|
||||||
while (size > 0) {
|
while (size > 0) {
|
||||||
uint8_t header[2];
|
uint8_t header[2];
|
||||||
@@ -40,21 +60,28 @@ static void rng_egd_request_entropy(RngBackend *b, RngRequest *req)
|
|||||||
header[0] = 0x02;
|
header[0] = 0x02;
|
||||||
header[1] = len;
|
header[1] = len;
|
||||||
|
|
||||||
/* XXX this blocks entire thread. Rewrite to use
|
qemu_chr_fe_write(s->chr, header, sizeof(header));
|
||||||
* qemu_chr_fe_write and background I/O callbacks */
|
|
||||||
qemu_chr_fe_write_all(&s->chr, header, sizeof(header));
|
|
||||||
|
|
||||||
size -= len;
|
size -= len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->requests = g_slist_append(s->requests, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rng_egd_free_request(RngRequest *req)
|
||||||
|
{
|
||||||
|
g_free(req->data);
|
||||||
|
g_free(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rng_egd_chr_can_read(void *opaque)
|
static int rng_egd_chr_can_read(void *opaque)
|
||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(opaque);
|
RngEgd *s = RNG_EGD(opaque);
|
||||||
RngRequest *req;
|
GSList *i;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH(req, &s->parent.requests, next) {
|
for (i = s->requests; i; i = i->next) {
|
||||||
|
RngRequest *req = i->data;
|
||||||
size += req->size - req->offset;
|
size += req->size - req->offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +93,8 @@ static void rng_egd_chr_read(void *opaque, const uint8_t *buf, int size)
|
|||||||
RngEgd *s = RNG_EGD(opaque);
|
RngEgd *s = RNG_EGD(opaque);
|
||||||
size_t buf_offset = 0;
|
size_t buf_offset = 0;
|
||||||
|
|
||||||
while (size > 0 && !QSIMPLEQ_EMPTY(&s->parent.requests)) {
|
while (size > 0 && s->requests) {
|
||||||
RngRequest *req = QSIMPLEQ_FIRST(&s->parent.requests);
|
RngRequest *req = s->requests->data;
|
||||||
int len = MIN(size, req->size - req->offset);
|
int len = MIN(size, req->size - req->offset);
|
||||||
|
|
||||||
memcpy(req->data + req->offset, buf + buf_offset, len);
|
memcpy(req->data + req->offset, buf + buf_offset, len);
|
||||||
@@ -76,37 +103,62 @@ static void rng_egd_chr_read(void *opaque, const uint8_t *buf, int size)
|
|||||||
size -= len;
|
size -= len;
|
||||||
|
|
||||||
if (req->offset == req->size) {
|
if (req->offset == req->size) {
|
||||||
|
s->requests = g_slist_remove_link(s->requests, s->requests);
|
||||||
|
|
||||||
req->receive_entropy(req->opaque, req->data, req->size);
|
req->receive_entropy(req->opaque, req->data, req->size);
|
||||||
|
|
||||||
rng_backend_finalize_request(&s->parent, req);
|
rng_egd_free_request(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void rng_egd_free_requests(RngEgd *s)
|
||||||
|
{
|
||||||
|
GSList *i;
|
||||||
|
|
||||||
|
for (i = s->requests; i; i = i->next) {
|
||||||
|
rng_egd_free_request(i->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_slist_free(s->requests);
|
||||||
|
s->requests = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rng_egd_cancel_requests(RngBackend *b)
|
||||||
|
{
|
||||||
|
RngEgd *s = RNG_EGD(b);
|
||||||
|
|
||||||
|
/* We simply delete the list of pending requests. If there is data in the
|
||||||
|
* queue waiting to be read, this is okay, because there will always be
|
||||||
|
* more data than we requested originally
|
||||||
|
*/
|
||||||
|
rng_egd_free_requests(s);
|
||||||
|
}
|
||||||
|
|
||||||
static void rng_egd_opened(RngBackend *b, Error **errp)
|
static void rng_egd_opened(RngBackend *b, Error **errp)
|
||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(b);
|
RngEgd *s = RNG_EGD(b);
|
||||||
Chardev *chr;
|
|
||||||
|
|
||||||
if (s->chr_name == NULL) {
|
if (s->chr_name == NULL) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
error_set(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||||
"chardev", "a valid character device");
|
"chardev", "a valid character device");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chr = qemu_chr_find(s->chr_name);
|
s->chr = qemu_chr_find(s->chr_name);
|
||||||
if (chr == NULL) {
|
if (s->chr == NULL) {
|
||||||
error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
|
error_set(errp, QERR_DEVICE_NOT_FOUND, s->chr_name);
|
||||||
"Device '%s' not found", s->chr_name);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!qemu_chr_fe_init(&s->chr, chr, errp)) {
|
|
||||||
|
if (qemu_chr_fe_claim(s->chr) != 0) {
|
||||||
|
error_set(errp, QERR_DEVICE_IN_USE, s->chr_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FIXME we should resubmit pending requests when the CDS reconnects. */
|
/* FIXME we should resubmit pending requests when the CDS reconnects. */
|
||||||
qemu_chr_fe_set_handlers(&s->chr, rng_egd_chr_can_read,
|
qemu_chr_add_handlers(s->chr, rng_egd_chr_can_read, rng_egd_chr_read,
|
||||||
rng_egd_chr_read, NULL, s, NULL, true);
|
NULL, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
||||||
@@ -115,7 +167,7 @@ static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
|||||||
RngEgd *s = RNG_EGD(b);
|
RngEgd *s = RNG_EGD(b);
|
||||||
|
|
||||||
if (b->opened) {
|
if (b->opened) {
|
||||||
error_setg(errp, QERR_PERMISSION_DENIED);
|
error_set(errp, QERR_PERMISSION_DENIED);
|
||||||
} else {
|
} else {
|
||||||
g_free(s->chr_name);
|
g_free(s->chr_name);
|
||||||
s->chr_name = g_strdup(value);
|
s->chr_name = g_strdup(value);
|
||||||
@@ -125,10 +177,9 @@ static void rng_egd_set_chardev(Object *obj, const char *value, Error **errp)
|
|||||||
static char *rng_egd_get_chardev(Object *obj, Error **errp)
|
static char *rng_egd_get_chardev(Object *obj, Error **errp)
|
||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(obj);
|
RngEgd *s = RNG_EGD(obj);
|
||||||
Chardev *chr = qemu_chr_fe_get_driver(&s->chr);
|
|
||||||
|
|
||||||
if (chr && chr->label) {
|
if (s->chr && s->chr->label) {
|
||||||
return g_strdup(chr->label);
|
return g_strdup(s->chr->label);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -145,8 +196,14 @@ static void rng_egd_finalize(Object *obj)
|
|||||||
{
|
{
|
||||||
RngEgd *s = RNG_EGD(obj);
|
RngEgd *s = RNG_EGD(obj);
|
||||||
|
|
||||||
qemu_chr_fe_deinit(&s->chr, false);
|
if (s->chr) {
|
||||||
|
qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
|
||||||
|
qemu_chr_fe_release(s->chr);
|
||||||
|
}
|
||||||
|
|
||||||
g_free(s->chr_name);
|
g_free(s->chr_name);
|
||||||
|
|
||||||
|
rng_egd_free_requests(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_egd_class_init(ObjectClass *klass, void *data)
|
static void rng_egd_class_init(ObjectClass *klass, void *data)
|
||||||
@@ -154,6 +211,7 @@ static void rng_egd_class_init(ObjectClass *klass, void *data)
|
|||||||
RngBackendClass *rbc = RNG_BACKEND_CLASS(klass);
|
RngBackendClass *rbc = RNG_BACKEND_CLASS(klass);
|
||||||
|
|
||||||
rbc->request_entropy = rng_egd_request_entropy;
|
rbc->request_entropy = rng_egd_request_entropy;
|
||||||
|
rbc->cancel_requests = rng_egd_cancel_requests;
|
||||||
rbc->opened = rng_egd_opened;
|
rbc->opened = rng_egd_opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,21 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/rng-random.h"
|
#include "sysemu/rng-random.h"
|
||||||
#include "sysemu/rng.h"
|
#include "sysemu/rng.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qemu/main-loop.h"
|
#include "qemu/main-loop.h"
|
||||||
|
|
||||||
struct RngRandom
|
struct RndRandom
|
||||||
{
|
{
|
||||||
RngBackend parent;
|
RngBackend parent;
|
||||||
|
|
||||||
int fd;
|
int fd;
|
||||||
char *filename;
|
char *filename;
|
||||||
|
|
||||||
|
EntropyReceiveFunc *receive_func;
|
||||||
|
void *opaque;
|
||||||
|
size_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,44 +36,45 @@ struct RngRandom
|
|||||||
|
|
||||||
static void entropy_available(void *opaque)
|
static void entropy_available(void *opaque)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(opaque);
|
RndRandom *s = RNG_RANDOM(opaque);
|
||||||
|
uint8_t buffer[s->size];
|
||||||
while (!QSIMPLEQ_EMPTY(&s->parent.requests)) {
|
|
||||||
RngRequest *req = QSIMPLEQ_FIRST(&s->parent.requests);
|
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
|
|
||||||
len = read(s->fd, req->data, req->size);
|
len = read(s->fd, buffer, s->size);
|
||||||
if (len < 0 && errno == EAGAIN) {
|
if (len < 0 && errno == EAGAIN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
g_assert(len != -1);
|
g_assert(len != -1);
|
||||||
|
|
||||||
req->receive_entropy(req->opaque, req->data, len);
|
s->receive_func(s->opaque, buffer, len);
|
||||||
|
s->receive_func = NULL;
|
||||||
|
|
||||||
rng_backend_finalize_request(&s->parent, req);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We've drained all requests, the fd handler can be reset. */
|
|
||||||
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_random_request_entropy(RngBackend *b, RngRequest *req)
|
static void rng_random_request_entropy(RngBackend *b, size_t size,
|
||||||
|
EntropyReceiveFunc *receive_entropy,
|
||||||
|
void *opaque)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(b);
|
RndRandom *s = RNG_RANDOM(b);
|
||||||
|
|
||||||
if (QSIMPLEQ_EMPTY(&s->parent.requests)) {
|
if (s->receive_func) {
|
||||||
/* If there are no pending requests yet, we need to
|
s->receive_func(s->opaque, NULL, 0);
|
||||||
* install our fd handler. */
|
|
||||||
qemu_set_fd_handler(s->fd, entropy_available, NULL, s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->receive_func = receive_entropy;
|
||||||
|
s->opaque = opaque;
|
||||||
|
s->size = size;
|
||||||
|
|
||||||
|
qemu_set_fd_handler(s->fd, entropy_available, NULL, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_random_opened(RngBackend *b, Error **errp)
|
static void rng_random_opened(RngBackend *b, Error **errp)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(b);
|
RndRandom *s = RNG_RANDOM(b);
|
||||||
|
|
||||||
if (s->filename == NULL) {
|
if (s->filename == NULL) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
error_set(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||||
"filename", "a valid filename");
|
"filename", "a valid filename");
|
||||||
} else {
|
} else {
|
||||||
s->fd = qemu_open(s->filename, O_RDONLY | O_NONBLOCK);
|
s->fd = qemu_open(s->filename, O_RDONLY | O_NONBLOCK);
|
||||||
@@ -83,7 +86,7 @@ static void rng_random_opened(RngBackend *b, Error **errp)
|
|||||||
|
|
||||||
static char *rng_random_get_filename(Object *obj, Error **errp)
|
static char *rng_random_get_filename(Object *obj, Error **errp)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(obj);
|
RndRandom *s = RNG_RANDOM(obj);
|
||||||
|
|
||||||
return g_strdup(s->filename);
|
return g_strdup(s->filename);
|
||||||
}
|
}
|
||||||
@@ -92,10 +95,10 @@ static void rng_random_set_filename(Object *obj, const char *filename,
|
|||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
RngBackend *b = RNG_BACKEND(obj);
|
RngBackend *b = RNG_BACKEND(obj);
|
||||||
RngRandom *s = RNG_RANDOM(obj);
|
RndRandom *s = RNG_RANDOM(obj);
|
||||||
|
|
||||||
if (b->opened) {
|
if (b->opened) {
|
||||||
error_setg(errp, QERR_PERMISSION_DENIED);
|
error_set(errp, QERR_PERMISSION_DENIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +108,7 @@ static void rng_random_set_filename(Object *obj, const char *filename,
|
|||||||
|
|
||||||
static void rng_random_init(Object *obj)
|
static void rng_random_init(Object *obj)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(obj);
|
RndRandom *s = RNG_RANDOM(obj);
|
||||||
|
|
||||||
object_property_add_str(obj, "filename",
|
object_property_add_str(obj, "filename",
|
||||||
rng_random_get_filename,
|
rng_random_get_filename,
|
||||||
@@ -118,7 +121,7 @@ static void rng_random_init(Object *obj)
|
|||||||
|
|
||||||
static void rng_random_finalize(Object *obj)
|
static void rng_random_finalize(Object *obj)
|
||||||
{
|
{
|
||||||
RngRandom *s = RNG_RANDOM(obj);
|
RndRandom *s = RNG_RANDOM(obj);
|
||||||
|
|
||||||
if (s->fd != -1) {
|
if (s->fd != -1) {
|
||||||
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
|
||||||
@@ -139,7 +142,7 @@ static void rng_random_class_init(ObjectClass *klass, void *data)
|
|||||||
static const TypeInfo rng_random_info = {
|
static const TypeInfo rng_random_info = {
|
||||||
.name = TYPE_RNG_RANDOM,
|
.name = TYPE_RNG_RANDOM,
|
||||||
.parent = TYPE_RNG_BACKEND,
|
.parent = TYPE_RNG_BACKEND,
|
||||||
.instance_size = sizeof(RngRandom),
|
.instance_size = sizeof(RndRandom),
|
||||||
.class_init = rng_random_class_init,
|
.class_init = rng_random_class_init,
|
||||||
.instance_init = rng_random_init,
|
.instance_init = rng_random_init,
|
||||||
.instance_finalize = rng_random_finalize,
|
.instance_finalize = rng_random_finalize,
|
||||||
|
|||||||
@@ -10,9 +10,7 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/rng.h"
|
#include "sysemu/rng.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "qom/object_interfaces.h"
|
#include "qom/object_interfaces.h"
|
||||||
|
|
||||||
@@ -21,20 +19,18 @@ void rng_backend_request_entropy(RngBackend *s, size_t size,
|
|||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
RngBackendClass *k = RNG_BACKEND_GET_CLASS(s);
|
RngBackendClass *k = RNG_BACKEND_GET_CLASS(s);
|
||||||
RngRequest *req;
|
|
||||||
|
|
||||||
if (k->request_entropy) {
|
if (k->request_entropy) {
|
||||||
req = g_malloc(sizeof(*req));
|
k->request_entropy(s, size, receive_entropy, opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req->offset = 0;
|
void rng_backend_cancel_requests(RngBackend *s)
|
||||||
req->size = size;
|
{
|
||||||
req->receive_entropy = receive_entropy;
|
RngBackendClass *k = RNG_BACKEND_GET_CLASS(s);
|
||||||
req->opaque = opaque;
|
|
||||||
req->data = g_malloc(req->size);
|
|
||||||
|
|
||||||
k->request_entropy(s, req);
|
if (k->cancel_requests) {
|
||||||
|
k->cancel_requests(s);
|
||||||
QSIMPLEQ_INSERT_TAIL(&s->requests, req, next);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +57,7 @@ static void rng_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!value && s->opened) {
|
if (!value && s->opened) {
|
||||||
error_setg(errp, QERR_PERMISSION_DENIED);
|
error_set(errp, QERR_PERMISSION_DENIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,48 +72,14 @@ static void rng_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
|||||||
s->opened = true;
|
s->opened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_backend_free_request(RngRequest *req)
|
|
||||||
{
|
|
||||||
g_free(req->data);
|
|
||||||
g_free(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rng_backend_free_requests(RngBackend *s)
|
|
||||||
{
|
|
||||||
RngRequest *req, *next;
|
|
||||||
|
|
||||||
QSIMPLEQ_FOREACH_SAFE(req, &s->requests, next, next) {
|
|
||||||
rng_backend_free_request(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSIMPLEQ_INIT(&s->requests);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rng_backend_finalize_request(RngBackend *s, RngRequest *req)
|
|
||||||
{
|
|
||||||
QSIMPLEQ_REMOVE(&s->requests, req, RngRequest, next);
|
|
||||||
rng_backend_free_request(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rng_backend_init(Object *obj)
|
static void rng_backend_init(Object *obj)
|
||||||
{
|
{
|
||||||
RngBackend *s = RNG_BACKEND(obj);
|
|
||||||
|
|
||||||
QSIMPLEQ_INIT(&s->requests);
|
|
||||||
|
|
||||||
object_property_add_bool(obj, "opened",
|
object_property_add_bool(obj, "opened",
|
||||||
rng_backend_prop_get_opened,
|
rng_backend_prop_get_opened,
|
||||||
rng_backend_prop_set_opened,
|
rng_backend_prop_set_opened,
|
||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rng_backend_finalize(Object *obj)
|
|
||||||
{
|
|
||||||
RngBackend *s = RNG_BACKEND(obj);
|
|
||||||
|
|
||||||
rng_backend_free_requests(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rng_backend_class_init(ObjectClass *oc, void *data)
|
static void rng_backend_class_init(ObjectClass *oc, void *data)
|
||||||
{
|
{
|
||||||
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
|
||||||
@@ -130,7 +92,6 @@ static const TypeInfo rng_backend_info = {
|
|||||||
.parent = TYPE_OBJECT,
|
.parent = TYPE_OBJECT,
|
||||||
.instance_size = sizeof(RngBackend),
|
.instance_size = sizeof(RngBackend),
|
||||||
.instance_init = rng_backend_init,
|
.instance_init = rng_backend_init,
|
||||||
.instance_finalize = rng_backend_finalize,
|
|
||||||
.class_size = sizeof(RngBackendClass),
|
.class_size = sizeof(RngBackendClass),
|
||||||
.class_init = rng_backend_class_init,
|
.class_init = rng_backend_class_init,
|
||||||
.abstract = true,
|
.abstract = true,
|
||||||
|
|||||||
@@ -23,25 +23,19 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "chardev/char.h"
|
#include "sysemu/char.h"
|
||||||
|
|
||||||
#define BUF_SIZE 32
|
#define BUF_SIZE 32
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Chardev parent;
|
CharDriverState *chr;
|
||||||
|
|
||||||
uint8_t in_buf[32];
|
uint8_t in_buf[32];
|
||||||
int in_buf_used;
|
int in_buf_used;
|
||||||
} TestdevChardev;
|
} TestdevCharState;
|
||||||
|
|
||||||
#define TYPE_CHARDEV_TESTDEV "chardev-testdev"
|
|
||||||
#define TESTDEV_CHARDEV(obj) \
|
|
||||||
OBJECT_CHECK(TestdevChardev, (obj), TYPE_CHARDEV_TESTDEV)
|
|
||||||
|
|
||||||
/* Try to interpret a whole incoming packet */
|
/* Try to interpret a whole incoming packet */
|
||||||
static int testdev_eat_packet(TestdevChardev *testdev)
|
static int testdev_eat_packet(TestdevCharState *testdev)
|
||||||
{
|
{
|
||||||
const uint8_t *cur = testdev->in_buf;
|
const uint8_t *cur = testdev->in_buf;
|
||||||
int len = testdev->in_buf_used;
|
int len = testdev->in_buf_used;
|
||||||
@@ -82,9 +76,9 @@ static int testdev_eat_packet(TestdevChardev *testdev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The other end is writing some data. Store it and try to interpret */
|
/* The other end is writing some data. Store it and try to interpret */
|
||||||
static int testdev_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
static int testdev_write(CharDriverState *chr, const uint8_t *buf, int len)
|
||||||
{
|
{
|
||||||
TestdevChardev *testdev = TESTDEV_CHARDEV(chr);
|
TestdevCharState *testdev = chr->opaque;
|
||||||
int tocopy, eaten, orig_len = len;
|
int tocopy, eaten, orig_len = len;
|
||||||
|
|
||||||
while (len) {
|
while (len) {
|
||||||
@@ -107,23 +101,31 @@ static int testdev_chr_write(Chardev *chr, const uint8_t *buf, int len)
|
|||||||
return orig_len;
|
return orig_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void char_testdev_class_init(ObjectClass *oc, void *data)
|
static void testdev_close(struct CharDriverState *chr)
|
||||||
{
|
{
|
||||||
ChardevClass *cc = CHARDEV_CLASS(oc);
|
TestdevCharState *testdev = chr->opaque;
|
||||||
|
|
||||||
cc->chr_write = testdev_chr_write;
|
g_free(testdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const TypeInfo char_testdev_type_info = {
|
CharDriverState *chr_testdev_init(void)
|
||||||
.name = TYPE_CHARDEV_TESTDEV,
|
{
|
||||||
.parent = TYPE_CHARDEV,
|
TestdevCharState *testdev;
|
||||||
.instance_size = sizeof(TestdevChardev),
|
CharDriverState *chr;
|
||||||
.class_init = char_testdev_class_init,
|
|
||||||
};
|
testdev = g_malloc0(sizeof(TestdevCharState));
|
||||||
|
testdev->chr = chr = g_malloc0(sizeof(CharDriverState));
|
||||||
|
|
||||||
|
chr->opaque = testdev;
|
||||||
|
chr->chr_write = testdev_write;
|
||||||
|
chr->chr_close = testdev_close;
|
||||||
|
|
||||||
|
return chr;
|
||||||
|
}
|
||||||
|
|
||||||
static void register_types(void)
|
static void register_types(void)
|
||||||
{
|
{
|
||||||
type_register_static(&char_testdev_type_info);
|
register_char_driver("testdev", CHARDEV_BACKEND_KIND_TESTDEV, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
type_init(register_types);
|
type_init(register_types);
|
||||||
@@ -12,9 +12,7 @@
|
|||||||
* Based on backends/rng.c by Anthony Liguori
|
* Based on backends/rng.c by Anthony Liguori
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "sysemu/tpm_backend.h"
|
#include "sysemu/tpm_backend.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
#include "qapi/qmp/qerror.h"
|
||||||
#include "sysemu/tpm.h"
|
#include "sysemu/tpm.h"
|
||||||
#include "qemu/thread.h"
|
#include "qemu/thread.h"
|
||||||
@@ -98,20 +96,6 @@ bool tpm_backend_get_tpm_established_flag(TPMBackend *s)
|
|||||||
return k->ops->get_tpm_established_flag(s);
|
return k->ops->get_tpm_established_flag(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
int tpm_backend_reset_tpm_established_flag(TPMBackend *s, uint8_t locty)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->reset_tpm_established_flag(s, locty);
|
|
||||||
}
|
|
||||||
|
|
||||||
TPMVersion tpm_backend_get_tpm_version(TPMBackend *s)
|
|
||||||
{
|
|
||||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
|
||||||
|
|
||||||
return k->ops->get_tpm_version(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool tpm_backend_prop_get_opened(Object *obj, Error **errp)
|
static bool tpm_backend_prop_get_opened(Object *obj, Error **errp)
|
||||||
{
|
{
|
||||||
TPMBackend *s = TPM_BACKEND(obj);
|
TPMBackend *s = TPM_BACKEND(obj);
|
||||||
@@ -135,7 +119,7 @@ static void tpm_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!value && s->opened) {
|
if (!value && s->opened) {
|
||||||
error_setg(errp, QERR_PERMISSION_DENIED);
|
error_set(errp, QERR_PERMISSION_DENIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +165,17 @@ void tpm_backend_thread_end(TPMBackendThread *tbt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
|
||||||
|
GFunc func, gpointer user_data)
|
||||||
|
{
|
||||||
|
if (!tbt->pool) {
|
||||||
|
tpm_backend_thread_create(tbt, func, user_data);
|
||||||
|
} else {
|
||||||
|
g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const TypeInfo tpm_backend_info = {
|
static const TypeInfo tpm_backend_info = {
|
||||||
.name = TYPE_TPM_BACKEND,
|
.name = TYPE_TPM_BACKEND,
|
||||||
.parent = TYPE_OBJECT,
|
.parent = TYPE_OBJECT,
|
||||||
|
|||||||
20
balloon.c
20
balloon.c
@@ -24,30 +24,17 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "monitor/monitor.h"
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "exec/cpu-common.h"
|
#include "exec/cpu-common.h"
|
||||||
#include "sysemu/kvm.h"
|
#include "sysemu/kvm.h"
|
||||||
#include "sysemu/balloon.h"
|
#include "sysemu/balloon.h"
|
||||||
#include "trace-root.h"
|
#include "trace.h"
|
||||||
#include "qmp-commands.h"
|
#include "qmp-commands.h"
|
||||||
#include "qapi/qmp/qerror.h"
|
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "qapi/qmp/qjson.h"
|
||||||
|
|
||||||
static QEMUBalloonEvent *balloon_event_fn;
|
static QEMUBalloonEvent *balloon_event_fn;
|
||||||
static QEMUBalloonStatus *balloon_stat_fn;
|
static QEMUBalloonStatus *balloon_stat_fn;
|
||||||
static void *balloon_opaque;
|
static void *balloon_opaque;
|
||||||
static bool balloon_inhibited;
|
|
||||||
|
|
||||||
bool qemu_balloon_is_inhibited(void)
|
|
||||||
{
|
|
||||||
return balloon_inhibited;
|
|
||||||
}
|
|
||||||
|
|
||||||
void qemu_balloon_inhibit(bool state)
|
|
||||||
{
|
|
||||||
balloon_inhibited = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool have_balloon(Error **errp)
|
static bool have_balloon(Error **errp)
|
||||||
{
|
{
|
||||||
@@ -71,6 +58,7 @@ int qemu_add_balloon_handler(QEMUBalloonEvent *event_func,
|
|||||||
/* We're already registered one balloon handler. How many can
|
/* We're already registered one balloon handler. How many can
|
||||||
* a guest really have?
|
* a guest really have?
|
||||||
*/
|
*/
|
||||||
|
error_report("Another balloon device already registered");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
balloon_event_fn = event_func;
|
balloon_event_fn = event_func;
|
||||||
@@ -109,7 +97,7 @@ void qmp_balloon(int64_t target, Error **errp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (target <= 0) {
|
if (target <= 0) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "target", "a size");
|
error_set(errp, QERR_INVALID_PARAMETER_VALUE, "target", "a size");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
block-obj-y += raw-format.o qcow.o vdi.o vmdk.o cloop.o bochs.o vpc.o vvfat.o dmg.o
|
block-obj-y += raw_bsd.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o
|
||||||
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o
|
block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o
|
||||||
block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
|
||||||
block-obj-y += qed-check.o
|
block-obj-y += qed-check.o
|
||||||
block-obj-y += vhdx.o vhdx-endian.o vhdx-log.o
|
block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
|
||||||
block-obj-y += quorum.o
|
block-obj-$(CONFIG_QUORUM) += quorum.o
|
||||||
block-obj-y += parallels.o blkdebug.o blkverify.o blkreplay.o
|
block-obj-y += parallels.o blkdebug.o blkverify.o
|
||||||
block-obj-y += block-backend.o snapshot.o qapi.o
|
block-obj-y += block-backend.o snapshot.o qapi.o
|
||||||
block-obj-$(CONFIG_WIN32) += file-win32.o win32-aio.o
|
block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
|
||||||
block-obj-$(CONFIG_POSIX) += file-posix.o
|
block-obj-$(CONFIG_POSIX) += raw-posix.o
|
||||||
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o
|
||||||
block-obj-y += null.o mirror.o commit.o io.o
|
block-obj-y += null.o mirror.o
|
||||||
block-obj-y += throttle-groups.o
|
|
||||||
|
|
||||||
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
block-obj-y += nbd.o nbd-client.o sheepdog.o
|
||||||
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
block-obj-$(CONFIG_LIBISCSI) += iscsi.o
|
||||||
block-obj-$(if $(CONFIG_LIBISCSI),y,n) += iscsi-opts.o
|
|
||||||
block-obj-$(CONFIG_LIBNFS) += nfs.o
|
block-obj-$(CONFIG_LIBNFS) += nfs.o
|
||||||
block-obj-$(CONFIG_CURL) += curl.o
|
block-obj-$(CONFIG_CURL) += curl.o
|
||||||
block-obj-$(CONFIG_RBD) += rbd.o
|
block-obj-$(CONFIG_RBD) += rbd.o
|
||||||
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
block-obj-$(CONFIG_GLUSTERFS) += gluster.o
|
||||||
block-obj-$(CONFIG_VXHS) += vxhs.o
|
block-obj-$(CONFIG_ARCHIPELAGO) += archipelago.o
|
||||||
block-obj-$(CONFIG_LIBSSH2) += ssh.o
|
block-obj-$(CONFIG_LIBSSH2) += ssh.o
|
||||||
block-obj-y += accounting.o dirty-bitmap.o
|
block-obj-y += dictzip.o
|
||||||
|
block-obj-y += tar.o
|
||||||
|
block-obj-y += accounting.o
|
||||||
block-obj-y += write-threshold.o
|
block-obj-y += write-threshold.o
|
||||||
block-obj-y += backup.o
|
|
||||||
block-obj-$(CONFIG_REPLICATION) += replication.o
|
|
||||||
|
|
||||||
block-obj-y += crypto.o
|
|
||||||
|
|
||||||
common-obj-y += stream.o
|
common-obj-y += stream.o
|
||||||
|
common-obj-y += commit.o
|
||||||
|
common-obj-y += backup.o
|
||||||
|
|
||||||
nfs.o-libs := $(LIBNFS_LIBS)
|
|
||||||
iscsi.o-cflags := $(LIBISCSI_CFLAGS)
|
iscsi.o-cflags := $(LIBISCSI_CFLAGS)
|
||||||
iscsi.o-libs := $(LIBISCSI_LIBS)
|
iscsi.o-libs := $(LIBISCSI_LIBS)
|
||||||
curl.o-cflags := $(CURL_CFLAGS)
|
curl.o-cflags := $(CURL_CFLAGS)
|
||||||
@@ -39,10 +36,9 @@ rbd.o-cflags := $(RBD_CFLAGS)
|
|||||||
rbd.o-libs := $(RBD_LIBS)
|
rbd.o-libs := $(RBD_LIBS)
|
||||||
gluster.o-cflags := $(GLUSTERFS_CFLAGS)
|
gluster.o-cflags := $(GLUSTERFS_CFLAGS)
|
||||||
gluster.o-libs := $(GLUSTERFS_LIBS)
|
gluster.o-libs := $(GLUSTERFS_LIBS)
|
||||||
vxhs.o-libs := $(VXHS_LIBS)
|
|
||||||
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
ssh.o-cflags := $(LIBSSH2_CFLAGS)
|
||||||
ssh.o-libs := $(LIBSSH2_LIBS)
|
ssh.o-libs := $(LIBSSH2_LIBS)
|
||||||
block-obj-$(if $(CONFIG_BZIP2),m,n) += dmg-bz2.o
|
archipelago.o-libs := $(ARCHIPELAGO_LIBS)
|
||||||
dmg-bz2.o-libs := $(BZIP2_LIBS)
|
dmg.o-libs := $(BZIP2_LIBS)
|
||||||
qcow.o-libs := -lz
|
qcow.o-libs := -lz
|
||||||
linux-aio.o-libs := -laio
|
linux-aio.o-libs := -laio
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* QEMU System Emulator block accounting
|
* QEMU System Emulator block accounting
|
||||||
*
|
*
|
||||||
* Copyright (c) 2011 Christoph Hellwig
|
* Copyright (c) 2011 Christoph Hellwig
|
||||||
* Copyright (c) 2015 Igalia, S.L.
|
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -23,66 +22,9 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "block/accounting.h"
|
#include "block/accounting.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/timer.h"
|
#include "qemu/timer.h"
|
||||||
#include "sysemu/qtest.h"
|
|
||||||
|
|
||||||
static QEMUClockType clock_type = QEMU_CLOCK_REALTIME;
|
|
||||||
static const int qtest_latency_ns = NANOSECONDS_PER_SECOND / 1000;
|
|
||||||
|
|
||||||
void block_acct_init(BlockAcctStats *stats)
|
|
||||||
{
|
|
||||||
qemu_mutex_init(&stats->lock);
|
|
||||||
if (qtest_enabled()) {
|
|
||||||
clock_type = QEMU_CLOCK_VIRTUAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void block_acct_setup(BlockAcctStats *stats, bool account_invalid,
|
|
||||||
bool account_failed)
|
|
||||||
{
|
|
||||||
stats->account_invalid = account_invalid;
|
|
||||||
stats->account_failed = account_failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void block_acct_cleanup(BlockAcctStats *stats)
|
|
||||||
{
|
|
||||||
BlockAcctTimedStats *s, *next;
|
|
||||||
QSLIST_FOREACH_SAFE(s, &stats->intervals, entries, next) {
|
|
||||||
g_free(s);
|
|
||||||
}
|
|
||||||
qemu_mutex_destroy(&stats->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length)
|
|
||||||
{
|
|
||||||
BlockAcctTimedStats *s;
|
|
||||||
unsigned i;
|
|
||||||
|
|
||||||
s = g_new0(BlockAcctTimedStats, 1);
|
|
||||||
s->interval_length = interval_length;
|
|
||||||
s->stats = stats;
|
|
||||||
qemu_mutex_lock(&stats->lock);
|
|
||||||
QSLIST_INSERT_HEAD(&stats->intervals, s, entries);
|
|
||||||
|
|
||||||
for (i = 0; i < BLOCK_MAX_IOTYPE; i++) {
|
|
||||||
timed_average_init(&s->latency[i], clock_type,
|
|
||||||
(uint64_t) interval_length * NANOSECONDS_PER_SECOND);
|
|
||||||
}
|
|
||||||
qemu_mutex_unlock(&stats->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockAcctTimedStats *block_acct_interval_next(BlockAcctStats *stats,
|
|
||||||
BlockAcctTimedStats *s)
|
|
||||||
{
|
|
||||||
if (s == NULL) {
|
|
||||||
return QSLIST_FIRST(&stats->intervals);
|
|
||||||
} else {
|
|
||||||
return QSLIST_NEXT(s, entries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void block_acct_start(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
void block_acct_start(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
||||||
int64_t bytes, enum BlockAcctType type)
|
int64_t bytes, enum BlockAcctType type)
|
||||||
@@ -90,96 +32,32 @@ void block_acct_start(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
|||||||
assert(type < BLOCK_MAX_IOTYPE);
|
assert(type < BLOCK_MAX_IOTYPE);
|
||||||
|
|
||||||
cookie->bytes = bytes;
|
cookie->bytes = bytes;
|
||||||
cookie->start_time_ns = qemu_clock_get_ns(clock_type);
|
cookie->start_time_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
|
||||||
cookie->type = type;
|
cookie->type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
|
|
||||||
bool failed)
|
|
||||||
{
|
|
||||||
BlockAcctTimedStats *s;
|
|
||||||
int64_t time_ns = qemu_clock_get_ns(clock_type);
|
|
||||||
int64_t latency_ns = time_ns - cookie->start_time_ns;
|
|
||||||
|
|
||||||
if (qtest_enabled()) {
|
|
||||||
latency_ns = qtest_latency_ns;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(cookie->type < BLOCK_MAX_IOTYPE);
|
|
||||||
|
|
||||||
qemu_mutex_lock(&stats->lock);
|
|
||||||
|
|
||||||
if (failed) {
|
|
||||||
stats->failed_ops[cookie->type]++;
|
|
||||||
} else {
|
|
||||||
stats->nr_bytes[cookie->type] += cookie->bytes;
|
|
||||||
stats->nr_ops[cookie->type]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!failed || stats->account_failed) {
|
|
||||||
stats->total_time_ns[cookie->type] += latency_ns;
|
|
||||||
stats->last_access_time_ns = time_ns;
|
|
||||||
|
|
||||||
QSLIST_FOREACH(s, &stats->intervals, entries) {
|
|
||||||
timed_average_account(&s->latency[cookie->type], latency_ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_mutex_unlock(&stats->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void block_acct_done(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
void block_acct_done(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
||||||
{
|
{
|
||||||
block_account_one_io(stats, cookie, false);
|
assert(cookie->type < BLOCK_MAX_IOTYPE);
|
||||||
|
|
||||||
|
stats->nr_bytes[cookie->type] += cookie->bytes;
|
||||||
|
stats->nr_ops[cookie->type]++;
|
||||||
|
stats->total_time_ns[cookie->type] +=
|
||||||
|
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - cookie->start_time_ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
void block_acct_failed(BlockAcctStats *stats, BlockAcctCookie *cookie)
|
|
||||||
|
void block_acct_highest_sector(BlockAcctStats *stats, int64_t sector_num,
|
||||||
|
unsigned int nb_sectors)
|
||||||
{
|
{
|
||||||
block_account_one_io(stats, cookie, true);
|
if (stats->wr_highest_sector < sector_num + nb_sectors - 1) {
|
||||||
}
|
stats->wr_highest_sector = sector_num + nb_sectors - 1;
|
||||||
|
|
||||||
void block_acct_invalid(BlockAcctStats *stats, enum BlockAcctType type)
|
|
||||||
{
|
|
||||||
assert(type < BLOCK_MAX_IOTYPE);
|
|
||||||
|
|
||||||
/* block_account_one_io() updates total_time_ns[], but this one does
|
|
||||||
* not. The reason is that invalid requests are accounted during their
|
|
||||||
* submission, therefore there's no actual I/O involved.
|
|
||||||
*/
|
|
||||||
qemu_mutex_lock(&stats->lock);
|
|
||||||
stats->invalid_ops[type]++;
|
|
||||||
|
|
||||||
if (stats->account_invalid) {
|
|
||||||
stats->last_access_time_ns = qemu_clock_get_ns(clock_type);
|
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock(&stats->lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void block_acct_merge_done(BlockAcctStats *stats, enum BlockAcctType type,
|
void block_acct_merge_done(BlockAcctStats *stats, enum BlockAcctType type,
|
||||||
int num_requests)
|
int num_requests)
|
||||||
{
|
{
|
||||||
assert(type < BLOCK_MAX_IOTYPE);
|
assert(type < BLOCK_MAX_IOTYPE);
|
||||||
|
|
||||||
qemu_mutex_lock(&stats->lock);
|
|
||||||
stats->merged[type] += num_requests;
|
stats->merged[type] += num_requests;
|
||||||
qemu_mutex_unlock(&stats->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t block_acct_idle_time_ns(BlockAcctStats *stats)
|
|
||||||
{
|
|
||||||
return qemu_clock_get_ns(clock_type) - stats->last_access_time_ns;
|
|
||||||
}
|
|
||||||
|
|
||||||
double block_acct_queue_depth(BlockAcctTimedStats *stats,
|
|
||||||
enum BlockAcctType type)
|
|
||||||
{
|
|
||||||
uint64_t sum, elapsed;
|
|
||||||
|
|
||||||
assert(type < BLOCK_MAX_IOTYPE);
|
|
||||||
|
|
||||||
qemu_mutex_lock(&stats->stats->lock);
|
|
||||||
sum = timed_average_sum(&stats->latency[type], &elapsed);
|
|
||||||
qemu_mutex_unlock(&stats->stats->lock);
|
|
||||||
|
|
||||||
return (double) sum / elapsed;
|
|
||||||
}
|
}
|
||||||
|
|||||||
1084
block/archipelago.c
Normal file
1084
block/archipelago.c
Normal file
File diff suppressed because it is too large
Load Diff
530
block/backup.c
530
block/backup.c
@@ -11,48 +11,42 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "block/block.h"
|
#include "block/block.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "block/blockjob_int.h"
|
#include "block/blockjob.h"
|
||||||
#include "block/block_backup.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
|
||||||
#include "qemu/ratelimit.h"
|
#include "qemu/ratelimit.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "sysemu/block-backend.h"
|
|
||||||
#include "qemu/bitmap.h"
|
|
||||||
#include "qemu/error-report.h"
|
|
||||||
|
|
||||||
#define BACKUP_CLUSTER_SIZE_DEFAULT (1 << 16)
|
#define BACKUP_CLUSTER_BITS 16
|
||||||
|
#define BACKUP_CLUSTER_SIZE (1 << BACKUP_CLUSTER_BITS)
|
||||||
|
#define BACKUP_SECTORS_PER_CLUSTER (BACKUP_CLUSTER_SIZE / BDRV_SECTOR_SIZE)
|
||||||
|
|
||||||
#define SLICE_TIME 100000000ULL /* ns */
|
#define SLICE_TIME 100000000ULL /* ns */
|
||||||
|
|
||||||
|
typedef struct CowRequest {
|
||||||
|
int64_t start;
|
||||||
|
int64_t end;
|
||||||
|
QLIST_ENTRY(CowRequest) list;
|
||||||
|
CoQueue wait_queue; /* coroutines blocked on this request */
|
||||||
|
} CowRequest;
|
||||||
|
|
||||||
typedef struct BackupBlockJob {
|
typedef struct BackupBlockJob {
|
||||||
BlockJob common;
|
BlockJob common;
|
||||||
BlockBackend *target;
|
BlockDriverState *target;
|
||||||
/* bitmap for sync=incremental */
|
|
||||||
BdrvDirtyBitmap *sync_bitmap;
|
|
||||||
MirrorSyncMode sync_mode;
|
MirrorSyncMode sync_mode;
|
||||||
RateLimit limit;
|
RateLimit limit;
|
||||||
BlockdevOnError on_source_error;
|
BlockdevOnError on_source_error;
|
||||||
BlockdevOnError on_target_error;
|
BlockdevOnError on_target_error;
|
||||||
CoRwlock flush_rwlock;
|
CoRwlock flush_rwlock;
|
||||||
uint64_t sectors_read;
|
uint64_t sectors_read;
|
||||||
unsigned long *done_bitmap;
|
HBitmap *bitmap;
|
||||||
int64_t cluster_size;
|
|
||||||
bool compress;
|
|
||||||
NotifierWithReturn before_write;
|
|
||||||
QLIST_HEAD(, CowRequest) inflight_reqs;
|
QLIST_HEAD(, CowRequest) inflight_reqs;
|
||||||
} BackupBlockJob;
|
} BackupBlockJob;
|
||||||
|
|
||||||
/* Size of a cluster in sectors, instead of bytes. */
|
|
||||||
static inline int64_t cluster_size_sectors(BackupBlockJob *job)
|
|
||||||
{
|
|
||||||
return job->cluster_size / BDRV_SECTOR_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See if in-flight requests overlap and wait for them to complete */
|
/* See if in-flight requests overlap and wait for them to complete */
|
||||||
static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
||||||
int64_t start,
|
int64_t start,
|
||||||
@@ -65,7 +59,7 @@ static void coroutine_fn wait_for_overlapping_requests(BackupBlockJob *job,
|
|||||||
retry = false;
|
retry = false;
|
||||||
QLIST_FOREACH(req, &job->inflight_reqs, list) {
|
QLIST_FOREACH(req, &job->inflight_reqs, list) {
|
||||||
if (end > req->start && start < req->end) {
|
if (end > req->start && start < req->end) {
|
||||||
qemu_co_queue_wait(&req->wait_queue, NULL);
|
qemu_co_queue_wait(&req->wait_queue);
|
||||||
retry = true;
|
retry = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -90,25 +84,23 @@ static void cow_request_end(CowRequest *req)
|
|||||||
qemu_co_queue_restart_all(&req->wait_queue);
|
qemu_co_queue_restart_all(&req->wait_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
static int coroutine_fn backup_do_cow(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
bool *error_is_read,
|
bool *error_is_read)
|
||||||
bool is_write_notifier)
|
|
||||||
{
|
{
|
||||||
BlockBackend *blk = job->common.blk;
|
BackupBlockJob *job = (BackupBlockJob *)bs->job;
|
||||||
CowRequest cow_request;
|
CowRequest cow_request;
|
||||||
struct iovec iov;
|
struct iovec iov;
|
||||||
QEMUIOVector bounce_qiov;
|
QEMUIOVector bounce_qiov;
|
||||||
void *bounce_buffer = NULL;
|
void *bounce_buffer = NULL;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
|
||||||
int64_t start, end;
|
int64_t start, end;
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
qemu_co_rwlock_rdlock(&job->flush_rwlock);
|
qemu_co_rwlock_rdlock(&job->flush_rwlock);
|
||||||
|
|
||||||
start = sector_num / sectors_per_cluster;
|
start = sector_num / BACKUP_SECTORS_PER_CLUSTER;
|
||||||
end = DIV_ROUND_UP(sector_num + nb_sectors, sectors_per_cluster);
|
end = DIV_ROUND_UP(sector_num + nb_sectors, BACKUP_SECTORS_PER_CLUSTER);
|
||||||
|
|
||||||
trace_backup_do_cow_enter(job, start, sector_num, nb_sectors);
|
trace_backup_do_cow_enter(job, start, sector_num, nb_sectors);
|
||||||
|
|
||||||
@@ -116,27 +108,26 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
|||||||
cow_request_begin(&cow_request, job, start, end);
|
cow_request_begin(&cow_request, job, start, end);
|
||||||
|
|
||||||
for (; start < end; start++) {
|
for (; start < end; start++) {
|
||||||
if (test_bit(start, job->done_bitmap)) {
|
if (hbitmap_get(job->bitmap, start)) {
|
||||||
trace_backup_do_cow_skip(job, start);
|
trace_backup_do_cow_skip(job, start);
|
||||||
continue; /* already copied */
|
continue; /* already copied */
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_backup_do_cow_process(job, start);
|
trace_backup_do_cow_process(job, start);
|
||||||
|
|
||||||
n = MIN(sectors_per_cluster,
|
n = MIN(BACKUP_SECTORS_PER_CLUSTER,
|
||||||
job->common.len / BDRV_SECTOR_SIZE -
|
job->common.len / BDRV_SECTOR_SIZE -
|
||||||
start * sectors_per_cluster);
|
start * BACKUP_SECTORS_PER_CLUSTER);
|
||||||
|
|
||||||
if (!bounce_buffer) {
|
if (!bounce_buffer) {
|
||||||
bounce_buffer = blk_blockalign(blk, job->cluster_size);
|
bounce_buffer = qemu_blockalign(bs, BACKUP_CLUSTER_SIZE);
|
||||||
}
|
}
|
||||||
iov.iov_base = bounce_buffer;
|
iov.iov_base = bounce_buffer;
|
||||||
iov.iov_len = n * BDRV_SECTOR_SIZE;
|
iov.iov_len = n * BDRV_SECTOR_SIZE;
|
||||||
qemu_iovec_init_external(&bounce_qiov, &iov, 1);
|
qemu_iovec_init_external(&bounce_qiov, &iov, 1);
|
||||||
|
|
||||||
ret = blk_co_preadv(blk, start * job->cluster_size,
|
ret = bdrv_co_readv(bs, start * BACKUP_SECTORS_PER_CLUSTER, n,
|
||||||
bounce_qiov.size, &bounce_qiov,
|
&bounce_qiov);
|
||||||
is_write_notifier ? BDRV_REQ_NO_SERIALISING : 0);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
trace_backup_do_cow_read_fail(job, start, ret);
|
trace_backup_do_cow_read_fail(job, start, ret);
|
||||||
if (error_is_read) {
|
if (error_is_read) {
|
||||||
@@ -146,12 +137,13 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
|
if (buffer_is_zero(iov.iov_base, iov.iov_len)) {
|
||||||
ret = blk_co_pwrite_zeroes(job->target, start * job->cluster_size,
|
ret = bdrv_co_write_zeroes(job->target,
|
||||||
bounce_qiov.size, BDRV_REQ_MAY_UNMAP);
|
start * BACKUP_SECTORS_PER_CLUSTER,
|
||||||
|
n, BDRV_REQ_MAY_UNMAP);
|
||||||
} else {
|
} else {
|
||||||
ret = blk_co_pwritev(job->target, start * job->cluster_size,
|
ret = bdrv_co_writev(job->target,
|
||||||
bounce_qiov.size, &bounce_qiov,
|
start * BACKUP_SECTORS_PER_CLUSTER, n,
|
||||||
job->compress ? BDRV_REQ_WRITE_COMPRESSED : 0);
|
&bounce_qiov);
|
||||||
}
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
trace_backup_do_cow_write_fail(job, start, ret);
|
trace_backup_do_cow_write_fail(job, start, ret);
|
||||||
@@ -161,7 +153,7 @@ static int coroutine_fn backup_do_cow(BackupBlockJob *job,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_bit(start, job->done_bitmap);
|
hbitmap_set(job->bitmap, start, 1);
|
||||||
|
|
||||||
/* Publish progress, guest I/O counts as progress too. Note that the
|
/* Publish progress, guest I/O counts as progress too. Note that the
|
||||||
* offset field is an opaque progress value, it is not a disk offset.
|
* offset field is an opaque progress value, it is not a disk offset.
|
||||||
@@ -188,16 +180,14 @@ static int coroutine_fn backup_before_write_notify(
|
|||||||
NotifierWithReturn *notifier,
|
NotifierWithReturn *notifier,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
BackupBlockJob *job = container_of(notifier, BackupBlockJob, before_write);
|
|
||||||
BdrvTrackedRequest *req = opaque;
|
BdrvTrackedRequest *req = opaque;
|
||||||
int64_t sector_num = req->offset >> BDRV_SECTOR_BITS;
|
int64_t sector_num = req->offset >> BDRV_SECTOR_BITS;
|
||||||
int nb_sectors = req->bytes >> BDRV_SECTOR_BITS;
|
int nb_sectors = req->bytes >> BDRV_SECTOR_BITS;
|
||||||
|
|
||||||
assert(req->bs == blk_bs(job->common.blk));
|
|
||||||
assert((req->offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
assert((req->offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||||
assert((req->bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
assert((req->bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
||||||
|
|
||||||
return backup_do_cow(job, sector_num, nb_sectors, NULL, true);
|
return backup_do_cow(req->bs, sector_num, nb_sectors, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
||||||
@@ -205,134 +195,35 @@ static void backup_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||||
|
|
||||||
if (speed < 0) {
|
if (speed < 0) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
error_set(errp, QERR_INVALID_PARAMETER, "speed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void backup_cleanup_sync_bitmap(BackupBlockJob *job, int ret)
|
static void backup_iostatus_reset(BlockJob *job)
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bm;
|
|
||||||
BlockDriverState *bs = blk_bs(job->common.blk);
|
|
||||||
|
|
||||||
if (ret < 0 || block_job_is_cancelled(&job->common)) {
|
|
||||||
/* Merge the successor back into the parent, delete nothing. */
|
|
||||||
bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
|
|
||||||
assert(bm);
|
|
||||||
} else {
|
|
||||||
/* Everything is fine, delete this bitmap and install the backup. */
|
|
||||||
bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
|
|
||||||
assert(bm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_commit(BlockJob *job)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
if (s->sync_bitmap) {
|
|
||||||
backup_cleanup_sync_bitmap(s, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_abort(BlockJob *job)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
if (s->sync_bitmap) {
|
|
||||||
backup_cleanup_sync_bitmap(s, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_clean(BlockJob *job)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
assert(s->target);
|
|
||||||
blk_unref(s->target);
|
|
||||||
s->target = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_attached_aio_context(BlockJob *job, AioContext *aio_context)
|
|
||||||
{
|
{
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||||
|
|
||||||
blk_set_aio_context(s->target, aio_context);
|
bdrv_iostatus_reset(s->target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void backup_do_checkpoint(BlockJob *job, Error **errp)
|
static const BlockJobDriver backup_job_driver = {
|
||||||
{
|
.instance_size = sizeof(BackupBlockJob),
|
||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
||||||
int64_t len;
|
.set_speed = backup_set_speed,
|
||||||
|
.iostatus_reset = backup_iostatus_reset,
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
};
|
||||||
|
|
||||||
if (backup_job->sync_mode != MIRROR_SYNC_MODE_NONE) {
|
|
||||||
error_setg(errp, "The backup job only supports block checkpoint in"
|
|
||||||
" sync=none mode");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = DIV_ROUND_UP(backup_job->common.len, backup_job->cluster_size);
|
|
||||||
bitmap_zero(backup_job->done_bitmap, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void backup_wait_for_overlapping_requests(BlockJob *job, int64_t sector_num,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
|
||||||
int64_t sectors_per_cluster = cluster_size_sectors(backup_job);
|
|
||||||
int64_t start, end;
|
|
||||||
|
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
|
||||||
|
|
||||||
start = sector_num / sectors_per_cluster;
|
|
||||||
end = DIV_ROUND_UP(sector_num + nb_sectors, sectors_per_cluster);
|
|
||||||
wait_for_overlapping_requests(backup_job, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
void backup_cow_request_begin(CowRequest *req, BlockJob *job,
|
|
||||||
int64_t sector_num,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
BackupBlockJob *backup_job = container_of(job, BackupBlockJob, common);
|
|
||||||
int64_t sectors_per_cluster = cluster_size_sectors(backup_job);
|
|
||||||
int64_t start, end;
|
|
||||||
|
|
||||||
assert(job->driver->job_type == BLOCK_JOB_TYPE_BACKUP);
|
|
||||||
|
|
||||||
start = sector_num / sectors_per_cluster;
|
|
||||||
end = DIV_ROUND_UP(sector_num + nb_sectors, sectors_per_cluster);
|
|
||||||
cow_request_begin(req, backup_job, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
void backup_cow_request_end(CowRequest *req)
|
|
||||||
{
|
|
||||||
cow_request_end(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void backup_drain(BlockJob *job)
|
|
||||||
{
|
|
||||||
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
|
||||||
|
|
||||||
/* Need to keep a reference in case blk_drain triggers execution
|
|
||||||
* of backup_complete...
|
|
||||||
*/
|
|
||||||
if (s->target) {
|
|
||||||
BlockBackend *target = s->target;
|
|
||||||
blk_ref(target);
|
|
||||||
blk_drain(target);
|
|
||||||
blk_unref(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockErrorAction backup_error_action(BackupBlockJob *job,
|
static BlockErrorAction backup_error_action(BackupBlockJob *job,
|
||||||
bool read, int error)
|
bool read, int error)
|
||||||
{
|
{
|
||||||
if (read) {
|
if (read) {
|
||||||
return block_job_error_action(&job->common, job->on_source_error,
|
return block_job_error_action(&job->common, job->common.bs,
|
||||||
true, error);
|
job->on_source_error, true, error);
|
||||||
} else {
|
} else {
|
||||||
return block_job_error_action(&job->common, job->on_target_error,
|
return block_job_error_action(&job->common, job->target,
|
||||||
false, error);
|
job->on_target_error, false, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,24 +233,66 @@ typedef struct {
|
|||||||
|
|
||||||
static void backup_complete(BlockJob *job, void *opaque)
|
static void backup_complete(BlockJob *job, void *opaque)
|
||||||
{
|
{
|
||||||
|
BackupBlockJob *s = container_of(job, BackupBlockJob, common);
|
||||||
BackupCompleteData *data = opaque;
|
BackupCompleteData *data = opaque;
|
||||||
|
|
||||||
|
bdrv_unref(s->target);
|
||||||
|
|
||||||
block_job_completed(job, data->ret);
|
block_job_completed(job, data->ret);
|
||||||
g_free(data);
|
g_free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
static void coroutine_fn backup_run(void *opaque)
|
||||||
{
|
{
|
||||||
|
BackupBlockJob *job = opaque;
|
||||||
|
BackupCompleteData *data;
|
||||||
|
BlockDriverState *bs = job->common.bs;
|
||||||
|
BlockDriverState *target = job->target;
|
||||||
|
BlockdevOnError on_target_error = job->on_target_error;
|
||||||
|
NotifierWithReturn before_write = {
|
||||||
|
.notify = backup_before_write_notify,
|
||||||
|
};
|
||||||
|
int64_t start, end;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
QLIST_INIT(&job->inflight_reqs);
|
||||||
|
qemu_co_rwlock_init(&job->flush_rwlock);
|
||||||
|
|
||||||
|
start = 0;
|
||||||
|
end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
|
||||||
|
BACKUP_SECTORS_PER_CLUSTER);
|
||||||
|
|
||||||
|
job->bitmap = hbitmap_alloc(end, 0);
|
||||||
|
|
||||||
|
bdrv_set_enable_write_cache(target, true);
|
||||||
|
bdrv_set_on_error(target, on_target_error, on_target_error);
|
||||||
|
bdrv_iostatus_enable(target);
|
||||||
|
|
||||||
|
bdrv_add_before_write_notifier(bs, &before_write);
|
||||||
|
|
||||||
|
if (job->sync_mode == MIRROR_SYNC_MODE_NONE) {
|
||||||
|
while (!block_job_is_cancelled(&job->common)) {
|
||||||
|
/* Yield until the job is cancelled. We just let our before_write
|
||||||
|
* notify callback service CoW requests. */
|
||||||
|
job->common.busy = false;
|
||||||
|
qemu_coroutine_yield();
|
||||||
|
job->common.busy = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
||||||
|
for (; start < end; start++) {
|
||||||
|
bool error_is_read;
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we need to yield so that bdrv_drain_all() returns.
|
/* we need to yield so that qemu_aio_flush() returns.
|
||||||
* (without, VM does not reboot)
|
* (without, VM does not reboot)
|
||||||
*/
|
*/
|
||||||
if (job->common.speed) {
|
if (job->common.speed) {
|
||||||
uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
|
uint64_t delay_ns = ratelimit_calculate_delay(
|
||||||
job->sectors_read);
|
&job->limit, job->sectors_read);
|
||||||
job->sectors_read = 0;
|
job->sectors_read = 0;
|
||||||
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
|
||||||
} else {
|
} else {
|
||||||
@@ -367,120 +300,17 @@ static bool coroutine_fn yield_and_check(BackupBlockJob *job)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (block_job_is_cancelled(&job->common)) {
|
if (block_job_is_cancelled(&job->common)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
|
|
||||||
{
|
|
||||||
bool error_is_read;
|
|
||||||
int ret = 0;
|
|
||||||
int clusters_per_iter;
|
|
||||||
uint32_t granularity;
|
|
||||||
int64_t sector;
|
|
||||||
int64_t cluster;
|
|
||||||
int64_t end;
|
|
||||||
int64_t last_cluster = -1;
|
|
||||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
|
||||||
BdrvDirtyBitmapIter *dbi;
|
|
||||||
|
|
||||||
granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
|
|
||||||
clusters_per_iter = MAX((granularity / job->cluster_size), 1);
|
|
||||||
dbi = bdrv_dirty_iter_new(job->sync_bitmap, 0);
|
|
||||||
|
|
||||||
/* Find the next dirty sector(s) */
|
|
||||||
while ((sector = bdrv_dirty_iter_next(dbi)) != -1) {
|
|
||||||
cluster = sector / sectors_per_cluster;
|
|
||||||
|
|
||||||
/* Fake progress updates for any clusters we skipped */
|
|
||||||
if (cluster != last_cluster + 1) {
|
|
||||||
job->common.offset += ((cluster - last_cluster - 1) *
|
|
||||||
job->cluster_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (end = cluster + clusters_per_iter; cluster < end; cluster++) {
|
|
||||||
do {
|
|
||||||
if (yield_and_check(job)) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
ret = backup_do_cow(job, cluster * sectors_per_cluster,
|
|
||||||
sectors_per_cluster, &error_is_read,
|
|
||||||
false);
|
|
||||||
if ((ret < 0) &&
|
|
||||||
backup_error_action(job, error_is_read, -ret) ==
|
|
||||||
BLOCK_ERROR_ACTION_REPORT) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
} while (ret < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the bitmap granularity is smaller than the backup granularity,
|
|
||||||
* we need to advance the iterator pointer to the next cluster. */
|
|
||||||
if (granularity < job->cluster_size) {
|
|
||||||
bdrv_set_dirty_iter(dbi, cluster * sectors_per_cluster);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_cluster = cluster - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Play some final catchup with the progress meter */
|
|
||||||
end = DIV_ROUND_UP(job->common.len, job->cluster_size);
|
|
||||||
if (last_cluster + 1 < end) {
|
|
||||||
job->common.offset += ((end - last_cluster - 1) * job->cluster_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
bdrv_dirty_iter_free(dbi);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void coroutine_fn backup_run(void *opaque)
|
|
||||||
{
|
|
||||||
BackupBlockJob *job = opaque;
|
|
||||||
BackupCompleteData *data;
|
|
||||||
BlockDriverState *bs = blk_bs(job->common.blk);
|
|
||||||
int64_t start, end;
|
|
||||||
int64_t sectors_per_cluster = cluster_size_sectors(job);
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
QLIST_INIT(&job->inflight_reqs);
|
|
||||||
qemu_co_rwlock_init(&job->flush_rwlock);
|
|
||||||
|
|
||||||
start = 0;
|
|
||||||
end = DIV_ROUND_UP(job->common.len, job->cluster_size);
|
|
||||||
|
|
||||||
job->done_bitmap = bitmap_new(end);
|
|
||||||
|
|
||||||
job->before_write.notify = backup_before_write_notify;
|
|
||||||
bdrv_add_before_write_notifier(bs, &job->before_write);
|
|
||||||
|
|
||||||
if (job->sync_mode == MIRROR_SYNC_MODE_NONE) {
|
|
||||||
while (!block_job_is_cancelled(&job->common)) {
|
|
||||||
/* Yield until the job is cancelled. We just let our before_write
|
|
||||||
* notify callback service CoW requests. */
|
|
||||||
block_job_yield(&job->common);
|
|
||||||
}
|
|
||||||
} else if (job->sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
|
|
||||||
ret = backup_run_incremental(job);
|
|
||||||
} else {
|
|
||||||
/* Both FULL and TOP SYNC_MODE's require copying.. */
|
|
||||||
for (; start < end; start++) {
|
|
||||||
bool error_is_read;
|
|
||||||
int alloced = 0;
|
|
||||||
|
|
||||||
if (yield_and_check(job)) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
if (job->sync_mode == MIRROR_SYNC_MODE_TOP) {
|
||||||
int i, n;
|
int i, n;
|
||||||
|
int alloced = 0;
|
||||||
|
|
||||||
/* Check to see if these blocks are already in the
|
/* Check to see if these blocks are already in the
|
||||||
* backing file. */
|
* backing file. */
|
||||||
|
|
||||||
for (i = 0; i < sectors_per_cluster;) {
|
for (i = 0; i < BACKUP_SECTORS_PER_CLUSTER;) {
|
||||||
/* bdrv_is_allocated() only returns true/false based
|
/* bdrv_is_allocated() only returns true/false based
|
||||||
* on the first set of sectors it comes across that
|
* on the first set of sectors it comes across that
|
||||||
* are are all in the same state.
|
* are are all in the same state.
|
||||||
@@ -489,11 +319,11 @@ static void coroutine_fn backup_run(void *opaque)
|
|||||||
* needed but at some point that is always the case. */
|
* needed but at some point that is always the case. */
|
||||||
alloced =
|
alloced =
|
||||||
bdrv_is_allocated(bs,
|
bdrv_is_allocated(bs,
|
||||||
start * sectors_per_cluster + i,
|
start * BACKUP_SECTORS_PER_CLUSTER + i,
|
||||||
sectors_per_cluster - i, &n);
|
BACKUP_SECTORS_PER_CLUSTER - i, &n);
|
||||||
i += n;
|
i += n;
|
||||||
|
|
||||||
if (alloced || n == 0) {
|
if (alloced == 1 || n == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,13 +335,8 @@ static void coroutine_fn backup_run(void *opaque)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* FULL sync mode we copy the whole drive. */
|
/* FULL sync mode we copy the whole drive. */
|
||||||
if (alloced < 0) {
|
ret = backup_do_cow(bs, start * BACKUP_SECTORS_PER_CLUSTER,
|
||||||
ret = alloced;
|
BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
|
||||||
} else {
|
|
||||||
ret = backup_do_cow(job, start * sectors_per_cluster,
|
|
||||||
sectors_per_cluster, &error_is_read,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* Depending on error action, fail now or retry cluster */
|
/* Depending on error action, fail now or retry cluster */
|
||||||
BlockErrorAction action =
|
BlockErrorAction action =
|
||||||
@@ -526,174 +351,87 @@ static void coroutine_fn backup_run(void *opaque)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifier_with_return_remove(&job->before_write);
|
notifier_with_return_remove(&before_write);
|
||||||
|
|
||||||
/* wait until pending backup_do_cow() calls have completed */
|
/* wait until pending backup_do_cow() calls have completed */
|
||||||
qemu_co_rwlock_wrlock(&job->flush_rwlock);
|
qemu_co_rwlock_wrlock(&job->flush_rwlock);
|
||||||
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
qemu_co_rwlock_unlock(&job->flush_rwlock);
|
||||||
g_free(job->done_bitmap);
|
|
||||||
|
hbitmap_free(job->bitmap);
|
||||||
|
|
||||||
|
bdrv_iostatus_disable(target);
|
||||||
|
bdrv_op_unblock_all(target, job->common.blocker);
|
||||||
|
|
||||||
data = g_malloc(sizeof(*data));
|
data = g_malloc(sizeof(*data));
|
||||||
data->ret = ret;
|
data->ret = ret;
|
||||||
block_job_defer_to_main_loop(&job->common, backup_complete, data);
|
block_job_defer_to_main_loop(&job->common, backup_complete, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const BlockJobDriver backup_job_driver = {
|
void backup_start(BlockDriverState *bs, BlockDriverState *target,
|
||||||
.instance_size = sizeof(BackupBlockJob),
|
int64_t speed, MirrorSyncMode sync_mode,
|
||||||
.job_type = BLOCK_JOB_TYPE_BACKUP,
|
|
||||||
.start = backup_run,
|
|
||||||
.set_speed = backup_set_speed,
|
|
||||||
.commit = backup_commit,
|
|
||||||
.abort = backup_abort,
|
|
||||||
.clean = backup_clean,
|
|
||||||
.attached_aio_context = backup_attached_aio_context,
|
|
||||||
.drain = backup_drain,
|
|
||||||
};
|
|
||||||
|
|
||||||
BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
|
|
||||||
BlockDriverState *target, int64_t speed,
|
|
||||||
MirrorSyncMode sync_mode, BdrvDirtyBitmap *sync_bitmap,
|
|
||||||
bool compress,
|
|
||||||
BlockdevOnError on_source_error,
|
BlockdevOnError on_source_error,
|
||||||
BlockdevOnError on_target_error,
|
BlockdevOnError on_target_error,
|
||||||
int creation_flags,
|
|
||||||
BlockCompletionFunc *cb, void *opaque,
|
BlockCompletionFunc *cb, void *opaque,
|
||||||
BlockJobTxn *txn, Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
int64_t len;
|
int64_t len;
|
||||||
BlockDriverInfo bdi;
|
|
||||||
BackupBlockJob *job = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
assert(bs);
|
assert(bs);
|
||||||
assert(target);
|
assert(target);
|
||||||
|
assert(cb);
|
||||||
|
|
||||||
if (bs == target) {
|
if (bs == target) {
|
||||||
error_setg(errp, "Source and target cannot be the same");
|
error_setg(errp, "Source and target cannot be the same");
|
||||||
return NULL;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((on_source_error == BLOCKDEV_ON_ERROR_STOP ||
|
||||||
|
on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
|
||||||
|
!bdrv_iostatus_is_enabled(bs)) {
|
||||||
|
error_set(errp, QERR_INVALID_PARAMETER, "on-source-error");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bdrv_is_inserted(bs)) {
|
if (!bdrv_is_inserted(bs)) {
|
||||||
error_setg(errp, "Device is not inserted: %s",
|
error_setg(errp, "Device is not inserted: %s",
|
||||||
bdrv_get_device_name(bs));
|
bdrv_get_device_name(bs));
|
||||||
return NULL;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bdrv_is_inserted(target)) {
|
if (!bdrv_is_inserted(target)) {
|
||||||
error_setg(errp, "Device is not inserted: %s",
|
error_setg(errp, "Device is not inserted: %s",
|
||||||
bdrv_get_device_name(target));
|
bdrv_get_device_name(target));
|
||||||
return NULL;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (compress && target->drv->bdrv_co_pwritev_compressed == NULL) {
|
|
||||||
error_setg(errp, "Compression is not supported for this drive %s",
|
|
||||||
bdrv_get_device_name(target));
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
|
||||||
return NULL;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
|
if (bdrv_op_is_blocked(target, BLOCK_OP_TYPE_BACKUP_TARGET, errp)) {
|
||||||
return NULL;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
|
|
||||||
if (!sync_bitmap) {
|
|
||||||
error_setg(errp, "must provide a valid bitmap name for "
|
|
||||||
"\"incremental\" sync mode");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create a new bitmap, and freeze/disable this one. */
|
|
||||||
if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} else if (sync_bitmap) {
|
|
||||||
error_setg(errp,
|
|
||||||
"a sync_bitmap was provided to backup_run, "
|
|
||||||
"but received an incompatible sync_mode (%s)",
|
|
||||||
MirrorSyncMode_lookup[sync_mode]);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
len = bdrv_getlength(bs);
|
len = bdrv_getlength(bs);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
error_setg_errno(errp, -len, "unable to get length for '%s'",
|
||||||
bdrv_get_device_name(bs));
|
bdrv_get_device_name(bs));
|
||||||
goto error;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* job->common.len is fixed, so we can't allow resize */
|
BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
|
||||||
job = block_job_create(job_id, &backup_job_driver, bs,
|
cb, opaque, errp);
|
||||||
BLK_PERM_CONSISTENT_READ,
|
|
||||||
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
|
||||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD,
|
|
||||||
speed, creation_flags, cb, opaque, errp);
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
goto error;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The target must match the source in size, so no resize here either */
|
bdrv_op_block_all(target, job->common.blocker);
|
||||||
job->target = blk_new(BLK_PERM_WRITE,
|
|
||||||
BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE |
|
|
||||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_GRAPH_MOD);
|
|
||||||
ret = blk_insert_bs(job->target, target, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
job->on_source_error = on_source_error;
|
job->on_source_error = on_source_error;
|
||||||
job->on_target_error = on_target_error;
|
job->on_target_error = on_target_error;
|
||||||
|
job->target = target;
|
||||||
job->sync_mode = sync_mode;
|
job->sync_mode = sync_mode;
|
||||||
job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_INCREMENTAL ?
|
|
||||||
sync_bitmap : NULL;
|
|
||||||
job->compress = compress;
|
|
||||||
|
|
||||||
/* If there is no backing file on the target, we cannot rely on COW if our
|
|
||||||
* backup cluster size is smaller than the target cluster size. Even for
|
|
||||||
* targets with a backing file, try to avoid COW if possible. */
|
|
||||||
ret = bdrv_get_info(target, &bdi);
|
|
||||||
if (ret == -ENOTSUP && !target->backing) {
|
|
||||||
/* Cluster size is not defined */
|
|
||||||
error_report("WARNING: The target block device doesn't provide "
|
|
||||||
"information about the block size and it doesn't have a "
|
|
||||||
"backing file. The default block size of %u bytes is "
|
|
||||||
"used. If the actual block size of the target exceeds "
|
|
||||||
"this default, the backup may be unusable",
|
|
||||||
BACKUP_CLUSTER_SIZE_DEFAULT);
|
|
||||||
job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
|
|
||||||
} else if (ret < 0 && !target->backing) {
|
|
||||||
error_setg_errno(errp, -ret,
|
|
||||||
"Couldn't determine the cluster size of the target image, "
|
|
||||||
"which has no backing file");
|
|
||||||
error_append_hint(errp,
|
|
||||||
"Aborting, since this may create an unusable destination image\n");
|
|
||||||
goto error;
|
|
||||||
} else if (ret < 0 && target->backing) {
|
|
||||||
/* Not fatal; just trudge on ahead. */
|
|
||||||
job->cluster_size = BACKUP_CLUSTER_SIZE_DEFAULT;
|
|
||||||
} else {
|
|
||||||
job->cluster_size = MAX(BACKUP_CLUSTER_SIZE_DEFAULT, bdi.cluster_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Required permissions are already taken with target's blk_new() */
|
|
||||||
block_job_add_bdrv(&job->common, "target", target, 0, BLK_PERM_ALL,
|
|
||||||
&error_abort);
|
|
||||||
job->common.len = len;
|
job->common.len = len;
|
||||||
block_job_txn_add_job(txn, &job->common);
|
job->common.co = qemu_coroutine_create(backup_run);
|
||||||
|
qemu_coroutine_enter(job->common.co, job);
|
||||||
return &job->common;
|
|
||||||
|
|
||||||
error:
|
|
||||||
if (sync_bitmap) {
|
|
||||||
bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
|
|
||||||
}
|
|
||||||
if (job) {
|
|
||||||
backup_clean(&job->common);
|
|
||||||
block_job_early_fail(&job->common);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|||||||
517
block/blkdebug.c
517
block/blkdebug.c
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Block protocol for I/O error injection
|
* Block protocol for I/O error injection
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016-2017 Red Hat, Inc.
|
|
||||||
* Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com>
|
* Copyright (c) 2010 Kevin Wolf <kwolf@redhat.com>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
@@ -23,37 +22,27 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu-common.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qapi/qmp/qbool.h"
|
#include "qapi/qmp/qbool.h"
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
|
#include "qapi/qmp/qint.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
#include "sysemu/qtest.h"
|
|
||||||
|
|
||||||
typedef struct BDRVBlkdebugState {
|
typedef struct BDRVBlkdebugState {
|
||||||
int state;
|
int state;
|
||||||
int new_state;
|
int new_state;
|
||||||
uint64_t align;
|
|
||||||
uint64_t max_transfer;
|
|
||||||
uint64_t opt_write_zero;
|
|
||||||
uint64_t max_write_zero;
|
|
||||||
uint64_t opt_discard;
|
|
||||||
uint64_t max_discard;
|
|
||||||
|
|
||||||
/* For blkdebug_refresh_filename() */
|
QLIST_HEAD(, BlkdebugRule) rules[BLKDBG_EVENT_MAX];
|
||||||
char *config_file;
|
|
||||||
|
|
||||||
QLIST_HEAD(, BlkdebugRule) rules[BLKDBG__MAX];
|
|
||||||
QSIMPLEQ_HEAD(, BlkdebugRule) active_rules;
|
QSIMPLEQ_HEAD(, BlkdebugRule) active_rules;
|
||||||
QLIST_HEAD(, BlkdebugSuspendedReq) suspended_reqs;
|
QLIST_HEAD(, BlkdebugSuspendedReq) suspended_reqs;
|
||||||
} BDRVBlkdebugState;
|
} BDRVBlkdebugState;
|
||||||
|
|
||||||
typedef struct BlkdebugAIOCB {
|
typedef struct BlkdebugAIOCB {
|
||||||
BlockAIOCB common;
|
BlockAIOCB common;
|
||||||
|
QEMUBH *bh;
|
||||||
int ret;
|
int ret;
|
||||||
} BlkdebugAIOCB;
|
} BlkdebugAIOCB;
|
||||||
|
|
||||||
@@ -63,6 +52,10 @@ typedef struct BlkdebugSuspendedReq {
|
|||||||
QLIST_ENTRY(BlkdebugSuspendedReq) next;
|
QLIST_ENTRY(BlkdebugSuspendedReq) next;
|
||||||
} BlkdebugSuspendedReq;
|
} BlkdebugSuspendedReq;
|
||||||
|
|
||||||
|
static const AIOCBInfo blkdebug_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(BlkdebugAIOCB),
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
ACTION_INJECT_ERROR,
|
ACTION_INJECT_ERROR,
|
||||||
ACTION_SET_STATE,
|
ACTION_SET_STATE,
|
||||||
@@ -70,7 +63,7 @@ enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
typedef struct BlkdebugRule {
|
typedef struct BlkdebugRule {
|
||||||
BlkdebugEvent event;
|
BlkDebugEvent event;
|
||||||
int action;
|
int action;
|
||||||
int state;
|
int state;
|
||||||
union {
|
union {
|
||||||
@@ -78,7 +71,7 @@ typedef struct BlkdebugRule {
|
|||||||
int error;
|
int error;
|
||||||
int immediately;
|
int immediately;
|
||||||
int once;
|
int once;
|
||||||
int64_t offset;
|
int64_t sector;
|
||||||
} inject;
|
} inject;
|
||||||
struct {
|
struct {
|
||||||
int new_state;
|
int new_state;
|
||||||
@@ -149,12 +142,69 @@ static QemuOptsList *config_groups[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static int get_event_by_name(const char *name, BlkdebugEvent *event)
|
static const char *event_names[BLKDBG_EVENT_MAX] = {
|
||||||
|
[BLKDBG_L1_UPDATE] = "l1_update",
|
||||||
|
[BLKDBG_L1_GROW_ALLOC_TABLE] = "l1_grow.alloc_table",
|
||||||
|
[BLKDBG_L1_GROW_WRITE_TABLE] = "l1_grow.write_table",
|
||||||
|
[BLKDBG_L1_GROW_ACTIVATE_TABLE] = "l1_grow.activate_table",
|
||||||
|
|
||||||
|
[BLKDBG_L2_LOAD] = "l2_load",
|
||||||
|
[BLKDBG_L2_UPDATE] = "l2_update",
|
||||||
|
[BLKDBG_L2_UPDATE_COMPRESSED] = "l2_update_compressed",
|
||||||
|
[BLKDBG_L2_ALLOC_COW_READ] = "l2_alloc.cow_read",
|
||||||
|
[BLKDBG_L2_ALLOC_WRITE] = "l2_alloc.write",
|
||||||
|
|
||||||
|
[BLKDBG_READ_AIO] = "read_aio",
|
||||||
|
[BLKDBG_READ_BACKING_AIO] = "read_backing_aio",
|
||||||
|
[BLKDBG_READ_COMPRESSED] = "read_compressed",
|
||||||
|
|
||||||
|
[BLKDBG_WRITE_AIO] = "write_aio",
|
||||||
|
[BLKDBG_WRITE_COMPRESSED] = "write_compressed",
|
||||||
|
|
||||||
|
[BLKDBG_VMSTATE_LOAD] = "vmstate_load",
|
||||||
|
[BLKDBG_VMSTATE_SAVE] = "vmstate_save",
|
||||||
|
|
||||||
|
[BLKDBG_COW_READ] = "cow_read",
|
||||||
|
[BLKDBG_COW_WRITE] = "cow_write",
|
||||||
|
|
||||||
|
[BLKDBG_REFTABLE_LOAD] = "reftable_load",
|
||||||
|
[BLKDBG_REFTABLE_GROW] = "reftable_grow",
|
||||||
|
[BLKDBG_REFTABLE_UPDATE] = "reftable_update",
|
||||||
|
|
||||||
|
[BLKDBG_REFBLOCK_LOAD] = "refblock_load",
|
||||||
|
[BLKDBG_REFBLOCK_UPDATE] = "refblock_update",
|
||||||
|
[BLKDBG_REFBLOCK_UPDATE_PART] = "refblock_update_part",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC] = "refblock_alloc",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC_HOOKUP] = "refblock_alloc.hookup",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC_WRITE] = "refblock_alloc.write",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS] = "refblock_alloc.write_blocks",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC_WRITE_TABLE] = "refblock_alloc.write_table",
|
||||||
|
[BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE] = "refblock_alloc.switch_table",
|
||||||
|
|
||||||
|
[BLKDBG_CLUSTER_ALLOC] = "cluster_alloc",
|
||||||
|
[BLKDBG_CLUSTER_ALLOC_BYTES] = "cluster_alloc_bytes",
|
||||||
|
[BLKDBG_CLUSTER_FREE] = "cluster_free",
|
||||||
|
|
||||||
|
[BLKDBG_FLUSH_TO_OS] = "flush_to_os",
|
||||||
|
[BLKDBG_FLUSH_TO_DISK] = "flush_to_disk",
|
||||||
|
|
||||||
|
[BLKDBG_PWRITEV_RMW_HEAD] = "pwritev_rmw.head",
|
||||||
|
[BLKDBG_PWRITEV_RMW_AFTER_HEAD] = "pwritev_rmw.after_head",
|
||||||
|
[BLKDBG_PWRITEV_RMW_TAIL] = "pwritev_rmw.tail",
|
||||||
|
[BLKDBG_PWRITEV_RMW_AFTER_TAIL] = "pwritev_rmw.after_tail",
|
||||||
|
[BLKDBG_PWRITEV] = "pwritev",
|
||||||
|
[BLKDBG_PWRITEV_ZERO] = "pwritev_zero",
|
||||||
|
[BLKDBG_PWRITEV_DONE] = "pwritev_done",
|
||||||
|
|
||||||
|
[BLKDBG_EMPTY_IMAGE_PREPARE] = "empty_image_prepare",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int get_event_by_name(const char *name, BlkDebugEvent *event)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < BLKDBG__MAX; i++) {
|
for (i = 0; i < BLKDBG_EVENT_MAX; i++) {
|
||||||
if (!strcmp(BlkdebugEvent_lookup[i], name)) {
|
if (!strcmp(event_names[i], name)) {
|
||||||
*event = i;
|
*event = i;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -166,24 +216,24 @@ static int get_event_by_name(const char *name, BlkdebugEvent *event)
|
|||||||
struct add_rule_data {
|
struct add_rule_data {
|
||||||
BDRVBlkdebugState *s;
|
BDRVBlkdebugState *s;
|
||||||
int action;
|
int action;
|
||||||
|
Error **errp;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
|
static int add_rule(QemuOpts *opts, void *opaque)
|
||||||
{
|
{
|
||||||
struct add_rule_data *d = opaque;
|
struct add_rule_data *d = opaque;
|
||||||
BDRVBlkdebugState *s = d->s;
|
BDRVBlkdebugState *s = d->s;
|
||||||
const char* event_name;
|
const char* event_name;
|
||||||
BlkdebugEvent event;
|
BlkDebugEvent event;
|
||||||
struct BlkdebugRule *rule;
|
struct BlkdebugRule *rule;
|
||||||
int64_t sector;
|
|
||||||
|
|
||||||
/* Find the right event for the rule */
|
/* Find the right event for the rule */
|
||||||
event_name = qemu_opt_get(opts, "event");
|
event_name = qemu_opt_get(opts, "event");
|
||||||
if (!event_name) {
|
if (!event_name) {
|
||||||
error_setg(errp, "Missing event name for rule");
|
error_setg(d->errp, "Missing event name for rule");
|
||||||
return -1;
|
return -1;
|
||||||
} else if (get_event_by_name(event_name, &event) < 0) {
|
} else if (get_event_by_name(event_name, &event) < 0) {
|
||||||
error_setg(errp, "Invalid event name \"%s\"", event_name);
|
error_setg(d->errp, "Invalid event name \"%s\"", event_name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,9 +252,7 @@ static int add_rule(void *opaque, QemuOpts *opts, Error **errp)
|
|||||||
rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0);
|
rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0);
|
||||||
rule->options.inject.immediately =
|
rule->options.inject.immediately =
|
||||||
qemu_opt_get_bool(opts, "immediately", 0);
|
qemu_opt_get_bool(opts, "immediately", 0);
|
||||||
sector = qemu_opt_get_number(opts, "sector", -1);
|
rule->options.inject.sector = qemu_opt_get_number(opts, "sector", -1);
|
||||||
rule->options.inject.offset =
|
|
||||||
sector == -1 ? -1 : sector * BDRV_SECTOR_SIZE;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ACTION_SET_STATE:
|
case ACTION_SET_STATE:
|
||||||
@@ -271,7 +319,8 @@ static int read_config(BDRVBlkdebugState *s, const char *filename,
|
|||||||
|
|
||||||
d.s = s;
|
d.s = s;
|
||||||
d.action = ACTION_INJECT_ERROR;
|
d.action = ACTION_INJECT_ERROR;
|
||||||
qemu_opts_foreach(&inject_error_opts, add_rule, &d, &local_err);
|
d.errp = &local_err;
|
||||||
|
qemu_opts_foreach(&inject_error_opts, add_rule, &d, 1);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
@@ -279,7 +328,7 @@ static int read_config(BDRVBlkdebugState *s, const char *filename,
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.action = ACTION_SET_STATE;
|
d.action = ACTION_SET_STATE;
|
||||||
qemu_opts_foreach(&set_state_opts, add_rule, &d, &local_err);
|
qemu_opts_foreach(&set_state_opts, add_rule, &d, 1);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
@@ -306,7 +355,7 @@ static void blkdebug_parse_filename(const char *filename, QDict *options,
|
|||||||
if (!strstart(filename, "blkdebug:", &filename)) {
|
if (!strstart(filename, "blkdebug:", &filename)) {
|
||||||
/* There was no prefix; therefore, all options have to be already
|
/* There was no prefix; therefore, all options have to be already
|
||||||
present in the QDict (except for the filename) */
|
present in the QDict (except for the filename) */
|
||||||
qdict_put_str(options, "x-image", filename);
|
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +374,7 @@ static void blkdebug_parse_filename(const char *filename, QDict *options,
|
|||||||
|
|
||||||
/* TODO Allow multi-level nesting and set file.filename here */
|
/* TODO Allow multi-level nesting and set file.filename here */
|
||||||
filename = c + 1;
|
filename = c + 1;
|
||||||
qdict_put_str(options, "x-image", filename);
|
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
static QemuOptsList runtime_opts = {
|
||||||
@@ -347,31 +396,6 @@ static QemuOptsList runtime_opts = {
|
|||||||
.type = QEMU_OPT_SIZE,
|
.type = QEMU_OPT_SIZE,
|
||||||
.help = "Required alignment in bytes",
|
.help = "Required alignment in bytes",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.name = "max-transfer",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Maximum transfer size in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "opt-write-zero",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Optimum write zero alignment in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "max-write-zero",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Maximum write zero size in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "opt-discard",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Optimum discard alignment in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "max-discard",
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Maximum discard size in bytes",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -382,8 +406,9 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
QemuOpts *opts;
|
QemuOpts *opts;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
int ret;
|
const char *config;
|
||||||
uint64_t align;
|
uint64_t align;
|
||||||
|
int ret;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||||
@@ -394,8 +419,8 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Read rules from config file or command line options */
|
/* Read rules from config file or command line options */
|
||||||
s->config_file = g_strdup(qemu_opt_get(opts, "config"));
|
config = qemu_opt_get(opts, "config");
|
||||||
ret = read_config(s, s->config_file, options, errp);
|
ret = read_config(s, config, options, errp);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -403,243 +428,134 @@ static int blkdebug_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
/* Set initial state */
|
/* Set initial state */
|
||||||
s->state = 1;
|
s->state = 1;
|
||||||
|
|
||||||
/* Open the image file */
|
/* Open the backing file */
|
||||||
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options, "image",
|
assert(bs->file == NULL);
|
||||||
bs, &child_file, false, &local_err);
|
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-image"), options, "image",
|
||||||
if (local_err) {
|
flags | BDRV_O_PROTOCOL, false, &local_err);
|
||||||
ret = -EINVAL;
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bs->supported_write_flags = BDRV_REQ_FUA &
|
/* Set request alignment */
|
||||||
bs->file->bs->supported_write_flags;
|
align = qemu_opt_get_size(opts, "align", bs->request_alignment);
|
||||||
bs->supported_zero_flags = (BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP) &
|
if (align > 0 && align < INT_MAX && !(align & (align - 1))) {
|
||||||
bs->file->bs->supported_zero_flags;
|
bs->request_alignment = align;
|
||||||
|
} else {
|
||||||
|
error_setg(errp, "Invalid alignment");
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
|
goto fail_unref;
|
||||||
/* Set alignment overrides */
|
|
||||||
s->align = qemu_opt_get_size(opts, "align", 0);
|
|
||||||
if (s->align && (s->align >= INT_MAX || !is_power_of_2(s->align))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with align %" PRIu64,
|
|
||||||
s->align);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
align = MAX(s->align, bs->file->bs->bl.request_alignment);
|
|
||||||
|
|
||||||
s->max_transfer = qemu_opt_get_size(opts, "max-transfer", 0);
|
|
||||||
if (s->max_transfer &&
|
|
||||||
(s->max_transfer >= INT_MAX ||
|
|
||||||
!QEMU_IS_ALIGNED(s->max_transfer, align))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with max-transfer %" PRIu64,
|
|
||||||
s->max_transfer);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->opt_write_zero = qemu_opt_get_size(opts, "opt-write-zero", 0);
|
|
||||||
if (s->opt_write_zero &&
|
|
||||||
(s->opt_write_zero >= INT_MAX ||
|
|
||||||
!QEMU_IS_ALIGNED(s->opt_write_zero, align))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with opt-write-zero %" PRIu64,
|
|
||||||
s->opt_write_zero);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->max_write_zero = qemu_opt_get_size(opts, "max-write-zero", 0);
|
|
||||||
if (s->max_write_zero &&
|
|
||||||
(s->max_write_zero >= INT_MAX ||
|
|
||||||
!QEMU_IS_ALIGNED(s->max_write_zero,
|
|
||||||
MAX(s->opt_write_zero, align)))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with max-write-zero %" PRIu64,
|
|
||||||
s->max_write_zero);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->opt_discard = qemu_opt_get_size(opts, "opt-discard", 0);
|
|
||||||
if (s->opt_discard &&
|
|
||||||
(s->opt_discard >= INT_MAX ||
|
|
||||||
!QEMU_IS_ALIGNED(s->opt_discard, align))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with opt-discard %" PRIu64,
|
|
||||||
s->opt_discard);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->max_discard = qemu_opt_get_size(opts, "max-discard", 0);
|
|
||||||
if (s->max_discard &&
|
|
||||||
(s->max_discard >= INT_MAX ||
|
|
||||||
!QEMU_IS_ALIGNED(s->max_discard,
|
|
||||||
MAX(s->opt_discard, align)))) {
|
|
||||||
error_setg(errp, "Cannot meet constraints with max-discard %" PRIu64,
|
|
||||||
s->max_discard);
|
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
fail_unref:
|
||||||
|
bdrv_unref(bs->file);
|
||||||
out:
|
out:
|
||||||
if (ret < 0) {
|
|
||||||
g_free(s->config_file);
|
|
||||||
}
|
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rule_check(BlockDriverState *bs, uint64_t offset, uint64_t bytes)
|
static void error_callback_bh(void *opaque)
|
||||||
|
{
|
||||||
|
struct BlkdebugAIOCB *acb = opaque;
|
||||||
|
qemu_bh_delete(acb->bh);
|
||||||
|
acb->common.cb(acb->common.opaque, acb->ret);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockAIOCB *inject_error(BlockDriverState *bs,
|
||||||
|
BlockCompletionFunc *cb, void *opaque, BlkdebugRule *rule)
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
BlkdebugRule *rule = NULL;
|
int error = rule->options.inject.error;
|
||||||
int error;
|
struct BlkdebugAIOCB *acb;
|
||||||
bool immediately;
|
QEMUBH *bh;
|
||||||
|
bool immediately = rule->options.inject.immediately;
|
||||||
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
|
||||||
uint64_t inject_offset = rule->options.inject.offset;
|
|
||||||
|
|
||||||
if (inject_offset == -1 ||
|
|
||||||
(bytes && inject_offset >= offset &&
|
|
||||||
inject_offset < offset + bytes))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rule || !rule->options.inject.error) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
immediately = rule->options.inject.immediately;
|
|
||||||
error = rule->options.inject.error;
|
|
||||||
|
|
||||||
if (rule->options.inject.once) {
|
if (rule->options.inject.once) {
|
||||||
QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
|
QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
|
||||||
remove_rule(rule);
|
remove_rule(rule);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!immediately) {
|
if (immediately) {
|
||||||
aio_co_schedule(qemu_get_current_aio_context(), qemu_coroutine_self());
|
return NULL;
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return -error;
|
acb = qemu_aio_get(&blkdebug_aiocb_info, bs, cb, opaque);
|
||||||
|
acb->ret = -error;
|
||||||
|
|
||||||
|
bh = aio_bh_new(bdrv_get_aio_context(bs), error_callback_bh, acb);
|
||||||
|
acb->bh = bh;
|
||||||
|
qemu_bh_schedule(bh);
|
||||||
|
|
||||||
|
return &acb->common;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static BlockAIOCB *blkdebug_aio_readv(BlockDriverState *bs,
|
||||||
blkdebug_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
QEMUIOVector *qiov, int flags)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
int err;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
|
BlkdebugRule *rule = NULL;
|
||||||
|
|
||||||
/* Sanity check block layer guarantees */
|
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
if (rule->options.inject.sector == -1 ||
|
||||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
(rule->options.inject.sector >= sector_num &&
|
||||||
if (bs->bl.max_transfer) {
|
rule->options.inject.sector < sector_num + nb_sectors)) {
|
||||||
assert(bytes <= bs->bl.max_transfer);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rule_check(bs, offset, bytes);
|
if (rule && rule->options.inject.error) {
|
||||||
if (err) {
|
return inject_error(bs, cb, opaque, rule);
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
return bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static BlockAIOCB *blkdebug_aio_writev(BlockDriverState *bs,
|
||||||
blkdebug_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
QEMUIOVector *qiov, int flags)
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
int err;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
|
BlkdebugRule *rule = NULL;
|
||||||
|
|
||||||
/* Sanity check block layer guarantees */
|
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
if (rule->options.inject.sector == -1 ||
|
||||||
assert(QEMU_IS_ALIGNED(bytes, bs->bl.request_alignment));
|
(rule->options.inject.sector >= sector_num &&
|
||||||
if (bs->bl.max_transfer) {
|
rule->options.inject.sector < sector_num + nb_sectors)) {
|
||||||
assert(bytes <= bs->bl.max_transfer);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rule_check(bs, offset, bytes);
|
if (rule && rule->options.inject.error) {
|
||||||
if (err) {
|
return inject_error(bs, cb, opaque, rule);
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int blkdebug_co_flush(BlockDriverState *bs)
|
static BlockAIOCB *blkdebug_aio_flush(BlockDriverState *bs,
|
||||||
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
int err = rule_check(bs, 0, 0);
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
|
BlkdebugRule *rule = NULL;
|
||||||
|
|
||||||
if (err) {
|
QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) {
|
||||||
return err;
|
if (rule->options.inject.sector == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bdrv_co_flush(bs->file->bs);
|
if (rule && rule->options.inject.error) {
|
||||||
|
return inject_error(bs, cb, opaque, rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdrv_aio_flush(bs->file, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn blkdebug_co_pwrite_zeroes(BlockDriverState *bs,
|
|
||||||
int64_t offset, int count,
|
|
||||||
BdrvRequestFlags flags)
|
|
||||||
{
|
|
||||||
uint32_t align = MAX(bs->bl.request_alignment,
|
|
||||||
bs->bl.pwrite_zeroes_alignment);
|
|
||||||
int err;
|
|
||||||
|
|
||||||
/* Only pass through requests that are larger than requested
|
|
||||||
* preferred alignment (so that we test the fallback to writes on
|
|
||||||
* unaligned portions), and check that the block layer never hands
|
|
||||||
* us anything unaligned that crosses an alignment boundary. */
|
|
||||||
if (count < align) {
|
|
||||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
|
||||||
QEMU_IS_ALIGNED(offset + count, align) ||
|
|
||||||
DIV_ROUND_UP(offset, align) ==
|
|
||||||
DIV_ROUND_UP(offset + count, align));
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
assert(QEMU_IS_ALIGNED(offset, align));
|
|
||||||
assert(QEMU_IS_ALIGNED(count, align));
|
|
||||||
if (bs->bl.max_pwrite_zeroes) {
|
|
||||||
assert(count <= bs->bl.max_pwrite_zeroes);
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rule_check(bs, offset, count);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkdebug_co_pdiscard(BlockDriverState *bs,
|
|
||||||
int64_t offset, int count)
|
|
||||||
{
|
|
||||||
uint32_t align = bs->bl.pdiscard_alignment;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
/* Only pass through requests that are larger than requested
|
|
||||||
* minimum alignment, and ensure that unaligned requests do not
|
|
||||||
* cross optimum discard boundaries. */
|
|
||||||
if (count < bs->bl.request_alignment) {
|
|
||||||
assert(QEMU_IS_ALIGNED(offset, align) ||
|
|
||||||
QEMU_IS_ALIGNED(offset + count, align) ||
|
|
||||||
DIV_ROUND_UP(offset, align) ==
|
|
||||||
DIV_ROUND_UP(offset + count, align));
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
assert(QEMU_IS_ALIGNED(offset, bs->bl.request_alignment));
|
|
||||||
assert(QEMU_IS_ALIGNED(count, bs->bl.request_alignment));
|
|
||||||
if (align && count >= align) {
|
|
||||||
assert(QEMU_IS_ALIGNED(offset, align));
|
|
||||||
assert(QEMU_IS_ALIGNED(count, align));
|
|
||||||
}
|
|
||||||
if (bs->bl.max_pdiscard) {
|
|
||||||
assert(count <= bs->bl.max_pdiscard);
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rule_check(bs, offset, count);
|
|
||||||
if (err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bdrv_co_pdiscard(bs->file->bs, offset, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void blkdebug_close(BlockDriverState *bs)
|
static void blkdebug_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
@@ -647,13 +563,11 @@ static void blkdebug_close(BlockDriverState *bs)
|
|||||||
BlkdebugRule *rule, *next;
|
BlkdebugRule *rule, *next;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < BLKDBG__MAX; i++) {
|
for (i = 0; i < BLKDBG_EVENT_MAX; i++) {
|
||||||
QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
|
QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
|
||||||
remove_rule(rule);
|
remove_rule(rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free(s->config_file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void suspend_request(BlockDriverState *bs, BlkdebugRule *rule)
|
static void suspend_request(BlockDriverState *bs, BlkdebugRule *rule)
|
||||||
@@ -669,13 +583,9 @@ static void suspend_request(BlockDriverState *bs, BlkdebugRule *rule)
|
|||||||
remove_rule(rule);
|
remove_rule(rule);
|
||||||
QLIST_INSERT_HEAD(&s->suspended_reqs, &r, next);
|
QLIST_INSERT_HEAD(&s->suspended_reqs, &r, next);
|
||||||
|
|
||||||
if (!qtest_enabled()) {
|
|
||||||
printf("blkdebug: Suspended request '%s'\n", r.tag);
|
printf("blkdebug: Suspended request '%s'\n", r.tag);
|
||||||
}
|
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
if (!qtest_enabled()) {
|
|
||||||
printf("blkdebug: Resuming request '%s'\n", r.tag);
|
printf("blkdebug: Resuming request '%s'\n", r.tag);
|
||||||
}
|
|
||||||
|
|
||||||
QLIST_REMOVE(&r, next);
|
QLIST_REMOVE(&r, next);
|
||||||
g_free(r.tag);
|
g_free(r.tag);
|
||||||
@@ -712,13 +622,13 @@ static bool process_rule(BlockDriverState *bs, struct BlkdebugRule *rule,
|
|||||||
return injected;
|
return injected;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkdebug_debug_event(BlockDriverState *bs, BlkdebugEvent event)
|
static void blkdebug_debug_event(BlockDriverState *bs, BlkDebugEvent event)
|
||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
struct BlkdebugRule *rule, *next;
|
struct BlkdebugRule *rule, *next;
|
||||||
bool injected;
|
bool injected;
|
||||||
|
|
||||||
assert((int)event >= 0 && event < BLKDBG__MAX);
|
assert((int)event >= 0 && event < BLKDBG_EVENT_MAX);
|
||||||
|
|
||||||
injected = false;
|
injected = false;
|
||||||
s->new_state = s->state;
|
s->new_state = s->state;
|
||||||
@@ -733,7 +643,7 @@ static int blkdebug_debug_breakpoint(BlockDriverState *bs, const char *event,
|
|||||||
{
|
{
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
BDRVBlkdebugState *s = bs->opaque;
|
||||||
struct BlkdebugRule *rule;
|
struct BlkdebugRule *rule;
|
||||||
BlkdebugEvent blkdebug_event;
|
BlkDebugEvent blkdebug_event;
|
||||||
|
|
||||||
if (get_event_by_name(event, &blkdebug_event) < 0) {
|
if (get_event_by_name(event, &blkdebug_event) < 0) {
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
@@ -760,7 +670,7 @@ static int blkdebug_debug_resume(BlockDriverState *bs, const char *tag)
|
|||||||
|
|
||||||
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, next) {
|
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, next) {
|
||||||
if (!strcmp(r->tag, tag)) {
|
if (!strcmp(r->tag, tag)) {
|
||||||
qemu_coroutine_enter(r->co);
|
qemu_coroutine_enter(r->co, NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -775,7 +685,7 @@ static int blkdebug_debug_remove_breakpoint(BlockDriverState *bs,
|
|||||||
BlkdebugRule *rule, *next;
|
BlkdebugRule *rule, *next;
|
||||||
int i, ret = -ENOENT;
|
int i, ret = -ENOENT;
|
||||||
|
|
||||||
for (i = 0; i < BLKDBG__MAX; i++) {
|
for (i = 0; i < BLKDBG_EVENT_MAX; i++) {
|
||||||
QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
|
QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) {
|
||||||
if (rule->action == ACTION_SUSPEND &&
|
if (rule->action == ACTION_SUSPEND &&
|
||||||
!strcmp(rule->options.suspend.tag, tag)) {
|
!strcmp(rule->options.suspend.tag, tag)) {
|
||||||
@@ -786,7 +696,7 @@ static int blkdebug_debug_remove_breakpoint(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, r_next) {
|
QLIST_FOREACH_SAFE(r, &s->suspended_reqs, next, r_next) {
|
||||||
if (!strcmp(r->tag, tag)) {
|
if (!strcmp(r->tag, tag)) {
|
||||||
qemu_coroutine_enter(r->co);
|
qemu_coroutine_enter(r->co, NULL);
|
||||||
ret = 0;
|
ret = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,50 +718,50 @@ static bool blkdebug_debug_is_suspended(BlockDriverState *bs, const char *tag)
|
|||||||
|
|
||||||
static int64_t blkdebug_getlength(BlockDriverState *bs)
|
static int64_t blkdebug_getlength(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
return bdrv_getlength(bs->file->bs);
|
return bdrv_getlength(bs->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int blkdebug_truncate(BlockDriverState *bs, int64_t offset, Error **errp)
|
static void blkdebug_refresh_filename(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
return bdrv_truncate(bs->file, offset, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
|
|
||||||
{
|
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
|
||||||
QDict *opts;
|
QDict *opts;
|
||||||
const QDictEntry *e;
|
const QDictEntry *e;
|
||||||
bool force_json = false;
|
bool force_json = false;
|
||||||
|
|
||||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
for (e = qdict_first(bs->options); e; e = qdict_next(bs->options, e)) {
|
||||||
if (strcmp(qdict_entry_key(e), "config") &&
|
if (strcmp(qdict_entry_key(e), "config") &&
|
||||||
strcmp(qdict_entry_key(e), "x-image"))
|
strcmp(qdict_entry_key(e), "x-image") &&
|
||||||
|
strcmp(qdict_entry_key(e), "image") &&
|
||||||
|
strncmp(qdict_entry_key(e), "image.", strlen("image.")))
|
||||||
{
|
{
|
||||||
force_json = true;
|
force_json = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force_json && !bs->file->bs->full_open_options) {
|
if (force_json && !bs->file->full_open_options) {
|
||||||
/* The config file cannot be recreated, so creating a plain filename
|
/* The config file cannot be recreated, so creating a plain filename
|
||||||
* is impossible */
|
* is impossible */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force_json && bs->file->bs->exact_filename[0]) {
|
if (!force_json && bs->file->exact_filename[0]) {
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"blkdebug:%s:%s", s->config_file ?: "",
|
"blkdebug:%s:%s",
|
||||||
bs->file->bs->exact_filename);
|
qdict_get_try_str(bs->options, "config") ?: "",
|
||||||
|
bs->file->exact_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = qdict_new();
|
opts = qdict_new();
|
||||||
qdict_put_str(opts, "driver", "blkdebug");
|
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("blkdebug")));
|
||||||
|
|
||||||
QINCREF(bs->file->bs->full_open_options);
|
QINCREF(bs->file->full_open_options);
|
||||||
qdict_put(opts, "image", bs->file->bs->full_open_options);
|
qdict_put_obj(opts, "image", QOBJECT(bs->file->full_open_options));
|
||||||
|
|
||||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
for (e = qdict_first(bs->options); e; e = qdict_next(bs->options, e)) {
|
||||||
if (strcmp(qdict_entry_key(e), "x-image")) {
|
if (strcmp(qdict_entry_key(e), "x-image") &&
|
||||||
|
strcmp(qdict_entry_key(e), "image") &&
|
||||||
|
strncmp(qdict_entry_key(e), "image.", strlen("image.")))
|
||||||
|
{
|
||||||
qobject_incref(qdict_entry_value(e));
|
qobject_incref(qdict_entry_value(e));
|
||||||
qdict_put_obj(opts, qdict_entry_key(e), qdict_entry_value(e));
|
qdict_put_obj(opts, qdict_entry_key(e), qdict_entry_value(e));
|
||||||
}
|
}
|
||||||
@@ -860,36 +770,6 @@ static void blkdebug_refresh_filename(BlockDriverState *bs, QDict *options)
|
|||||||
bs->full_open_options = opts;
|
bs->full_open_options = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkdebug_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
BDRVBlkdebugState *s = bs->opaque;
|
|
||||||
|
|
||||||
if (s->align) {
|
|
||||||
bs->bl.request_alignment = s->align;
|
|
||||||
}
|
|
||||||
if (s->max_transfer) {
|
|
||||||
bs->bl.max_transfer = s->max_transfer;
|
|
||||||
}
|
|
||||||
if (s->opt_write_zero) {
|
|
||||||
bs->bl.pwrite_zeroes_alignment = s->opt_write_zero;
|
|
||||||
}
|
|
||||||
if (s->max_write_zero) {
|
|
||||||
bs->bl.max_pwrite_zeroes = s->max_write_zero;
|
|
||||||
}
|
|
||||||
if (s->opt_discard) {
|
|
||||||
bs->bl.pdiscard_alignment = s->opt_discard;
|
|
||||||
}
|
|
||||||
if (s->max_discard) {
|
|
||||||
bs->bl.max_pdiscard = s->max_discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int blkdebug_reopen_prepare(BDRVReopenState *reopen_state,
|
|
||||||
BlockReopenQueue *queue, Error **errp)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_blkdebug = {
|
static BlockDriver bdrv_blkdebug = {
|
||||||
.format_name = "blkdebug",
|
.format_name = "blkdebug",
|
||||||
.protocol_name = "blkdebug",
|
.protocol_name = "blkdebug",
|
||||||
@@ -898,19 +778,12 @@ static BlockDriver bdrv_blkdebug = {
|
|||||||
.bdrv_parse_filename = blkdebug_parse_filename,
|
.bdrv_parse_filename = blkdebug_parse_filename,
|
||||||
.bdrv_file_open = blkdebug_open,
|
.bdrv_file_open = blkdebug_open,
|
||||||
.bdrv_close = blkdebug_close,
|
.bdrv_close = blkdebug_close,
|
||||||
.bdrv_reopen_prepare = blkdebug_reopen_prepare,
|
|
||||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
|
||||||
|
|
||||||
.bdrv_getlength = blkdebug_getlength,
|
.bdrv_getlength = blkdebug_getlength,
|
||||||
.bdrv_truncate = blkdebug_truncate,
|
|
||||||
.bdrv_refresh_filename = blkdebug_refresh_filename,
|
.bdrv_refresh_filename = blkdebug_refresh_filename,
|
||||||
.bdrv_refresh_limits = blkdebug_refresh_limits,
|
|
||||||
|
|
||||||
.bdrv_co_preadv = blkdebug_co_preadv,
|
.bdrv_aio_readv = blkdebug_aio_readv,
|
||||||
.bdrv_co_pwritev = blkdebug_co_pwritev,
|
.bdrv_aio_writev = blkdebug_aio_writev,
|
||||||
.bdrv_co_flush_to_disk = blkdebug_co_flush,
|
.bdrv_aio_flush = blkdebug_aio_flush,
|
||||||
.bdrv_co_pwrite_zeroes = blkdebug_co_pwrite_zeroes,
|
|
||||||
.bdrv_co_pdiscard = blkdebug_co_pdiscard,
|
|
||||||
|
|
||||||
.bdrv_debug_event = blkdebug_debug_event,
|
.bdrv_debug_event = blkdebug_debug_event,
|
||||||
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
.bdrv_debug_breakpoint = blkdebug_debug_breakpoint,
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block protocol for record/replay
|
|
||||||
*
|
|
||||||
* Copyright (c) 2010-2016 Institute for System Programming
|
|
||||||
* of the Russian Academy of Sciences.
|
|
||||||
*
|
|
||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
||||||
* See the COPYING file in the top-level directory.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "sysemu/replay.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
|
|
||||||
typedef struct Request {
|
|
||||||
Coroutine *co;
|
|
||||||
QEMUBH *bh;
|
|
||||||
} Request;
|
|
||||||
|
|
||||||
static int blkreplay_open(BlockDriverState *bs, QDict *options, int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* Open the image file */
|
|
||||||
bs->file = bdrv_open_child(NULL, options, "image",
|
|
||||||
bs, &child_file, false, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
ret = -EINVAL;
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = 0;
|
|
||||||
fail:
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void blkreplay_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t blkreplay_getlength(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
return bdrv_getlength(bs->file->bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This bh is used for synchronization of return from coroutines.
|
|
||||||
It continues yielded coroutine which then finishes its execution.
|
|
||||||
BH is called adjusted to some replay checkpoint, therefore
|
|
||||||
record and replay will always finish coroutines deterministically.
|
|
||||||
*/
|
|
||||||
static void blkreplay_bh_cb(void *opaque)
|
|
||||||
{
|
|
||||||
Request *req = opaque;
|
|
||||||
aio_co_wake(req->co);
|
|
||||||
qemu_bh_delete(req->bh);
|
|
||||||
g_free(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void block_request_create(uint64_t reqid, BlockDriverState *bs,
|
|
||||||
Coroutine *co)
|
|
||||||
{
|
|
||||||
Request *req = g_new(Request, 1);
|
|
||||||
*req = (Request) {
|
|
||||||
.co = co,
|
|
||||||
.bh = aio_bh_new(bdrv_get_aio_context(bs), blkreplay_bh_cb, req),
|
|
||||||
};
|
|
||||||
replay_block_event(req->bh, reqid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkreplay_co_preadv(BlockDriverState *bs,
|
|
||||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
|
||||||
uint64_t reqid = blkreplay_next_id();
|
|
||||||
int ret = bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
|
||||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkreplay_co_pwritev(BlockDriverState *bs,
|
|
||||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
|
||||||
uint64_t reqid = blkreplay_next_id();
|
|
||||||
int ret = bdrv_co_pwritev(bs->file, offset, bytes, qiov, flags);
|
|
||||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkreplay_co_pwrite_zeroes(BlockDriverState *bs,
|
|
||||||
int64_t offset, int count, BdrvRequestFlags flags)
|
|
||||||
{
|
|
||||||
uint64_t reqid = blkreplay_next_id();
|
|
||||||
int ret = bdrv_co_pwrite_zeroes(bs->file, offset, count, flags);
|
|
||||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkreplay_co_pdiscard(BlockDriverState *bs,
|
|
||||||
int64_t offset, int count)
|
|
||||||
{
|
|
||||||
uint64_t reqid = blkreplay_next_id();
|
|
||||||
int ret = bdrv_co_pdiscard(bs->file->bs, offset, count);
|
|
||||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int coroutine_fn blkreplay_co_flush(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
uint64_t reqid = blkreplay_next_id();
|
|
||||||
int ret = bdrv_co_flush(bs->file->bs);
|
|
||||||
block_request_create(reqid, bs, qemu_coroutine_self());
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_blkreplay = {
|
|
||||||
.format_name = "blkreplay",
|
|
||||||
.protocol_name = "blkreplay",
|
|
||||||
.instance_size = 0,
|
|
||||||
|
|
||||||
.bdrv_file_open = blkreplay_open,
|
|
||||||
.bdrv_close = blkreplay_close,
|
|
||||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
|
||||||
.bdrv_getlength = blkreplay_getlength,
|
|
||||||
|
|
||||||
.bdrv_co_preadv = blkreplay_co_preadv,
|
|
||||||
.bdrv_co_pwritev = blkreplay_co_pwritev,
|
|
||||||
|
|
||||||
.bdrv_co_pwrite_zeroes = blkreplay_co_pwrite_zeroes,
|
|
||||||
.bdrv_co_pdiscard = blkreplay_co_pdiscard,
|
|
||||||
.bdrv_co_flush = blkreplay_co_flush,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void bdrv_blkreplay_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_blkreplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(bdrv_blkreplay_init);
|
|
||||||
@@ -7,48 +7,49 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include <stdarg.h>
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu/sockets.h" /* for EINPROGRESS on Windows */
|
#include "qemu/sockets.h" /* for EINPROGRESS on Windows */
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BdrvChild *test_file;
|
BlockDriverState *test_file;
|
||||||
} BDRVBlkverifyState;
|
} BDRVBlkverifyState;
|
||||||
|
|
||||||
typedef struct BlkverifyRequest {
|
typedef struct BlkverifyAIOCB BlkverifyAIOCB;
|
||||||
Coroutine *co;
|
struct BlkverifyAIOCB {
|
||||||
BlockDriverState *bs;
|
BlockAIOCB common;
|
||||||
|
QEMUBH *bh;
|
||||||
|
|
||||||
/* Request metadata */
|
/* Request metadata */
|
||||||
bool is_write;
|
bool is_write;
|
||||||
uint64_t offset;
|
int64_t sector_num;
|
||||||
uint64_t bytes;
|
int nb_sectors;
|
||||||
int flags;
|
|
||||||
|
|
||||||
int (*request_fn)(BdrvChild *, int64_t, unsigned int, QEMUIOVector *,
|
|
||||||
BdrvRequestFlags);
|
|
||||||
|
|
||||||
int ret; /* test image result */
|
|
||||||
int raw_ret; /* raw image result */
|
|
||||||
|
|
||||||
|
int ret; /* first completed request's result */
|
||||||
unsigned int done; /* completion counter */
|
unsigned int done; /* completion counter */
|
||||||
|
|
||||||
QEMUIOVector *qiov; /* user I/O vector */
|
QEMUIOVector *qiov; /* user I/O vector */
|
||||||
QEMUIOVector *raw_qiov; /* cloned I/O vector for raw file */
|
QEMUIOVector raw_qiov; /* cloned I/O vector for raw file */
|
||||||
} BlkverifyRequest;
|
void *buf; /* buffer for raw file I/O */
|
||||||
|
|
||||||
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyRequest *r,
|
void (*verify)(BlkverifyAIOCB *acb);
|
||||||
|
};
|
||||||
|
|
||||||
|
static const AIOCBInfo blkverify_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(BlkverifyAIOCB),
|
||||||
|
};
|
||||||
|
|
||||||
|
static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb,
|
||||||
const char *fmt, ...)
|
const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
fprintf(stderr, "blkverify: %s offset=%" PRId64 " bytes=%" PRId64 " ",
|
fprintf(stderr, "blkverify: %s sector_num=%" PRId64 " nb_sectors=%d ",
|
||||||
r->is_write ? "write" : "read", r->offset, r->bytes);
|
acb->is_write ? "write" : "read", acb->sector_num,
|
||||||
|
acb->nb_sectors);
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
@@ -67,7 +68,7 @@ static void blkverify_parse_filename(const char *filename, QDict *options,
|
|||||||
if (!strstart(filename, "blkverify:", &filename)) {
|
if (!strstart(filename, "blkverify:", &filename)) {
|
||||||
/* There was no prefix; therefore, all options have to be already
|
/* There was no prefix; therefore, all options have to be already
|
||||||
present in the QDict (except for the filename) */
|
present in the QDict (except for the filename) */
|
||||||
qdict_put_str(options, "x-image", filename);
|
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ static void blkverify_parse_filename(const char *filename, QDict *options,
|
|||||||
|
|
||||||
/* TODO Allow multi-level nesting and set file.filename here */
|
/* TODO Allow multi-level nesting and set file.filename here */
|
||||||
filename = c + 1;
|
filename = c + 1;
|
||||||
qdict_put_str(options, "x-image", filename);
|
qdict_put(options, "x-image", qstring_from_str(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
static QemuOptsList runtime_opts = {
|
||||||
@@ -122,21 +123,21 @@ static int blkverify_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Open the raw file */
|
/* Open the raw file */
|
||||||
bs->file = bdrv_open_child(qemu_opt_get(opts, "x-raw"), options, "raw",
|
assert(bs->file == NULL);
|
||||||
bs, &child_file, false, &local_err);
|
ret = bdrv_open_image(&bs->file, qemu_opt_get(opts, "x-raw"), options,
|
||||||
if (local_err) {
|
"raw", flags | BDRV_O_PROTOCOL, false, &local_err);
|
||||||
ret = -EINVAL;
|
if (ret < 0) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open the test file */
|
/* Open the test file */
|
||||||
s->test_file = bdrv_open_child(qemu_opt_get(opts, "x-image"), options,
|
assert(s->test_file == NULL);
|
||||||
"test", bs, &child_format, false,
|
ret = bdrv_open_image(&s->test_file, qemu_opt_get(opts, "x-image"), options,
|
||||||
&local_err);
|
"test", flags, false, &local_err);
|
||||||
if (local_err) {
|
if (ret < 0) {
|
||||||
ret = -EINVAL;
|
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
|
s->test_file = NULL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ static void blkverify_close(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
bdrv_unref_child(bs, s->test_file);
|
bdrv_unref(s->test_file);
|
||||||
s->test_file = NULL;
|
s->test_file = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,109 +159,119 @@ static int64_t blkverify_getlength(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
return bdrv_getlength(s->test_file->bs);
|
return bdrv_getlength(s->test_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coroutine_fn blkverify_do_test_req(void *opaque)
|
static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov,
|
||||||
|
int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
{
|
{
|
||||||
BlkverifyRequest *r = opaque;
|
BlkverifyAIOCB *acb = qemu_aio_get(&blkverify_aiocb_info, bs, cb, opaque);
|
||||||
BDRVBlkverifyState *s = r->bs->opaque;
|
|
||||||
|
|
||||||
r->ret = r->request_fn(s->test_file, r->offset, r->bytes, r->qiov,
|
acb->bh = NULL;
|
||||||
r->flags);
|
acb->is_write = is_write;
|
||||||
r->done++;
|
acb->sector_num = sector_num;
|
||||||
qemu_coroutine_enter_if_inactive(r->co);
|
acb->nb_sectors = nb_sectors;
|
||||||
|
acb->ret = -EINPROGRESS;
|
||||||
|
acb->done = 0;
|
||||||
|
acb->qiov = qiov;
|
||||||
|
acb->buf = NULL;
|
||||||
|
acb->verify = NULL;
|
||||||
|
return acb;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coroutine_fn blkverify_do_raw_req(void *opaque)
|
static void blkverify_aio_bh(void *opaque)
|
||||||
{
|
{
|
||||||
BlkverifyRequest *r = opaque;
|
BlkverifyAIOCB *acb = opaque;
|
||||||
|
|
||||||
r->raw_ret = r->request_fn(r->bs->file, r->offset, r->bytes, r->raw_qiov,
|
qemu_bh_delete(acb->bh);
|
||||||
r->flags);
|
if (acb->buf) {
|
||||||
r->done++;
|
qemu_iovec_destroy(&acb->raw_qiov);
|
||||||
qemu_coroutine_enter_if_inactive(r->co);
|
qemu_vfree(acb->buf);
|
||||||
|
}
|
||||||
|
acb->common.cb(acb->common.opaque, acb->ret);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static void blkverify_aio_cb(void *opaque, int ret)
|
||||||
blkverify_co_prwv(BlockDriverState *bs, BlkverifyRequest *r, uint64_t offset,
|
|
||||||
uint64_t bytes, QEMUIOVector *qiov, QEMUIOVector *raw_qiov,
|
|
||||||
int flags, bool is_write)
|
|
||||||
{
|
{
|
||||||
Coroutine *co_a, *co_b;
|
BlkverifyAIOCB *acb = opaque;
|
||||||
|
|
||||||
*r = (BlkverifyRequest) {
|
switch (++acb->done) {
|
||||||
.co = qemu_coroutine_self(),
|
case 1:
|
||||||
.bs = bs,
|
acb->ret = ret;
|
||||||
.offset = offset,
|
break;
|
||||||
.bytes = bytes,
|
|
||||||
.qiov = qiov,
|
|
||||||
.raw_qiov = raw_qiov,
|
|
||||||
.flags = flags,
|
|
||||||
.is_write = is_write,
|
|
||||||
.request_fn = is_write ? bdrv_co_pwritev : bdrv_co_preadv,
|
|
||||||
};
|
|
||||||
|
|
||||||
co_a = qemu_coroutine_create(blkverify_do_test_req, r);
|
case 2:
|
||||||
co_b = qemu_coroutine_create(blkverify_do_raw_req, r);
|
if (acb->ret != ret) {
|
||||||
|
blkverify_err(acb, "return value mismatch %d != %d", acb->ret, ret);
|
||||||
qemu_coroutine_enter(co_a);
|
|
||||||
qemu_coroutine_enter(co_b);
|
|
||||||
|
|
||||||
while (r->done < 2) {
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r->ret != r->raw_ret) {
|
if (acb->verify) {
|
||||||
blkverify_err(r, "return value mismatch %d != %d", r->ret, r->raw_ret);
|
acb->verify(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return r->ret;
|
acb->bh = aio_bh_new(bdrv_get_aio_context(acb->common.bs),
|
||||||
}
|
blkverify_aio_bh, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
static int coroutine_fn
|
break;
|
||||||
blkverify_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
|
||||||
QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
|
||||||
BlkverifyRequest r;
|
|
||||||
QEMUIOVector raw_qiov;
|
|
||||||
void *buf;
|
|
||||||
ssize_t cmp_offset;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
buf = qemu_blockalign(bs->file->bs, qiov->size);
|
|
||||||
qemu_iovec_init(&raw_qiov, qiov->niov);
|
|
||||||
qemu_iovec_clone(&raw_qiov, qiov, buf);
|
|
||||||
|
|
||||||
ret = blkverify_co_prwv(bs, &r, offset, bytes, qiov, &raw_qiov, flags,
|
|
||||||
false);
|
|
||||||
|
|
||||||
cmp_offset = qemu_iovec_compare(qiov, &raw_qiov);
|
|
||||||
if (cmp_offset != -1) {
|
|
||||||
blkverify_err(&r, "contents mismatch at offset %" PRId64,
|
|
||||||
offset + cmp_offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_iovec_destroy(&raw_qiov);
|
|
||||||
qemu_vfree(buf);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static void blkverify_verify_readv(BlkverifyAIOCB *acb)
|
||||||
blkverify_co_pwritev(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
|
||||||
QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
{
|
||||||
BlkverifyRequest r;
|
ssize_t offset = qemu_iovec_compare(acb->qiov, &acb->raw_qiov);
|
||||||
return blkverify_co_prwv(bs, &r, offset, bytes, qiov, qiov, flags, true);
|
if (offset != -1) {
|
||||||
|
blkverify_err(acb, "contents mismatch in sector %" PRId64,
|
||||||
|
acb->sector_num + (int64_t)(offset / BDRV_SECTOR_SIZE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int blkverify_co_flush(BlockDriverState *bs)
|
static BlockAIOCB *blkverify_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
BlkverifyAIOCB *acb = blkverify_aio_get(bs, false, sector_num, qiov,
|
||||||
|
nb_sectors, cb, opaque);
|
||||||
|
|
||||||
|
acb->verify = blkverify_verify_readv;
|
||||||
|
acb->buf = qemu_blockalign(bs->file, qiov->size);
|
||||||
|
qemu_iovec_init(&acb->raw_qiov, acb->qiov->niov);
|
||||||
|
qemu_iovec_clone(&acb->raw_qiov, qiov, acb->buf);
|
||||||
|
|
||||||
|
bdrv_aio_readv(s->test_file, sector_num, qiov, nb_sectors,
|
||||||
|
blkverify_aio_cb, acb);
|
||||||
|
bdrv_aio_readv(bs->file, sector_num, &acb->raw_qiov, nb_sectors,
|
||||||
|
blkverify_aio_cb, acb);
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockAIOCB *blkverify_aio_writev(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
BlkverifyAIOCB *acb = blkverify_aio_get(bs, true, sector_num, qiov,
|
||||||
|
nb_sectors, cb, opaque);
|
||||||
|
|
||||||
|
bdrv_aio_writev(s->test_file, sector_num, qiov, nb_sectors,
|
||||||
|
blkverify_aio_cb, acb);
|
||||||
|
bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors,
|
||||||
|
blkverify_aio_cb, acb);
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockAIOCB *blkverify_aio_flush(BlockDriverState *bs,
|
||||||
|
BlockCompletionFunc *cb,
|
||||||
|
void *opaque)
|
||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
/* Only flush test file, the raw file is not important */
|
/* Only flush test file, the raw file is not important */
|
||||||
return bdrv_co_flush(s->test_file->bs);
|
return bdrv_aio_flush(s->test_file, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool blkverify_recurse_is_first_non_filter(BlockDriverState *bs,
|
static bool blkverify_recurse_is_first_non_filter(BlockDriverState *bs,
|
||||||
@@ -268,43 +279,54 @@ static bool blkverify_recurse_is_first_non_filter(BlockDriverState *bs,
|
|||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
bool perm = bdrv_recurse_is_first_non_filter(bs->file->bs, candidate);
|
bool perm = bdrv_recurse_is_first_non_filter(bs->file, candidate);
|
||||||
|
|
||||||
if (perm) {
|
if (perm) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return bdrv_recurse_is_first_non_filter(s->test_file->bs, candidate);
|
return bdrv_recurse_is_first_non_filter(s->test_file, candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkverify_refresh_filename(BlockDriverState *bs, QDict *options)
|
/* Propagate AioContext changes to ->test_file */
|
||||||
|
static void blkverify_detach_aio_context(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVBlkverifyState *s = bs->opaque;
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
/* bs->file->bs has already been refreshed */
|
bdrv_detach_aio_context(s->test_file);
|
||||||
bdrv_refresh_filename(s->test_file->bs);
|
}
|
||||||
|
|
||||||
if (bs->file->bs->full_open_options
|
static void blkverify_attach_aio_context(BlockDriverState *bs,
|
||||||
&& s->test_file->bs->full_open_options)
|
AioContext *new_context)
|
||||||
{
|
{
|
||||||
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
|
bdrv_attach_aio_context(s->test_file, new_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blkverify_refresh_filename(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVBlkverifyState *s = bs->opaque;
|
||||||
|
|
||||||
|
/* bs->file has already been refreshed */
|
||||||
|
bdrv_refresh_filename(s->test_file);
|
||||||
|
|
||||||
|
if (bs->file->full_open_options && s->test_file->full_open_options) {
|
||||||
QDict *opts = qdict_new();
|
QDict *opts = qdict_new();
|
||||||
qdict_put_str(opts, "driver", "blkverify");
|
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("blkverify")));
|
||||||
|
|
||||||
QINCREF(bs->file->bs->full_open_options);
|
QINCREF(bs->file->full_open_options);
|
||||||
qdict_put(opts, "raw", bs->file->bs->full_open_options);
|
qdict_put_obj(opts, "raw", QOBJECT(bs->file->full_open_options));
|
||||||
QINCREF(s->test_file->bs->full_open_options);
|
QINCREF(s->test_file->full_open_options);
|
||||||
qdict_put(opts, "test", s->test_file->bs->full_open_options);
|
qdict_put_obj(opts, "test", QOBJECT(s->test_file->full_open_options));
|
||||||
|
|
||||||
bs->full_open_options = opts;
|
bs->full_open_options = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bs->file->bs->exact_filename[0]
|
if (bs->file->exact_filename[0] && s->test_file->exact_filename[0]) {
|
||||||
&& s->test_file->bs->exact_filename[0])
|
|
||||||
{
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"blkverify:%s:%s",
|
"blkverify:%s:%s",
|
||||||
bs->file->bs->exact_filename,
|
bs->file->exact_filename, s->test_file->exact_filename);
|
||||||
s->test_file->bs->exact_filename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,13 +338,15 @@ static BlockDriver bdrv_blkverify = {
|
|||||||
.bdrv_parse_filename = blkverify_parse_filename,
|
.bdrv_parse_filename = blkverify_parse_filename,
|
||||||
.bdrv_file_open = blkverify_open,
|
.bdrv_file_open = blkverify_open,
|
||||||
.bdrv_close = blkverify_close,
|
.bdrv_close = blkverify_close,
|
||||||
.bdrv_child_perm = bdrv_filter_default_perms,
|
|
||||||
.bdrv_getlength = blkverify_getlength,
|
.bdrv_getlength = blkverify_getlength,
|
||||||
.bdrv_refresh_filename = blkverify_refresh_filename,
|
.bdrv_refresh_filename = blkverify_refresh_filename,
|
||||||
|
|
||||||
.bdrv_co_preadv = blkverify_co_preadv,
|
.bdrv_aio_readv = blkverify_aio_readv,
|
||||||
.bdrv_co_pwritev = blkverify_co_pwritev,
|
.bdrv_aio_writev = blkverify_aio_writev,
|
||||||
.bdrv_co_flush = blkverify_co_flush,
|
.bdrv_aio_flush = blkverify_aio_flush,
|
||||||
|
|
||||||
|
.bdrv_attach_aio_context = blkverify_attach_aio_context,
|
||||||
|
.bdrv_detach_aio_context = blkverify_detach_aio_context,
|
||||||
|
|
||||||
.is_filter = true,
|
.is_filter = true,
|
||||||
.bdrv_recurse_is_first_non_filter = blkverify_recurse_is_first_non_filter,
|
.bdrv_recurse_is_first_non_filter = blkverify_recurse_is_first_non_filter,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,12 +22,9 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qemu/bswap.h"
|
|
||||||
|
|
||||||
/**************************************************************/
|
/**************************************************************/
|
||||||
|
|
||||||
@@ -104,16 +101,7 @@ static int bochs_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
struct bochs_header bochs;
|
struct bochs_header bochs;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
bs->read_only = 1; // no write support yet
|
||||||
false, errp);
|
|
||||||
if (!bs->file) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_set_read_only(bs, true, errp); /* no write support yet */
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, 0, &bochs, sizeof(bochs));
|
ret = bdrv_pread(bs->file, 0, &bochs, sizeof(bochs));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -197,11 +185,6 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bochs_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num)
|
static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num)
|
||||||
{
|
{
|
||||||
BDRVBochsState *s = bs->opaque;
|
BDRVBochsState *s = bs->opaque;
|
||||||
@@ -236,52 +219,38 @@ static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num)
|
|||||||
return bitmap_offset + (512 * (s->bitmap_blocks + extent_offset));
|
return bitmap_offset + (512 * (s->bitmap_blocks + extent_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static int bochs_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
bochs_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
uint8_t *buf, int nb_sectors)
|
||||||
QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
{
|
||||||
BDRVBochsState *s = bs->opaque;
|
|
||||||
uint64_t sector_num = offset >> BDRV_SECTOR_BITS;
|
|
||||||
int nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
|
||||||
uint64_t bytes_done = 0;
|
|
||||||
QEMUIOVector local_qiov;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
|
|
||||||
qemu_iovec_init(&local_qiov, qiov->niov);
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
|
||||||
|
|
||||||
while (nb_sectors > 0) {
|
while (nb_sectors > 0) {
|
||||||
int64_t block_offset = seek_to_sector(bs, sector_num);
|
int64_t block_offset = seek_to_sector(bs, sector_num);
|
||||||
if (block_offset < 0) {
|
if (block_offset < 0) {
|
||||||
ret = block_offset;
|
return block_offset;
|
||||||
goto fail;
|
} else if (block_offset > 0) {
|
||||||
}
|
ret = bdrv_pread(bs->file, block_offset, buf, 512);
|
||||||
|
|
||||||
qemu_iovec_reset(&local_qiov);
|
|
||||||
qemu_iovec_concat(&local_qiov, qiov, bytes_done, 512);
|
|
||||||
|
|
||||||
if (block_offset > 0) {
|
|
||||||
ret = bdrv_co_preadv(bs->file, block_offset, 512,
|
|
||||||
&local_qiov, 0);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto fail;
|
return ret;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qemu_iovec_memset(&local_qiov, 0, 0, 512);
|
memset(buf, 0, 512);
|
||||||
}
|
}
|
||||||
nb_sectors--;
|
nb_sectors--;
|
||||||
sector_num++;
|
sector_num++;
|
||||||
bytes_done += 512;
|
buf += 512;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ret = 0;
|
static coroutine_fn int bochs_co_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
fail:
|
uint8_t *buf, int nb_sectors)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
BDRVBochsState *s = bs->opaque;
|
||||||
|
qemu_co_mutex_lock(&s->lock);
|
||||||
|
ret = bochs_read(bs, sector_num, buf, nb_sectors);
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
qemu_co_mutex_unlock(&s->lock);
|
||||||
qemu_iovec_destroy(&local_qiov);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,9 +265,7 @@ static BlockDriver bdrv_bochs = {
|
|||||||
.instance_size = sizeof(BDRVBochsState),
|
.instance_size = sizeof(BDRVBochsState),
|
||||||
.bdrv_probe = bochs_probe,
|
.bdrv_probe = bochs_probe,
|
||||||
.bdrv_open = bochs_open,
|
.bdrv_open = bochs_open,
|
||||||
.bdrv_child_perm = bdrv_format_default_perms,
|
.bdrv_read = bochs_co_read,
|
||||||
.bdrv_refresh_limits = bochs_refresh_limits,
|
|
||||||
.bdrv_co_preadv = bochs_co_preadv,
|
|
||||||
.bdrv_close = bochs_close,
|
.bdrv_close = bochs_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,9 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qemu/bswap.h"
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
/* Maximum compressed block size */
|
/* Maximum compressed block size */
|
||||||
@@ -66,16 +63,7 @@ static int cloop_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
uint32_t offsets_size, max_compressed_block_size = 1, i;
|
uint32_t offsets_size, max_compressed_block_size = 1, i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
bs->read_only = 1;
|
||||||
false, errp);
|
|
||||||
if (!bs->file) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_set_read_only(bs, true, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read header */
|
/* read header */
|
||||||
ret = bdrv_pread(bs->file, 128, &s->block_size, 4);
|
ret = bdrv_pread(bs->file, 128, &s->block_size, 4);
|
||||||
@@ -207,11 +195,6 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cloop_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cloop_read_block(BlockDriverState *bs, int block_num)
|
static inline int cloop_read_block(BlockDriverState *bs, int block_num)
|
||||||
{
|
{
|
||||||
BDRVCloopState *s = bs->opaque;
|
BDRVCloopState *s = bs->opaque;
|
||||||
@@ -220,8 +203,8 @@ static inline int cloop_read_block(BlockDriverState *bs, int block_num)
|
|||||||
int ret;
|
int ret;
|
||||||
uint32_t bytes = s->offsets[block_num + 1] - s->offsets[block_num];
|
uint32_t bytes = s->offsets[block_num + 1] - s->offsets[block_num];
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, s->offsets[block_num],
|
ret = bdrv_pread(bs->file, s->offsets[block_num], s->compressed_block,
|
||||||
s->compressed_block, bytes);
|
bytes);
|
||||||
if (ret != bytes) {
|
if (ret != bytes) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -244,38 +227,33 @@ static inline int cloop_read_block(BlockDriverState *bs, int block_num)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static int cloop_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
cloop_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
uint8_t *buf, int nb_sectors)
|
||||||
QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
{
|
||||||
BDRVCloopState *s = bs->opaque;
|
BDRVCloopState *s = bs->opaque;
|
||||||
uint64_t sector_num = offset >> BDRV_SECTOR_BITS;
|
int i;
|
||||||
int nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
|
||||||
int ret, i;
|
|
||||||
|
|
||||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
|
||||||
|
|
||||||
for (i = 0; i < nb_sectors; i++) {
|
for (i = 0; i < nb_sectors; i++) {
|
||||||
void *data;
|
|
||||||
uint32_t sector_offset_in_block =
|
uint32_t sector_offset_in_block =
|
||||||
((sector_num + i) % s->sectors_per_block),
|
((sector_num + i) % s->sectors_per_block),
|
||||||
block_num = (sector_num + i) / s->sectors_per_block;
|
block_num = (sector_num + i) / s->sectors_per_block;
|
||||||
if (cloop_read_block(bs, block_num) != 0) {
|
if (cloop_read_block(bs, block_num) != 0) {
|
||||||
ret = -EIO;
|
return -1;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
memcpy(buf + i * 512,
|
||||||
data = s->uncompressed_block + sector_offset_in_block * 512;
|
s->uncompressed_block + sector_offset_in_block * 512, 512);
|
||||||
qemu_iovec_from_buf(qiov, i * 512, data, 512);
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ret = 0;
|
static coroutine_fn int cloop_co_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
fail:
|
uint8_t *buf, int nb_sectors)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
BDRVCloopState *s = bs->opaque;
|
||||||
|
qemu_co_mutex_lock(&s->lock);
|
||||||
|
ret = cloop_read(bs, sector_num, buf, nb_sectors);
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
qemu_co_mutex_unlock(&s->lock);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,9 +271,7 @@ static BlockDriver bdrv_cloop = {
|
|||||||
.instance_size = sizeof(BDRVCloopState),
|
.instance_size = sizeof(BDRVCloopState),
|
||||||
.bdrv_probe = cloop_probe,
|
.bdrv_probe = cloop_probe,
|
||||||
.bdrv_open = cloop_open,
|
.bdrv_open = cloop_open,
|
||||||
.bdrv_child_perm = bdrv_format_default_perms,
|
.bdrv_read = cloop_co_read,
|
||||||
.bdrv_refresh_limits = cloop_refresh_limits,
|
|
||||||
.bdrv_co_preadv = cloop_co_preadv,
|
|
||||||
.bdrv_close = cloop_close,
|
.bdrv_close = cloop_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
430
block/commit.c
430
block/commit.c
@@ -12,15 +12,10 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "block/blockjob_int.h"
|
#include "block/blockjob.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qerror.h"
|
|
||||||
#include "qemu/ratelimit.h"
|
#include "qemu/ratelimit.h"
|
||||||
#include "sysemu/block-backend.h"
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
/*
|
/*
|
||||||
@@ -37,37 +32,28 @@ typedef struct CommitBlockJob {
|
|||||||
BlockJob common;
|
BlockJob common;
|
||||||
RateLimit limit;
|
RateLimit limit;
|
||||||
BlockDriverState *active;
|
BlockDriverState *active;
|
||||||
BlockDriverState *commit_top_bs;
|
BlockDriverState *top;
|
||||||
BlockBackend *top;
|
BlockDriverState *base;
|
||||||
BlockBackend *base;
|
|
||||||
BlockdevOnError on_error;
|
BlockdevOnError on_error;
|
||||||
int base_flags;
|
int base_flags;
|
||||||
int orig_overlay_flags;
|
int orig_overlay_flags;
|
||||||
char *backing_file_str;
|
char *backing_file_str;
|
||||||
} CommitBlockJob;
|
} CommitBlockJob;
|
||||||
|
|
||||||
static int coroutine_fn commit_populate(BlockBackend *bs, BlockBackend *base,
|
static int coroutine_fn commit_populate(BlockDriverState *bs,
|
||||||
|
BlockDriverState *base,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
void *buf)
|
void *buf)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
QEMUIOVector qiov;
|
|
||||||
struct iovec iov = {
|
|
||||||
.iov_base = buf,
|
|
||||||
.iov_len = nb_sectors * BDRV_SECTOR_SIZE,
|
|
||||||
};
|
|
||||||
|
|
||||||
qemu_iovec_init_external(&qiov, &iov, 1);
|
ret = bdrv_read(bs, sector_num, buf, nb_sectors);
|
||||||
|
if (ret) {
|
||||||
ret = blk_co_preadv(bs, sector_num * BDRV_SECTOR_SIZE,
|
|
||||||
qiov.size, &qiov, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = blk_co_pwritev(base, sector_num * BDRV_SECTOR_SIZE,
|
ret = bdrv_write(base, sector_num, buf, nb_sectors);
|
||||||
qiov.size, &qiov, 0);
|
if (ret) {
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,29 +69,14 @@ static void commit_complete(BlockJob *job, void *opaque)
|
|||||||
CommitBlockJob *s = container_of(job, CommitBlockJob, common);
|
CommitBlockJob *s = container_of(job, CommitBlockJob, common);
|
||||||
CommitCompleteData *data = opaque;
|
CommitCompleteData *data = opaque;
|
||||||
BlockDriverState *active = s->active;
|
BlockDriverState *active = s->active;
|
||||||
BlockDriverState *top = blk_bs(s->top);
|
BlockDriverState *top = s->top;
|
||||||
BlockDriverState *base = blk_bs(s->base);
|
BlockDriverState *base = s->base;
|
||||||
BlockDriverState *overlay_bs = bdrv_find_overlay(active, s->commit_top_bs);
|
BlockDriverState *overlay_bs;
|
||||||
int ret = data->ret;
|
int ret = data->ret;
|
||||||
bool remove_commit_top_bs = false;
|
|
||||||
|
|
||||||
/* Make sure overlay_bs and top stay around until bdrv_set_backing_hd() */
|
|
||||||
bdrv_ref(top);
|
|
||||||
bdrv_ref(overlay_bs);
|
|
||||||
|
|
||||||
/* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before
|
|
||||||
* the normal backing chain can be restored. */
|
|
||||||
blk_unref(s->base);
|
|
||||||
|
|
||||||
if (!block_job_is_cancelled(&s->common) && ret == 0) {
|
if (!block_job_is_cancelled(&s->common) && ret == 0) {
|
||||||
/* success */
|
/* success */
|
||||||
ret = bdrv_drop_intermediate(active, s->commit_top_bs, base,
|
ret = bdrv_drop_intermediate(active, top, base, s->backing_file_str);
|
||||||
s->backing_file_str);
|
|
||||||
} else if (overlay_bs) {
|
|
||||||
/* XXX Can (or should) we somehow keep 'consistent read' blocked even
|
|
||||||
* after the failed/cancelled commit job is gone? If we already wrote
|
|
||||||
* something to base, the intermediate images aren't valid any more. */
|
|
||||||
remove_commit_top_bs = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore base open flags here if appropriate (e.g., change the base back
|
/* restore base open flags here if appropriate (e.g., change the base back
|
||||||
@@ -114,62 +85,55 @@ static void commit_complete(BlockJob *job, void *opaque)
|
|||||||
if (s->base_flags != bdrv_get_flags(base)) {
|
if (s->base_flags != bdrv_get_flags(base)) {
|
||||||
bdrv_reopen(base, s->base_flags, NULL);
|
bdrv_reopen(base, s->base_flags, NULL);
|
||||||
}
|
}
|
||||||
|
overlay_bs = bdrv_find_overlay(active, top);
|
||||||
if (overlay_bs && s->orig_overlay_flags != bdrv_get_flags(overlay_bs)) {
|
if (overlay_bs && s->orig_overlay_flags != bdrv_get_flags(overlay_bs)) {
|
||||||
bdrv_reopen(overlay_bs, s->orig_overlay_flags, NULL);
|
bdrv_reopen(overlay_bs, s->orig_overlay_flags, NULL);
|
||||||
}
|
}
|
||||||
g_free(s->backing_file_str);
|
g_free(s->backing_file_str);
|
||||||
blk_unref(s->top);
|
|
||||||
block_job_completed(&s->common, ret);
|
block_job_completed(&s->common, ret);
|
||||||
g_free(data);
|
g_free(data);
|
||||||
|
|
||||||
/* If bdrv_drop_intermediate() didn't already do that, remove the commit
|
|
||||||
* filter driver from the backing chain. Do this as the final step so that
|
|
||||||
* the 'consistent read' permission can be granted. */
|
|
||||||
if (remove_commit_top_bs) {
|
|
||||||
bdrv_set_backing_hd(overlay_bs, top, &error_abort);
|
|
||||||
}
|
|
||||||
|
|
||||||
bdrv_unref(overlay_bs);
|
|
||||||
bdrv_unref(top);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void coroutine_fn commit_run(void *opaque)
|
static void coroutine_fn commit_run(void *opaque)
|
||||||
{
|
{
|
||||||
CommitBlockJob *s = opaque;
|
CommitBlockJob *s = opaque;
|
||||||
CommitCompleteData *data;
|
CommitCompleteData *data;
|
||||||
|
BlockDriverState *top = s->top;
|
||||||
|
BlockDriverState *base = s->base;
|
||||||
int64_t sector_num, end;
|
int64_t sector_num, end;
|
||||||
uint64_t delay_ns = 0;
|
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
void *buf = NULL;
|
void *buf = NULL;
|
||||||
int bytes_written = 0;
|
int bytes_written = 0;
|
||||||
int64_t base_len;
|
int64_t base_len;
|
||||||
|
|
||||||
ret = s->common.len = blk_getlength(s->top);
|
ret = s->common.len = bdrv_getlength(top);
|
||||||
|
|
||||||
|
|
||||||
if (s->common.len < 0) {
|
if (s->common.len < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = base_len = blk_getlength(s->base);
|
ret = base_len = bdrv_getlength(base);
|
||||||
if (base_len < 0) {
|
if (base_len < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (base_len < s->common.len) {
|
if (base_len < s->common.len) {
|
||||||
ret = blk_truncate(s->base, s->common.len, NULL);
|
ret = bdrv_truncate(base, s->common.len);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end = s->common.len >> BDRV_SECTOR_BITS;
|
end = s->common.len >> BDRV_SECTOR_BITS;
|
||||||
buf = blk_blockalign(s->top, COMMIT_BUFFER_SIZE);
|
buf = qemu_blockalign(top, COMMIT_BUFFER_SIZE);
|
||||||
|
|
||||||
for (sector_num = 0; sector_num < end; sector_num += n) {
|
for (sector_num = 0; sector_num < end; sector_num += n) {
|
||||||
|
uint64_t delay_ns = 0;
|
||||||
bool copy;
|
bool copy;
|
||||||
|
|
||||||
|
wait:
|
||||||
/* Note that even when no rate limit is applied we need to yield
|
/* Note that even when no rate limit is applied we need to yield
|
||||||
* with no pending I/O here so that bdrv_drain_all() returns.
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
||||||
*/
|
*/
|
||||||
@@ -178,20 +142,25 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Copy if allocated above the base */
|
/* Copy if allocated above the base */
|
||||||
ret = bdrv_is_allocated_above(blk_bs(s->top), blk_bs(s->base),
|
ret = bdrv_is_allocated_above(top, base, sector_num,
|
||||||
sector_num,
|
|
||||||
COMMIT_BUFFER_SIZE / BDRV_SECTOR_SIZE,
|
COMMIT_BUFFER_SIZE / BDRV_SECTOR_SIZE,
|
||||||
&n);
|
&n);
|
||||||
copy = (ret == 1);
|
copy = (ret == 1);
|
||||||
trace_commit_one_iteration(s, sector_num, n, ret);
|
trace_commit_one_iteration(s, sector_num, n, ret);
|
||||||
if (copy) {
|
if (copy) {
|
||||||
ret = commit_populate(s->top, s->base, sector_num, n, buf);
|
if (s->common.speed) {
|
||||||
|
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
||||||
|
if (delay_ns > 0) {
|
||||||
|
goto wait;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = commit_populate(top, base, sector_num, n, buf);
|
||||||
bytes_written += n * BDRV_SECTOR_SIZE;
|
bytes_written += n * BDRV_SECTOR_SIZE;
|
||||||
}
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
BlockErrorAction action =
|
if (s->on_error == BLOCKDEV_ON_ERROR_STOP ||
|
||||||
block_job_error_action(&s->common, false, s->on_error, -ret);
|
s->on_error == BLOCKDEV_ON_ERROR_REPORT||
|
||||||
if (action == BLOCK_ERROR_ACTION_REPORT) {
|
(s->on_error == BLOCKDEV_ON_ERROR_ENOSPC && ret == -ENOSPC)) {
|
||||||
goto out;
|
goto out;
|
||||||
} else {
|
} else {
|
||||||
n = 0;
|
n = 0;
|
||||||
@@ -200,10 +169,6 @@ static void coroutine_fn commit_run(void *opaque)
|
|||||||
}
|
}
|
||||||
/* Publish progress */
|
/* Publish progress */
|
||||||
s->common.offset += n * BDRV_SECTOR_SIZE;
|
s->common.offset += n * BDRV_SECTOR_SIZE;
|
||||||
|
|
||||||
if (copy && s->common.speed) {
|
|
||||||
delay_ns = ratelimit_calculate_delay(&s->limit, n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
@@ -221,7 +186,7 @@ static void commit_set_speed(BlockJob *job, int64_t speed, Error **errp)
|
|||||||
CommitBlockJob *s = container_of(job, CommitBlockJob, common);
|
CommitBlockJob *s = container_of(job, CommitBlockJob, common);
|
||||||
|
|
||||||
if (speed < 0) {
|
if (speed < 0) {
|
||||||
error_setg(errp, QERR_INVALID_PARAMETER, "speed");
|
error_set(errp, QERR_INVALID_PARAMETER, "speed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME);
|
||||||
@@ -231,70 +196,26 @@ static const BlockJobDriver commit_job_driver = {
|
|||||||
.instance_size = sizeof(CommitBlockJob),
|
.instance_size = sizeof(CommitBlockJob),
|
||||||
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
.job_type = BLOCK_JOB_TYPE_COMMIT,
|
||||||
.set_speed = commit_set_speed,
|
.set_speed = commit_set_speed,
|
||||||
.start = commit_run,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
|
void commit_start(BlockDriverState *bs, BlockDriverState *base,
|
||||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
BlockDriverState *top, int64_t speed,
|
||||||
{
|
BlockdevOnError on_error, BlockCompletionFunc *cb,
|
||||||
return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
|
void *opaque, const char *backing_file_str, Error **errp)
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t coroutine_fn bdrv_commit_top_get_block_status(
|
|
||||||
BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum,
|
|
||||||
BlockDriverState **file)
|
|
||||||
{
|
|
||||||
*pnum = nb_sectors;
|
|
||||||
*file = bs->backing->bs;
|
|
||||||
return BDRV_BLOCK_RAW | BDRV_BLOCK_OFFSET_VALID | BDRV_BLOCK_DATA |
|
|
||||||
(sector_num << BDRV_SECTOR_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bdrv_commit_top_refresh_filename(BlockDriverState *bs, QDict *opts)
|
|
||||||
{
|
|
||||||
bdrv_refresh_filename(bs->backing->bs);
|
|
||||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
|
||||||
bs->backing->bs->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bdrv_commit_top_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bdrv_commit_top_child_perm(BlockDriverState *bs, BdrvChild *c,
|
|
||||||
const BdrvChildRole *role,
|
|
||||||
uint64_t perm, uint64_t shared,
|
|
||||||
uint64_t *nperm, uint64_t *nshared)
|
|
||||||
{
|
|
||||||
*nperm = 0;
|
|
||||||
*nshared = BLK_PERM_ALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dummy node that provides consistent read to its users without requiring it
|
|
||||||
* from its backing file and that allows writes on the backing file chain. */
|
|
||||||
static BlockDriver bdrv_commit_top = {
|
|
||||||
.format_name = "commit_top",
|
|
||||||
.bdrv_co_preadv = bdrv_commit_top_preadv,
|
|
||||||
.bdrv_co_get_block_status = bdrv_commit_top_get_block_status,
|
|
||||||
.bdrv_refresh_filename = bdrv_commit_top_refresh_filename,
|
|
||||||
.bdrv_close = bdrv_commit_top_close,
|
|
||||||
.bdrv_child_perm = bdrv_commit_top_child_perm,
|
|
||||||
};
|
|
||||||
|
|
||||||
void commit_start(const char *job_id, BlockDriverState *bs,
|
|
||||||
BlockDriverState *base, BlockDriverState *top, int64_t speed,
|
|
||||||
BlockdevOnError on_error, const char *backing_file_str,
|
|
||||||
const char *filter_node_name, Error **errp)
|
|
||||||
{
|
{
|
||||||
CommitBlockJob *s;
|
CommitBlockJob *s;
|
||||||
BlockReopenQueue *reopen_queue = NULL;
|
BlockReopenQueue *reopen_queue = NULL;
|
||||||
int orig_overlay_flags;
|
int orig_overlay_flags;
|
||||||
int orig_base_flags;
|
int orig_base_flags;
|
||||||
BlockDriverState *iter;
|
|
||||||
BlockDriverState *overlay_bs;
|
BlockDriverState *overlay_bs;
|
||||||
BlockDriverState *commit_top_bs = NULL;
|
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
int ret;
|
|
||||||
|
if ((on_error == BLOCKDEV_ON_ERROR_STOP ||
|
||||||
|
on_error == BLOCKDEV_ON_ERROR_ENOSPC) &&
|
||||||
|
!bdrv_iostatus_is_enabled(bs)) {
|
||||||
|
error_setg(errp, "Invalid parameter combination");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
assert(top != bs);
|
assert(top != bs);
|
||||||
if (top == base) {
|
if (top == base) {
|
||||||
@@ -309,107 +230,34 @@ void commit_start(const char *job_id, BlockDriverState *bs,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s = block_job_create(job_id, &commit_job_driver, bs, 0, BLK_PERM_ALL,
|
|
||||||
speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp);
|
|
||||||
if (!s) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
orig_base_flags = bdrv_get_flags(base);
|
orig_base_flags = bdrv_get_flags(base);
|
||||||
orig_overlay_flags = bdrv_get_flags(overlay_bs);
|
orig_overlay_flags = bdrv_get_flags(overlay_bs);
|
||||||
|
|
||||||
/* convert base & overlay_bs to r/w, if necessary */
|
/* convert base & overlay_bs to r/w, if necessary */
|
||||||
if (!(orig_base_flags & BDRV_O_RDWR)) {
|
if (!(orig_base_flags & BDRV_O_RDWR)) {
|
||||||
reopen_queue = bdrv_reopen_queue(reopen_queue, base, NULL,
|
reopen_queue = bdrv_reopen_queue(reopen_queue, base,
|
||||||
orig_base_flags | BDRV_O_RDWR);
|
orig_base_flags | BDRV_O_RDWR);
|
||||||
}
|
}
|
||||||
if (!(orig_overlay_flags & BDRV_O_RDWR)) {
|
if (!(orig_overlay_flags & BDRV_O_RDWR)) {
|
||||||
reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs, NULL,
|
reopen_queue = bdrv_reopen_queue(reopen_queue, overlay_bs,
|
||||||
orig_overlay_flags | BDRV_O_RDWR);
|
orig_overlay_flags | BDRV_O_RDWR);
|
||||||
}
|
}
|
||||||
if (reopen_queue) {
|
if (reopen_queue) {
|
||||||
bdrv_reopen_multiple(bdrv_get_aio_context(bs), reopen_queue, &local_err);
|
bdrv_reopen_multiple(reopen_queue, &local_err);
|
||||||
if (local_err != NULL) {
|
if (local_err != NULL) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto fail;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert commit_top block node above top, so we can block consistent read
|
|
||||||
* on the backing chain below it */
|
|
||||||
commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, filter_node_name, 0,
|
|
||||||
errp);
|
|
||||||
if (commit_top_bs == NULL) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
commit_top_bs->total_sectors = top->total_sectors;
|
|
||||||
bdrv_set_aio_context(commit_top_bs, bdrv_get_aio_context(top));
|
|
||||||
|
|
||||||
bdrv_set_backing_hd(commit_top_bs, top, &local_err);
|
s = block_job_create(&commit_job_driver, bs, speed, cb, opaque, errp);
|
||||||
if (local_err) {
|
if (!s) {
|
||||||
bdrv_unref(commit_top_bs);
|
return;
|
||||||
commit_top_bs = NULL;
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
bdrv_set_backing_hd(overlay_bs, commit_top_bs, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
bdrv_unref(commit_top_bs);
|
|
||||||
commit_top_bs = NULL;
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->commit_top_bs = commit_top_bs;
|
|
||||||
bdrv_unref(commit_top_bs);
|
|
||||||
|
|
||||||
/* Block all nodes between top and base, because they will
|
|
||||||
* disappear from the chain after this operation. */
|
|
||||||
assert(bdrv_chain_contains(top, base));
|
|
||||||
for (iter = top; iter != base; iter = backing_bs(iter)) {
|
|
||||||
/* XXX BLK_PERM_WRITE needs to be allowed so we don't block ourselves
|
|
||||||
* at s->base (if writes are blocked for a node, they are also blocked
|
|
||||||
* for its backing file). The other options would be a second filter
|
|
||||||
* driver above s->base. */
|
|
||||||
ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
|
|
||||||
BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE,
|
|
||||||
errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* overlay_bs must be blocked because it needs to be modified to
|
|
||||||
* update the backing image string. */
|
|
||||||
ret = block_job_add_bdrv(&s->common, "overlay of top", overlay_bs,
|
|
||||||
BLK_PERM_GRAPH_MOD, BLK_PERM_ALL, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->base = blk_new(BLK_PERM_CONSISTENT_READ
|
|
||||||
| BLK_PERM_WRITE
|
|
||||||
| BLK_PERM_RESIZE,
|
|
||||||
BLK_PERM_CONSISTENT_READ
|
|
||||||
| BLK_PERM_GRAPH_MOD
|
|
||||||
| BLK_PERM_WRITE_UNCHANGED);
|
|
||||||
ret = blk_insert_bs(s->base, base, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Required permissions are already taken with block_job_add_bdrv() */
|
|
||||||
s->top = blk_new(0, BLK_PERM_ALL);
|
|
||||||
ret = blk_insert_bs(s->top, top, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s->base = base;
|
||||||
|
s->top = top;
|
||||||
s->active = bs;
|
s->active = bs;
|
||||||
|
|
||||||
s->base_flags = orig_base_flags;
|
s->base_flags = orig_base_flags;
|
||||||
@@ -418,172 +266,8 @@ void commit_start(const char *job_id, BlockDriverState *bs,
|
|||||||
s->backing_file_str = g_strdup(backing_file_str);
|
s->backing_file_str = g_strdup(backing_file_str);
|
||||||
|
|
||||||
s->on_error = on_error;
|
s->on_error = on_error;
|
||||||
|
s->common.co = qemu_coroutine_create(commit_run);
|
||||||
|
|
||||||
trace_commit_start(bs, base, top, s);
|
trace_commit_start(bs, base, top, s, s->common.co, opaque);
|
||||||
block_job_start(&s->common);
|
qemu_coroutine_enter(s->common.co, s);
|
||||||
return;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if (s->base) {
|
|
||||||
blk_unref(s->base);
|
|
||||||
}
|
|
||||||
if (s->top) {
|
|
||||||
blk_unref(s->top);
|
|
||||||
}
|
|
||||||
if (commit_top_bs) {
|
|
||||||
bdrv_set_backing_hd(overlay_bs, top, &error_abort);
|
|
||||||
}
|
|
||||||
block_job_early_fail(&s->common);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define COMMIT_BUF_SECTORS 2048
|
|
||||||
|
|
||||||
/* commit COW file into the raw image */
|
|
||||||
int bdrv_commit(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockBackend *src, *backing;
|
|
||||||
BlockDriverState *backing_file_bs = NULL;
|
|
||||||
BlockDriverState *commit_top_bs = NULL;
|
|
||||||
BlockDriver *drv = bs->drv;
|
|
||||||
int64_t sector, total_sectors, length, backing_length;
|
|
||||||
int n, ro, open_flags;
|
|
||||||
int ret = 0;
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
if (!drv)
|
|
||||||
return -ENOMEDIUM;
|
|
||||||
|
|
||||||
if (!bs->backing) {
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_COMMIT_SOURCE, NULL) ||
|
|
||||||
bdrv_op_is_blocked(bs->backing->bs, BLOCK_OP_TYPE_COMMIT_TARGET, NULL)) {
|
|
||||||
return -EBUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
ro = bs->backing->bs->read_only;
|
|
||||||
open_flags = bs->backing->bs->open_flags;
|
|
||||||
|
|
||||||
if (ro) {
|
|
||||||
if (bdrv_reopen(bs->backing->bs, open_flags | BDRV_O_RDWR, NULL)) {
|
|
||||||
return -EACCES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
src = blk_new(BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL);
|
|
||||||
backing = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
|
|
||||||
|
|
||||||
ret = blk_insert_bs(src, bs, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report_err(local_err);
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Insert commit_top block node above backing, so we can write to it */
|
|
||||||
backing_file_bs = backing_bs(bs);
|
|
||||||
|
|
||||||
commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, NULL, BDRV_O_RDWR,
|
|
||||||
&local_err);
|
|
||||||
if (commit_top_bs == NULL) {
|
|
||||||
error_report_err(local_err);
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
bdrv_set_aio_context(commit_top_bs, bdrv_get_aio_context(backing_file_bs));
|
|
||||||
|
|
||||||
bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort);
|
|
||||||
bdrv_set_backing_hd(bs, commit_top_bs, &error_abort);
|
|
||||||
|
|
||||||
ret = blk_insert_bs(backing, backing_file_bs, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report_err(local_err);
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
length = blk_getlength(src);
|
|
||||||
if (length < 0) {
|
|
||||||
ret = length;
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
backing_length = blk_getlength(backing);
|
|
||||||
if (backing_length < 0) {
|
|
||||||
ret = backing_length;
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If our top snapshot is larger than the backing file image,
|
|
||||||
* grow the backing file image if possible. If not possible,
|
|
||||||
* we must return an error */
|
|
||||||
if (length > backing_length) {
|
|
||||||
ret = blk_truncate(backing, length, &local_err);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_report_err(local_err);
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total_sectors = length >> BDRV_SECTOR_BITS;
|
|
||||||
|
|
||||||
/* blk_try_blockalign() for src will choose an alignment that works for
|
|
||||||
* backing as well, so no need to compare the alignment manually. */
|
|
||||||
buf = blk_try_blockalign(src, COMMIT_BUF_SECTORS * BDRV_SECTOR_SIZE);
|
|
||||||
if (buf == NULL) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (sector = 0; sector < total_sectors; sector += n) {
|
|
||||||
ret = bdrv_is_allocated(bs, sector, COMMIT_BUF_SECTORS, &n);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
if (ret) {
|
|
||||||
ret = blk_pread(src, sector * BDRV_SECTOR_SIZE, buf,
|
|
||||||
n * BDRV_SECTOR_SIZE);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = blk_pwrite(backing, sector * BDRV_SECTOR_SIZE, buf,
|
|
||||||
n * BDRV_SECTOR_SIZE, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drv->bdrv_make_empty) {
|
|
||||||
ret = drv->bdrv_make_empty(bs);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto ro_cleanup;
|
|
||||||
}
|
|
||||||
blk_flush(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure all data we wrote to the backing device is actually
|
|
||||||
* stable on disk.
|
|
||||||
*/
|
|
||||||
blk_flush(backing);
|
|
||||||
|
|
||||||
ret = 0;
|
|
||||||
ro_cleanup:
|
|
||||||
qemu_vfree(buf);
|
|
||||||
|
|
||||||
blk_unref(backing);
|
|
||||||
if (backing_file_bs) {
|
|
||||||
bdrv_set_backing_hd(bs, backing_file_bs, &error_abort);
|
|
||||||
}
|
|
||||||
bdrv_unref(commit_top_bs);
|
|
||||||
blk_unref(src);
|
|
||||||
|
|
||||||
if (ro) {
|
|
||||||
/* ignoring error return here */
|
|
||||||
bdrv_reopen(bs->backing->bs, open_flags & ~BDRV_O_RDWR, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|||||||
649
block/crypto.c
649
block/crypto.c
@@ -1,649 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU block full disk encryption
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015-2016 Red Hat, Inc.
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "sysemu/block-backend.h"
|
|
||||||
#include "crypto/block.h"
|
|
||||||
#include "qapi/opts-visitor.h"
|
|
||||||
#include "qapi-visit.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET "key-secret"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG "cipher-alg"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE "cipher-mode"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG "ivgen-alg"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg"
|
|
||||||
#define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time"
|
|
||||||
|
|
||||||
typedef struct BlockCrypto BlockCrypto;
|
|
||||||
|
|
||||||
struct BlockCrypto {
|
|
||||||
QCryptoBlock *block;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int block_crypto_probe_generic(QCryptoBlockFormat format,
|
|
||||||
const uint8_t *buf,
|
|
||||||
int buf_size,
|
|
||||||
const char *filename)
|
|
||||||
{
|
|
||||||
if (qcrypto_block_has_format(format, buf, buf_size)) {
|
|
||||||
return 100;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static ssize_t block_crypto_read_func(QCryptoBlock *block,
|
|
||||||
size_t offset,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t buflen,
|
|
||||||
void *opaque,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockDriverState *bs = opaque;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
ret = bdrv_pread(bs->file, offset, buf, buflen);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Could not read encryption header");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct BlockCryptoCreateData {
|
|
||||||
const char *filename;
|
|
||||||
QemuOpts *opts;
|
|
||||||
BlockBackend *blk;
|
|
||||||
uint64_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static ssize_t block_crypto_write_func(QCryptoBlock *block,
|
|
||||||
size_t offset,
|
|
||||||
const uint8_t *buf,
|
|
||||||
size_t buflen,
|
|
||||||
void *opaque,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
struct BlockCryptoCreateData *data = opaque;
|
|
||||||
ssize_t ret;
|
|
||||||
|
|
||||||
ret = blk_pwrite(data->blk, offset, buf, buflen, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Could not write encryption header");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static ssize_t block_crypto_init_func(QCryptoBlock *block,
|
|
||||||
size_t headerlen,
|
|
||||||
void *opaque,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
struct BlockCryptoCreateData *data = opaque;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* User provided size should reflect amount of space made
|
|
||||||
* available to the guest, so we must take account of that
|
|
||||||
* which will be used by the crypto header
|
|
||||||
*/
|
|
||||||
data->size += headerlen;
|
|
||||||
|
|
||||||
qemu_opt_set_number(data->opts, BLOCK_OPT_SIZE, data->size, &error_abort);
|
|
||||||
ret = bdrv_create_file(data->filename, data->opts, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->blk = blk_new_open(data->filename, NULL, NULL,
|
|
||||||
BDRV_O_RDWR | BDRV_O_PROTOCOL, errp);
|
|
||||||
if (!data->blk) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static QemuOptsList block_crypto_runtime_opts_luks = {
|
|
||||||
.name = "crypto",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_runtime_opts_luks.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of the secret that provides the encryption key",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static QemuOptsList block_crypto_create_opts_luks = {
|
|
||||||
.name = "crypto",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(block_crypto_create_opts_luks.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = BLOCK_OPT_SIZE,
|
|
||||||
.type = QEMU_OPT_SIZE,
|
|
||||||
.help = "Virtual disk size"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_KEY_SECRET,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of the secret that provides the encryption key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_ALG,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of encryption cipher algorithm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_CIPHER_MODE,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of encryption cipher mode",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_ALG,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of IV generator algorithm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of IV generator hash algorithm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_HASH_ALG,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of encryption hash algorithm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = BLOCK_CRYPTO_OPT_LUKS_ITER_TIME,
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Time to spend in PBKDF in milliseconds",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static QCryptoBlockOpenOptions *
|
|
||||||
block_crypto_open_opts_init(QCryptoBlockFormat format,
|
|
||||||
QemuOpts *opts,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
Visitor *v;
|
|
||||||
QCryptoBlockOpenOptions *ret = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
ret = g_new0(QCryptoBlockOpenOptions, 1);
|
|
||||||
ret->format = format;
|
|
||||||
|
|
||||||
v = opts_visitor_new(opts);
|
|
||||||
|
|
||||||
visit_start_struct(v, NULL, NULL, 0, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case Q_CRYPTO_BLOCK_FORMAT_LUKS:
|
|
||||||
visit_type_QCryptoBlockOptionsLUKS_members(
|
|
||||||
v, &ret->u.luks, &local_err);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
error_setg(&local_err, "Unsupported block format %d", format);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!local_err) {
|
|
||||||
visit_check_struct(v, &local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
visit_end_struct(v, NULL);
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
qapi_free_QCryptoBlockOpenOptions(ret);
|
|
||||||
ret = NULL;
|
|
||||||
}
|
|
||||||
visit_free(v);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static QCryptoBlockCreateOptions *
|
|
||||||
block_crypto_create_opts_init(QCryptoBlockFormat format,
|
|
||||||
QemuOpts *opts,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
Visitor *v;
|
|
||||||
QCryptoBlockCreateOptions *ret = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
ret = g_new0(QCryptoBlockCreateOptions, 1);
|
|
||||||
ret->format = format;
|
|
||||||
|
|
||||||
v = opts_visitor_new(opts);
|
|
||||||
|
|
||||||
visit_start_struct(v, NULL, NULL, 0, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case Q_CRYPTO_BLOCK_FORMAT_LUKS:
|
|
||||||
visit_type_QCryptoBlockCreateOptionsLUKS_members(
|
|
||||||
v, &ret->u.luks, &local_err);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
error_setg(&local_err, "Unsupported block format %d", format);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!local_err) {
|
|
||||||
visit_check_struct(v, &local_err);
|
|
||||||
}
|
|
||||||
|
|
||||||
visit_end_struct(v, NULL);
|
|
||||||
|
|
||||||
out:
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
qapi_free_QCryptoBlockCreateOptions(ret);
|
|
||||||
ret = NULL;
|
|
||||||
}
|
|
||||||
visit_free(v);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int block_crypto_open_generic(QCryptoBlockFormat format,
|
|
||||||
QemuOptsList *opts_spec,
|
|
||||||
BlockDriverState *bs,
|
|
||||||
QDict *options,
|
|
||||||
int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
QemuOpts *opts = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
int ret = -EINVAL;
|
|
||||||
QCryptoBlockOpenOptions *open_opts = NULL;
|
|
||||||
unsigned int cflags = 0;
|
|
||||||
|
|
||||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
|
||||||
false, errp);
|
|
||||||
if (!bs->file) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = qemu_opts_create(opts_spec, NULL, 0, &error_abort);
|
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
open_opts = block_crypto_open_opts_init(format, opts, errp);
|
|
||||||
if (!open_opts) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & BDRV_O_NO_IO) {
|
|
||||||
cflags |= QCRYPTO_BLOCK_OPEN_NO_IO;
|
|
||||||
}
|
|
||||||
crypto->block = qcrypto_block_open(open_opts,
|
|
||||||
block_crypto_read_func,
|
|
||||||
bs,
|
|
||||||
cflags,
|
|
||||||
errp);
|
|
||||||
|
|
||||||
if (!crypto->block) {
|
|
||||||
ret = -EIO;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
bs->encrypted = true;
|
|
||||||
bs->valid_key = true;
|
|
||||||
|
|
||||||
ret = 0;
|
|
||||||
cleanup:
|
|
||||||
qapi_free_QCryptoBlockOpenOptions(open_opts);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int block_crypto_create_generic(QCryptoBlockFormat format,
|
|
||||||
const char *filename,
|
|
||||||
QemuOpts *opts,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int ret = -EINVAL;
|
|
||||||
QCryptoBlockCreateOptions *create_opts = NULL;
|
|
||||||
QCryptoBlock *crypto = NULL;
|
|
||||||
struct BlockCryptoCreateData data = {
|
|
||||||
.size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
|
||||||
BDRV_SECTOR_SIZE),
|
|
||||||
.opts = opts,
|
|
||||||
.filename = filename,
|
|
||||||
};
|
|
||||||
|
|
||||||
create_opts = block_crypto_create_opts_init(format, opts, errp);
|
|
||||||
if (!create_opts) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
crypto = qcrypto_block_create(create_opts,
|
|
||||||
block_crypto_init_func,
|
|
||||||
block_crypto_write_func,
|
|
||||||
&data,
|
|
||||||
errp);
|
|
||||||
|
|
||||||
if (!crypto) {
|
|
||||||
ret = -EIO;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = 0;
|
|
||||||
cleanup:
|
|
||||||
qcrypto_block_free(crypto);
|
|
||||||
blk_unref(data.blk);
|
|
||||||
qapi_free_QCryptoBlockCreateOptions(create_opts);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int block_crypto_truncate(BlockDriverState *bs, int64_t offset,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
size_t payload_offset =
|
|
||||||
qcrypto_block_get_payload_offset(crypto->block);
|
|
||||||
|
|
||||||
offset += payload_offset;
|
|
||||||
|
|
||||||
return bdrv_truncate(bs->file, offset, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void block_crypto_close(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
qcrypto_block_free(crypto->block);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#define BLOCK_CRYPTO_MAX_SECTORS 32
|
|
||||||
|
|
||||||
static coroutine_fn int
|
|
||||||
block_crypto_co_readv(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
int remaining_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
int cur_nr_sectors; /* number of sectors in current iteration */
|
|
||||||
uint64_t bytes_done = 0;
|
|
||||||
uint8_t *cipher_data = NULL;
|
|
||||||
QEMUIOVector hd_qiov;
|
|
||||||
int ret = 0;
|
|
||||||
size_t payload_offset =
|
|
||||||
qcrypto_block_get_payload_offset(crypto->block) / 512;
|
|
||||||
|
|
||||||
qemu_iovec_init(&hd_qiov, qiov->niov);
|
|
||||||
|
|
||||||
/* Bounce buffer so we have a linear mem region for
|
|
||||||
* entire sector. XXX optimize so we avoid bounce
|
|
||||||
* buffer in case that qiov->niov == 1
|
|
||||||
*/
|
|
||||||
cipher_data =
|
|
||||||
qemu_try_blockalign(bs->file->bs, MIN(BLOCK_CRYPTO_MAX_SECTORS * 512,
|
|
||||||
qiov->size));
|
|
||||||
if (cipher_data == NULL) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (remaining_sectors) {
|
|
||||||
cur_nr_sectors = remaining_sectors;
|
|
||||||
|
|
||||||
if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
|
|
||||||
cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_iovec_reset(&hd_qiov);
|
|
||||||
qemu_iovec_add(&hd_qiov, cipher_data, cur_nr_sectors * 512);
|
|
||||||
|
|
||||||
ret = bdrv_co_readv(bs->file,
|
|
||||||
payload_offset + sector_num,
|
|
||||||
cur_nr_sectors, &hd_qiov);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qcrypto_block_decrypt(crypto->block,
|
|
||||||
sector_num,
|
|
||||||
cipher_data, cur_nr_sectors * 512,
|
|
||||||
NULL) < 0) {
|
|
||||||
ret = -EIO;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_iovec_from_buf(qiov, bytes_done,
|
|
||||||
cipher_data, cur_nr_sectors * 512);
|
|
||||||
|
|
||||||
remaining_sectors -= cur_nr_sectors;
|
|
||||||
sector_num += cur_nr_sectors;
|
|
||||||
bytes_done += cur_nr_sectors * 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
qemu_iovec_destroy(&hd_qiov);
|
|
||||||
qemu_vfree(cipher_data);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static coroutine_fn int
|
|
||||||
block_crypto_co_writev(BlockDriverState *bs, int64_t sector_num,
|
|
||||||
int remaining_sectors, QEMUIOVector *qiov)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
int cur_nr_sectors; /* number of sectors in current iteration */
|
|
||||||
uint64_t bytes_done = 0;
|
|
||||||
uint8_t *cipher_data = NULL;
|
|
||||||
QEMUIOVector hd_qiov;
|
|
||||||
int ret = 0;
|
|
||||||
size_t payload_offset =
|
|
||||||
qcrypto_block_get_payload_offset(crypto->block) / 512;
|
|
||||||
|
|
||||||
qemu_iovec_init(&hd_qiov, qiov->niov);
|
|
||||||
|
|
||||||
/* Bounce buffer so we have a linear mem region for
|
|
||||||
* entire sector. XXX optimize so we avoid bounce
|
|
||||||
* buffer in case that qiov->niov == 1
|
|
||||||
*/
|
|
||||||
cipher_data =
|
|
||||||
qemu_try_blockalign(bs->file->bs, MIN(BLOCK_CRYPTO_MAX_SECTORS * 512,
|
|
||||||
qiov->size));
|
|
||||||
if (cipher_data == NULL) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (remaining_sectors) {
|
|
||||||
cur_nr_sectors = remaining_sectors;
|
|
||||||
|
|
||||||
if (cur_nr_sectors > BLOCK_CRYPTO_MAX_SECTORS) {
|
|
||||||
cur_nr_sectors = BLOCK_CRYPTO_MAX_SECTORS;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_iovec_to_buf(qiov, bytes_done,
|
|
||||||
cipher_data, cur_nr_sectors * 512);
|
|
||||||
|
|
||||||
if (qcrypto_block_encrypt(crypto->block,
|
|
||||||
sector_num,
|
|
||||||
cipher_data, cur_nr_sectors * 512,
|
|
||||||
NULL) < 0) {
|
|
||||||
ret = -EIO;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_iovec_reset(&hd_qiov);
|
|
||||||
qemu_iovec_add(&hd_qiov, cipher_data, cur_nr_sectors * 512);
|
|
||||||
|
|
||||||
ret = bdrv_co_writev(bs->file,
|
|
||||||
payload_offset + sector_num,
|
|
||||||
cur_nr_sectors, &hd_qiov);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining_sectors -= cur_nr_sectors;
|
|
||||||
sector_num += cur_nr_sectors;
|
|
||||||
bytes_done += cur_nr_sectors * 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
qemu_iovec_destroy(&hd_qiov);
|
|
||||||
qemu_vfree(cipher_data);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int64_t block_crypto_getlength(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
int64_t len = bdrv_getlength(bs->file->bs);
|
|
||||||
|
|
||||||
ssize_t offset = qcrypto_block_get_payload_offset(crypto->block);
|
|
||||||
|
|
||||||
len -= offset;
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int block_crypto_probe_luks(const uint8_t *buf,
|
|
||||||
int buf_size,
|
|
||||||
const char *filename) {
|
|
||||||
return block_crypto_probe_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
||||||
buf, buf_size, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int block_crypto_open_luks(BlockDriverState *bs,
|
|
||||||
QDict *options,
|
|
||||||
int flags,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
return block_crypto_open_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
||||||
&block_crypto_runtime_opts_luks,
|
|
||||||
bs, options, flags, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int block_crypto_create_luks(const char *filename,
|
|
||||||
QemuOpts *opts,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
return block_crypto_create_generic(Q_CRYPTO_BLOCK_FORMAT_LUKS,
|
|
||||||
filename, opts, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int block_crypto_get_info_luks(BlockDriverState *bs,
|
|
||||||
BlockDriverInfo *bdi)
|
|
||||||
{
|
|
||||||
BlockDriverInfo subbdi;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = bdrv_get_info(bs->file->bs, &subbdi);
|
|
||||||
if (ret != 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bdi->unallocated_blocks_are_zero = false;
|
|
||||||
bdi->can_write_zeroes_with_unmap = false;
|
|
||||||
bdi->cluster_size = subbdi.cluster_size;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ImageInfoSpecific *
|
|
||||||
block_crypto_get_specific_info_luks(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockCrypto *crypto = bs->opaque;
|
|
||||||
ImageInfoSpecific *spec_info;
|
|
||||||
QCryptoBlockInfo *info;
|
|
||||||
|
|
||||||
info = qcrypto_block_get_info(crypto->block, NULL);
|
|
||||||
if (!info) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (info->format != Q_CRYPTO_BLOCK_FORMAT_LUKS) {
|
|
||||||
qapi_free_QCryptoBlockInfo(info);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
spec_info = g_new(ImageInfoSpecific, 1);
|
|
||||||
spec_info->type = IMAGE_INFO_SPECIFIC_KIND_LUKS;
|
|
||||||
spec_info->u.luks.data = g_new(QCryptoBlockInfoLUKS, 1);
|
|
||||||
*spec_info->u.luks.data = info->u.luks;
|
|
||||||
|
|
||||||
/* Blank out pointers we've just stolen to avoid double free */
|
|
||||||
memset(&info->u.luks, 0, sizeof(info->u.luks));
|
|
||||||
|
|
||||||
qapi_free_QCryptoBlockInfo(info);
|
|
||||||
|
|
||||||
return spec_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockDriver bdrv_crypto_luks = {
|
|
||||||
.format_name = "luks",
|
|
||||||
.instance_size = sizeof(BlockCrypto),
|
|
||||||
.bdrv_probe = block_crypto_probe_luks,
|
|
||||||
.bdrv_open = block_crypto_open_luks,
|
|
||||||
.bdrv_close = block_crypto_close,
|
|
||||||
.bdrv_child_perm = bdrv_format_default_perms,
|
|
||||||
.bdrv_create = block_crypto_create_luks,
|
|
||||||
.bdrv_truncate = block_crypto_truncate,
|
|
||||||
.create_opts = &block_crypto_create_opts_luks,
|
|
||||||
|
|
||||||
.bdrv_co_readv = block_crypto_co_readv,
|
|
||||||
.bdrv_co_writev = block_crypto_co_writev,
|
|
||||||
.bdrv_getlength = block_crypto_getlength,
|
|
||||||
.bdrv_get_info = block_crypto_get_info_luks,
|
|
||||||
.bdrv_get_specific_info = block_crypto_get_specific_info_luks,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void block_crypto_init(void)
|
|
||||||
{
|
|
||||||
bdrv_register(&bdrv_crypto_luks);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(block_crypto_init);
|
|
||||||
507
block/curl.c
507
block/curl.c
@@ -21,31 +21,19 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qapi/qmp/qbool.h"
|
#include "qapi/qmp/qbool.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
|
||||||
#include "crypto/secret.h"
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include "qemu/cutils.h"
|
|
||||||
|
|
||||||
// #define DEBUG_CURL
|
// #define DEBUG_CURL
|
||||||
// #define DEBUG_VERBOSE
|
// #define DEBUG_VERBOSE
|
||||||
|
|
||||||
#ifdef DEBUG_CURL
|
#ifdef DEBUG_CURL
|
||||||
#define DEBUG_CURL_PRINT 1
|
#define DPRINTF(fmt, ...) do { printf(fmt, ## __VA_ARGS__); } while (0)
|
||||||
#else
|
#else
|
||||||
#define DEBUG_CURL_PRINT 0
|
#define DPRINTF(fmt, ...) do { } while (0)
|
||||||
#endif
|
#endif
|
||||||
#define DPRINTF(fmt, ...) \
|
|
||||||
do { \
|
|
||||||
if (DEBUG_CURL_PRINT) { \
|
|
||||||
fprintf(stderr, fmt, ## __VA_ARGS__); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#if LIBCURL_VERSION_NUM >= 0x071000
|
#if LIBCURL_VERSION_NUM >= 0x071000
|
||||||
/* The multi interface timer callback was introduced in 7.16.0 */
|
/* The multi interface timer callback was introduced in 7.16.0 */
|
||||||
@@ -68,54 +56,48 @@ static CURLMcode __curl_multi_socket_action(CURLM *multi_handle,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \
|
#define PROTOCOLS (CURLPROTO_HTTP | CURLPROTO_HTTPS | \
|
||||||
CURLPROTO_FTP | CURLPROTO_FTPS)
|
CURLPROTO_FTP | CURLPROTO_FTPS | \
|
||||||
|
CURLPROTO_TFTP)
|
||||||
|
|
||||||
#define CURL_NUM_STATES 8
|
#define CURL_NUM_STATES 8
|
||||||
#define CURL_NUM_ACB 8
|
#define CURL_NUM_ACB 8
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
#define READ_AHEAD_DEFAULT (256 * 1024)
|
#define READ_AHEAD_DEFAULT (256 * 1024)
|
||||||
#define CURL_TIMEOUT_DEFAULT 5
|
#define CURL_TIMEOUT_DEFAULT 5
|
||||||
#define CURL_TIMEOUT_MAX 10000
|
#define CURL_TIMEOUT_MAX 10000
|
||||||
|
|
||||||
|
#define FIND_RET_NONE 0
|
||||||
|
#define FIND_RET_OK 1
|
||||||
|
#define FIND_RET_WAIT 2
|
||||||
|
|
||||||
#define CURL_BLOCK_OPT_URL "url"
|
#define CURL_BLOCK_OPT_URL "url"
|
||||||
#define CURL_BLOCK_OPT_READAHEAD "readahead"
|
#define CURL_BLOCK_OPT_READAHEAD "readahead"
|
||||||
#define CURL_BLOCK_OPT_SSLVERIFY "sslverify"
|
#define CURL_BLOCK_OPT_SSLVERIFY "sslverify"
|
||||||
#define CURL_BLOCK_OPT_TIMEOUT "timeout"
|
#define CURL_BLOCK_OPT_TIMEOUT "timeout"
|
||||||
#define CURL_BLOCK_OPT_COOKIE "cookie"
|
#define CURL_BLOCK_OPT_COOKIE "cookie"
|
||||||
#define CURL_BLOCK_OPT_COOKIE_SECRET "cookie-secret"
|
|
||||||
#define CURL_BLOCK_OPT_USERNAME "username"
|
|
||||||
#define CURL_BLOCK_OPT_PASSWORD_SECRET "password-secret"
|
|
||||||
#define CURL_BLOCK_OPT_PROXY_USERNAME "proxy-username"
|
|
||||||
#define CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET "proxy-password-secret"
|
|
||||||
|
|
||||||
struct BDRVCURLState;
|
struct BDRVCURLState;
|
||||||
|
|
||||||
typedef struct CURLAIOCB {
|
typedef struct CURLAIOCB {
|
||||||
Coroutine *co;
|
BlockAIOCB common;
|
||||||
|
QEMUBH *bh;
|
||||||
QEMUIOVector *qiov;
|
QEMUIOVector *qiov;
|
||||||
|
|
||||||
uint64_t offset;
|
int64_t sector_num;
|
||||||
uint64_t bytes;
|
int nb_sectors;
|
||||||
int ret;
|
|
||||||
|
|
||||||
size_t start;
|
size_t start;
|
||||||
size_t end;
|
size_t end;
|
||||||
|
|
||||||
QSIMPLEQ_ENTRY(CURLAIOCB) next;
|
|
||||||
} CURLAIOCB;
|
} CURLAIOCB;
|
||||||
|
|
||||||
typedef struct CURLSocket {
|
|
||||||
int fd;
|
|
||||||
QLIST_ENTRY(CURLSocket) next;
|
|
||||||
} CURLSocket;
|
|
||||||
|
|
||||||
typedef struct CURLState
|
typedef struct CURLState
|
||||||
{
|
{
|
||||||
struct BDRVCURLState *s;
|
struct BDRVCURLState *s;
|
||||||
CURLAIOCB *acb[CURL_NUM_ACB];
|
CURLAIOCB *acb[CURL_NUM_ACB];
|
||||||
CURL *curl;
|
CURL *curl;
|
||||||
QLIST_HEAD(, CURLSocket) sockets;
|
curl_socket_t sock_fd;
|
||||||
char *orig_buf;
|
char *orig_buf;
|
||||||
uint64_t buf_start;
|
size_t buf_start;
|
||||||
size_t buf_off;
|
size_t buf_off;
|
||||||
size_t buf_len;
|
size_t buf_len;
|
||||||
char range[128];
|
char range[128];
|
||||||
@@ -126,7 +108,7 @@ typedef struct CURLState
|
|||||||
typedef struct BDRVCURLState {
|
typedef struct BDRVCURLState {
|
||||||
CURLM *multi;
|
CURLM *multi;
|
||||||
QEMUTimer timer;
|
QEMUTimer timer;
|
||||||
uint64_t len;
|
size_t len;
|
||||||
CURLState states[CURL_NUM_STATES];
|
CURLState states[CURL_NUM_STATES];
|
||||||
char *url;
|
char *url;
|
||||||
size_t readahead_size;
|
size_t readahead_size;
|
||||||
@@ -135,12 +117,6 @@ typedef struct BDRVCURLState {
|
|||||||
char *cookie;
|
char *cookie;
|
||||||
bool accept_range;
|
bool accept_range;
|
||||||
AioContext *aio_context;
|
AioContext *aio_context;
|
||||||
QemuMutex mutex;
|
|
||||||
QSIMPLEQ_HEAD(, CURLAIOCB) free_state_waitq;
|
|
||||||
char *username;
|
|
||||||
char *password;
|
|
||||||
char *proxyusername;
|
|
||||||
char *proxypassword;
|
|
||||||
} BDRVCURLState;
|
} BDRVCURLState;
|
||||||
|
|
||||||
static void curl_clean_state(CURLState *s);
|
static void curl_clean_state(CURLState *s);
|
||||||
@@ -148,7 +124,6 @@ static void curl_multi_do(void *arg);
|
|||||||
static void curl_multi_read(void *arg);
|
static void curl_multi_read(void *arg);
|
||||||
|
|
||||||
#ifdef NEED_CURL_TIMER_CALLBACK
|
#ifdef NEED_CURL_TIMER_CALLBACK
|
||||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
|
||||||
static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
||||||
{
|
{
|
||||||
BDRVCURLState *s = opaque;
|
BDRVCURLState *s = opaque;
|
||||||
@@ -165,57 +140,36 @@ static int curl_timer_cb(CURLM *multi, long timeout_ms, void *opaque)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
|
||||||
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action,
|
||||||
void *userp, void *sp)
|
void *userp, void *sp)
|
||||||
{
|
{
|
||||||
BDRVCURLState *s;
|
BDRVCURLState *s;
|
||||||
CURLState *state = NULL;
|
CURLState *state = NULL;
|
||||||
CURLSocket *socket;
|
|
||||||
|
|
||||||
curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&state);
|
curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char **)&state);
|
||||||
|
state->sock_fd = fd;
|
||||||
s = state->s;
|
s = state->s;
|
||||||
|
|
||||||
QLIST_FOREACH(socket, &state->sockets, next) {
|
DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, fd);
|
||||||
if (socket->fd == fd) {
|
|
||||||
if (action == CURL_POLL_REMOVE) {
|
|
||||||
QLIST_REMOVE(socket, next);
|
|
||||||
g_free(socket);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!socket) {
|
|
||||||
socket = g_new0(CURLSocket, 1);
|
|
||||||
socket->fd = fd;
|
|
||||||
QLIST_INSERT_HEAD(&state->sockets, socket, next);
|
|
||||||
}
|
|
||||||
socket = NULL;
|
|
||||||
|
|
||||||
DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, (int)fd);
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case CURL_POLL_IN:
|
case CURL_POLL_IN:
|
||||||
aio_set_fd_handler(s->aio_context, fd, false,
|
aio_set_fd_handler(s->aio_context, fd, curl_multi_read,
|
||||||
curl_multi_read, NULL, NULL, state);
|
NULL, state);
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_OUT:
|
case CURL_POLL_OUT:
|
||||||
aio_set_fd_handler(s->aio_context, fd, false,
|
aio_set_fd_handler(s->aio_context, fd, NULL, curl_multi_do, state);
|
||||||
NULL, curl_multi_do, NULL, state);
|
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_INOUT:
|
case CURL_POLL_INOUT:
|
||||||
aio_set_fd_handler(s->aio_context, fd, false,
|
aio_set_fd_handler(s->aio_context, fd, curl_multi_read,
|
||||||
curl_multi_read, curl_multi_do, NULL, state);
|
curl_multi_do, state);
|
||||||
break;
|
break;
|
||||||
case CURL_POLL_REMOVE:
|
case CURL_POLL_REMOVE:
|
||||||
aio_set_fd_handler(s->aio_context, fd, false,
|
aio_set_fd_handler(s->aio_context, fd, NULL, NULL, NULL);
|
||||||
NULL, NULL, NULL, NULL);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
|
||||||
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||||
{
|
{
|
||||||
BDRVCURLState *s = opaque;
|
BDRVCURLState *s = opaque;
|
||||||
@@ -230,7 +184,6 @@ static size_t curl_header_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
|||||||
return realsize;
|
return realsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called from curl_multi_do_locked, with s->mutex held. */
|
|
||||||
static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
||||||
{
|
{
|
||||||
CURLState *s = ((CURLState*)opaque);
|
CURLState *s = ((CURLState*)opaque);
|
||||||
@@ -239,13 +192,12 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
|||||||
|
|
||||||
DPRINTF("CURL: Just reading %zd bytes\n", realsize);
|
DPRINTF("CURL: Just reading %zd bytes\n", realsize);
|
||||||
|
|
||||||
if (!s || !s->orig_buf) {
|
if (!s || !s->orig_buf)
|
||||||
goto read_end;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (s->buf_off >= s->buf_len) {
|
if (s->buf_off >= s->buf_len) {
|
||||||
/* buffer full, read nothing */
|
/* buffer full, read nothing */
|
||||||
goto read_end;
|
return 0;
|
||||||
}
|
}
|
||||||
realsize = MIN(realsize, s->buf_len - s->buf_off);
|
realsize = MIN(realsize, s->buf_len - s->buf_off);
|
||||||
memcpy(s->orig_buf + s->buf_off, ptr, realsize);
|
memcpy(s->orig_buf + s->buf_off, ptr, realsize);
|
||||||
@@ -258,43 +210,27 @@ static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ((s->buf_off >= acb->end)) {
|
if ((s->buf_off >= acb->end)) {
|
||||||
size_t request_length = acb->bytes;
|
|
||||||
|
|
||||||
qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
|
qemu_iovec_from_buf(acb->qiov, 0, s->orig_buf + acb->start,
|
||||||
acb->end - acb->start);
|
acb->end - acb->start);
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
if (acb->end - acb->start < request_length) {
|
qemu_aio_unref(acb);
|
||||||
size_t offset = acb->end - acb->start;
|
|
||||||
qemu_iovec_memset(acb->qiov, offset, 0,
|
|
||||||
request_length - offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
acb->ret = 0;
|
|
||||||
s->acb[i] = NULL;
|
s->acb[i] = NULL;
|
||||||
qemu_mutex_unlock(&s->s->mutex);
|
|
||||||
aio_co_wake(acb->co);
|
|
||||||
qemu_mutex_lock(&s->s->mutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
read_end:
|
return realsize;
|
||||||
/* curl will error out if we do not return this value */
|
|
||||||
return size * nmemb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with s->mutex held. */
|
static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len,
|
||||||
static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len,
|
|
||||||
CURLAIOCB *acb)
|
CURLAIOCB *acb)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
uint64_t end = start + len;
|
size_t end = start + len;
|
||||||
uint64_t clamped_end = MIN(end, s->len);
|
|
||||||
uint64_t clamped_len = clamped_end - start;
|
|
||||||
|
|
||||||
for (i=0; i<CURL_NUM_STATES; i++) {
|
for (i=0; i<CURL_NUM_STATES; i++) {
|
||||||
CURLState *state = &s->states[i];
|
CURLState *state = &s->states[i];
|
||||||
uint64_t buf_end = (state->buf_start + state->buf_off);
|
size_t buf_end = (state->buf_start + state->buf_off);
|
||||||
uint64_t buf_fend = (state->buf_start + state->buf_len);
|
size_t buf_fend = (state->buf_start + state->buf_len);
|
||||||
|
|
||||||
if (!state->orig_buf)
|
if (!state->orig_buf)
|
||||||
continue;
|
continue;
|
||||||
@@ -304,44 +240,41 @@ static bool curl_find_buf(BDRVCURLState *s, uint64_t start, uint64_t len,
|
|||||||
// Does the existing buffer cover our section?
|
// Does the existing buffer cover our section?
|
||||||
if ((start >= state->buf_start) &&
|
if ((start >= state->buf_start) &&
|
||||||
(start <= buf_end) &&
|
(start <= buf_end) &&
|
||||||
(clamped_end >= state->buf_start) &&
|
(end >= state->buf_start) &&
|
||||||
(clamped_end <= buf_end))
|
(end <= buf_end))
|
||||||
{
|
{
|
||||||
char *buf = state->orig_buf + (start - state->buf_start);
|
char *buf = state->orig_buf + (start - state->buf_start);
|
||||||
|
|
||||||
qemu_iovec_from_buf(acb->qiov, 0, buf, clamped_len);
|
qemu_iovec_from_buf(acb->qiov, 0, buf, len);
|
||||||
if (clamped_len < len) {
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
qemu_iovec_memset(acb->qiov, clamped_len, 0, len - clamped_len);
|
|
||||||
}
|
return FIND_RET_OK;
|
||||||
acb->ret = 0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for unfinished chunks
|
// Wait for unfinished chunks
|
||||||
if (state->in_use &&
|
if (state->in_use &&
|
||||||
(start >= state->buf_start) &&
|
(start >= state->buf_start) &&
|
||||||
(start <= buf_fend) &&
|
(start <= buf_fend) &&
|
||||||
(clamped_end >= state->buf_start) &&
|
(end >= state->buf_start) &&
|
||||||
(clamped_end <= buf_fend))
|
(end <= buf_fend))
|
||||||
{
|
{
|
||||||
int j;
|
int j;
|
||||||
|
|
||||||
acb->start = start - state->buf_start;
|
acb->start = start - state->buf_start;
|
||||||
acb->end = acb->start + clamped_len;
|
acb->end = acb->start + len;
|
||||||
|
|
||||||
for (j=0; j<CURL_NUM_ACB; j++) {
|
for (j=0; j<CURL_NUM_ACB; j++) {
|
||||||
if (!state->acb[j]) {
|
if (!state->acb[j]) {
|
||||||
state->acb[j] = acb;
|
state->acb[j] = acb;
|
||||||
return true;
|
return FIND_RET_WAIT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return FIND_RET_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with s->mutex held. */
|
|
||||||
static void curl_multi_check_completion(BDRVCURLState *s)
|
static void curl_multi_check_completion(BDRVCURLState *s)
|
||||||
{
|
{
|
||||||
int msgs_in_queue;
|
int msgs_in_queue;
|
||||||
@@ -364,18 +297,6 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
|||||||
/* ACBs for successful messages get completed in curl_read_cb */
|
/* ACBs for successful messages get completed in curl_read_cb */
|
||||||
if (msg->data.result != CURLE_OK) {
|
if (msg->data.result != CURLE_OK) {
|
||||||
int i;
|
int i;
|
||||||
static int errcount = 100;
|
|
||||||
|
|
||||||
/* Don't lose the original error message from curl, since
|
|
||||||
* it contains extra data.
|
|
||||||
*/
|
|
||||||
if (errcount > 0) {
|
|
||||||
error_report("curl: %s", state->errmsg);
|
|
||||||
if (--errcount == 0) {
|
|
||||||
error_report("curl: further errors suppressed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < CURL_NUM_ACB; i++) {
|
for (i = 0; i < CURL_NUM_ACB; i++) {
|
||||||
CURLAIOCB *acb = state->acb[i];
|
CURLAIOCB *acb = state->acb[i];
|
||||||
|
|
||||||
@@ -383,11 +304,9 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
acb->ret = -EIO;
|
acb->common.cb(acb->common.opaque, -EIO);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
state->acb[i] = NULL;
|
state->acb[i] = NULL;
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
aio_co_wake(acb->co);
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,10 +316,9 @@ static void curl_multi_check_completion(BDRVCURLState *s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with s->mutex held. */
|
static void curl_multi_do(void *arg)
|
||||||
static void curl_multi_do_locked(CURLState *s)
|
|
||||||
{
|
{
|
||||||
CURLSocket *socket, *next_socket;
|
CURLState *s = (CURLState *)arg;
|
||||||
int running;
|
int running;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
@@ -408,32 +326,18 @@ static void curl_multi_do_locked(CURLState *s)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Need to use _SAFE because curl_multi_socket_action() may trigger
|
|
||||||
* curl_sock_cb() which might modify this list */
|
|
||||||
QLIST_FOREACH_SAFE(socket, &s->sockets, next, next_socket) {
|
|
||||||
do {
|
do {
|
||||||
r = curl_multi_socket_action(s->s->multi, socket->fd, 0, &running);
|
r = curl_multi_socket_action(s->s->multi, s->sock_fd, 0, &running);
|
||||||
} while (r == CURLM_CALL_MULTI_PERFORM);
|
} while(r == CURLM_CALL_MULTI_PERFORM);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void curl_multi_do(void *arg)
|
|
||||||
{
|
|
||||||
CURLState *s = (CURLState *)arg;
|
|
||||||
|
|
||||||
qemu_mutex_lock(&s->s->mutex);
|
|
||||||
curl_multi_do_locked(s);
|
|
||||||
qemu_mutex_unlock(&s->s->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_multi_read(void *arg)
|
static void curl_multi_read(void *arg)
|
||||||
{
|
{
|
||||||
CURLState *s = (CURLState *)arg;
|
CURLState *s = (CURLState *)arg;
|
||||||
|
|
||||||
qemu_mutex_lock(&s->s->mutex);
|
curl_multi_do(arg);
|
||||||
curl_multi_do_locked(s);
|
|
||||||
curl_multi_check_completion(s->s);
|
curl_multi_check_completion(s->s);
|
||||||
qemu_mutex_unlock(&s->s->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_multi_timeout_do(void *arg)
|
static void curl_multi_timeout_do(void *arg)
|
||||||
@@ -446,38 +350,40 @@ static void curl_multi_timeout_do(void *arg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
|
||||||
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||||
|
|
||||||
curl_multi_check_completion(s);
|
curl_multi_check_completion(s);
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
#else
|
#else
|
||||||
abort();
|
abort();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with s->mutex held. */
|
static CURLState *curl_init_state(BlockDriverState *bs, BDRVCURLState *s)
|
||||||
static CURLState *curl_find_state(BDRVCURLState *s)
|
|
||||||
{
|
{
|
||||||
CURLState *state = NULL;
|
CURLState *state = NULL;
|
||||||
int i;
|
int i, j;
|
||||||
|
|
||||||
|
do {
|
||||||
|
for (i=0; i<CURL_NUM_STATES; i++) {
|
||||||
|
for (j=0; j<CURL_NUM_ACB; j++)
|
||||||
|
if (s->states[i].acb[j])
|
||||||
|
continue;
|
||||||
|
if (s->states[i].in_use)
|
||||||
|
continue;
|
||||||
|
|
||||||
for (i = 0; i < CURL_NUM_STATES; i++) {
|
|
||||||
if (!s->states[i].in_use) {
|
|
||||||
state = &s->states[i];
|
state = &s->states[i];
|
||||||
state->in_use = 1;
|
state->in_use = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!state) {
|
||||||
|
aio_poll(bdrv_get_aio_context(bs), true);
|
||||||
}
|
}
|
||||||
return state;
|
} while(!state);
|
||||||
}
|
|
||||||
|
|
||||||
static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
|
||||||
{
|
|
||||||
if (!state->curl) {
|
if (!state->curl) {
|
||||||
state->curl = curl_easy_init();
|
state->curl = curl_easy_init();
|
||||||
if (!state->curl) {
|
if (!state->curl) {
|
||||||
return -EIO;
|
return NULL;
|
||||||
}
|
}
|
||||||
curl_easy_setopt(state->curl, CURLOPT_URL, s->url);
|
curl_easy_setopt(state->curl, CURLOPT_URL, s->url);
|
||||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
|
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
|
||||||
@@ -496,21 +402,6 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
|||||||
curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg);
|
curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg);
|
||||||
curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1);
|
curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1);
|
||||||
|
|
||||||
if (s->username) {
|
|
||||||
curl_easy_setopt(state->curl, CURLOPT_USERNAME, s->username);
|
|
||||||
}
|
|
||||||
if (s->password) {
|
|
||||||
curl_easy_setopt(state->curl, CURLOPT_PASSWORD, s->password);
|
|
||||||
}
|
|
||||||
if (s->proxyusername) {
|
|
||||||
curl_easy_setopt(state->curl,
|
|
||||||
CURLOPT_PROXYUSERNAME, s->proxyusername);
|
|
||||||
}
|
|
||||||
if (s->proxypassword) {
|
|
||||||
curl_easy_setopt(state->curl,
|
|
||||||
CURLOPT_PROXYPASSWORD, s->proxypassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Restrict supported protocols to avoid security issues in the more
|
/* Restrict supported protocols to avoid security issues in the more
|
||||||
* obscure protocols. For example, do not allow POP3/SMTP/IMAP see
|
* obscure protocols. For example, do not allow POP3/SMTP/IMAP see
|
||||||
* CVE-2013-0249.
|
* CVE-2013-0249.
|
||||||
@@ -527,46 +418,22 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QLIST_INIT(&state->sockets);
|
|
||||||
state->s = s;
|
state->s = s;
|
||||||
|
|
||||||
return 0;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called with s->mutex held. */
|
|
||||||
static void curl_clean_state(CURLState *s)
|
static void curl_clean_state(CURLState *s)
|
||||||
{
|
{
|
||||||
CURLAIOCB *next;
|
|
||||||
int j;
|
|
||||||
for (j = 0; j < CURL_NUM_ACB; j++) {
|
|
||||||
assert(!s->acb[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->s->multi)
|
if (s->s->multi)
|
||||||
curl_multi_remove_handle(s->s->multi, s->curl);
|
curl_multi_remove_handle(s->s->multi, s->curl);
|
||||||
|
|
||||||
while (!QLIST_EMPTY(&s->sockets)) {
|
|
||||||
CURLSocket *socket = QLIST_FIRST(&s->sockets);
|
|
||||||
|
|
||||||
QLIST_REMOVE(socket, next);
|
|
||||||
g_free(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
s->in_use = 0;
|
s->in_use = 0;
|
||||||
|
|
||||||
next = QSIMPLEQ_FIRST(&s->s->free_state_waitq);
|
|
||||||
if (next) {
|
|
||||||
QSIMPLEQ_REMOVE_HEAD(&s->s->free_state_waitq, next);
|
|
||||||
qemu_mutex_unlock(&s->s->mutex);
|
|
||||||
aio_co_wake(next->co);
|
|
||||||
qemu_mutex_lock(&s->s->mutex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_parse_filename(const char *filename, QDict *options,
|
static void curl_parse_filename(const char *filename, QDict *options,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
qdict_put_str(options, CURL_BLOCK_OPT_URL, filename);
|
qdict_put(options, CURL_BLOCK_OPT_URL, qstring_from_str(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_detach_aio_context(BlockDriverState *bs)
|
static void curl_detach_aio_context(BlockDriverState *bs)
|
||||||
@@ -574,7 +441,6 @@ static void curl_detach_aio_context(BlockDriverState *bs)
|
|||||||
BDRVCURLState *s = bs->opaque;
|
BDRVCURLState *s = bs->opaque;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
|
||||||
for (i = 0; i < CURL_NUM_STATES; i++) {
|
for (i = 0; i < CURL_NUM_STATES; i++) {
|
||||||
if (s->states[i].in_use) {
|
if (s->states[i].in_use) {
|
||||||
curl_clean_state(&s->states[i]);
|
curl_clean_state(&s->states[i]);
|
||||||
@@ -590,7 +456,6 @@ static void curl_detach_aio_context(BlockDriverState *bs)
|
|||||||
curl_multi_cleanup(s->multi);
|
curl_multi_cleanup(s->multi);
|
||||||
s->multi = NULL;
|
s->multi = NULL;
|
||||||
}
|
}
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
|
|
||||||
timer_del(&s->timer);
|
timer_del(&s->timer);
|
||||||
}
|
}
|
||||||
@@ -643,36 +508,10 @@ static QemuOptsList runtime_opts = {
|
|||||||
.type = QEMU_OPT_STRING,
|
.type = QEMU_OPT_STRING,
|
||||||
.help = "Pass the cookie or list of cookies with each request"
|
.help = "Pass the cookie or list of cookies with each request"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.name = CURL_BLOCK_OPT_COOKIE_SECRET,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of secret used as cookie passed with each request"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = CURL_BLOCK_OPT_USERNAME,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Username for HTTP auth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = CURL_BLOCK_OPT_PASSWORD_SECRET,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of secret used as password for HTTP auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = CURL_BLOCK_OPT_PROXY_USERNAME,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Username for HTTP proxy auth"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET,
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of secret used as password for HTTP proxy auth",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@@ -682,10 +521,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
const char *file;
|
const char *file;
|
||||||
const char *cookie;
|
const char *cookie;
|
||||||
const char *cookie_secret;
|
|
||||||
double d;
|
double d;
|
||||||
const char *secretid;
|
|
||||||
const char *protocol_delimiter;
|
|
||||||
|
|
||||||
static int inited = 0;
|
static int inited = 0;
|
||||||
|
|
||||||
@@ -694,7 +530,6 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
return -EROFS;
|
return -EROFS;
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_mutex_init(&s->mutex);
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
@@ -720,22 +555,7 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY, true);
|
s->sslverify = qemu_opt_get_bool(opts, CURL_BLOCK_OPT_SSLVERIFY, true);
|
||||||
|
|
||||||
cookie = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE);
|
cookie = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE);
|
||||||
cookie_secret = qemu_opt_get(opts, CURL_BLOCK_OPT_COOKIE_SECRET);
|
|
||||||
|
|
||||||
if (cookie && cookie_secret) {
|
|
||||||
error_setg(errp,
|
|
||||||
"curl driver cannot handle both cookie and cookie secret");
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cookie_secret) {
|
|
||||||
s->cookie = qcrypto_secret_lookup_as_utf8(cookie_secret, errp);
|
|
||||||
if (!s->cookie) {
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
s->cookie = g_strdup(cookie);
|
s->cookie = g_strdup(cookie);
|
||||||
}
|
|
||||||
|
|
||||||
file = qemu_opt_get(opts, CURL_BLOCK_OPT_URL);
|
file = qemu_opt_get(opts, CURL_BLOCK_OPT_URL);
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
@@ -743,57 +563,20 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
goto out_noclean;
|
goto out_noclean;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strstart(file, bs->drv->protocol_name, &protocol_delimiter) ||
|
|
||||||
!strstart(protocol_delimiter, "://", NULL))
|
|
||||||
{
|
|
||||||
error_setg(errp, "%s curl driver cannot handle the URL '%s' (does not "
|
|
||||||
"start with '%s://')", bs->drv->protocol_name, file,
|
|
||||||
bs->drv->protocol_name);
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->username = g_strdup(qemu_opt_get(opts, CURL_BLOCK_OPT_USERNAME));
|
|
||||||
secretid = qemu_opt_get(opts, CURL_BLOCK_OPT_PASSWORD_SECRET);
|
|
||||||
|
|
||||||
if (secretid) {
|
|
||||||
s->password = qcrypto_secret_lookup_as_utf8(secretid, errp);
|
|
||||||
if (!s->password) {
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s->proxyusername = g_strdup(
|
|
||||||
qemu_opt_get(opts, CURL_BLOCK_OPT_PROXY_USERNAME));
|
|
||||||
secretid = qemu_opt_get(opts, CURL_BLOCK_OPT_PROXY_PASSWORD_SECRET);
|
|
||||||
if (secretid) {
|
|
||||||
s->proxypassword = qcrypto_secret_lookup_as_utf8(secretid, errp);
|
|
||||||
if (!s->proxypassword) {
|
|
||||||
goto out_noclean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inited) {
|
if (!inited) {
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
inited = 1;
|
inited = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF("CURL: Opening %s\n", file);
|
DPRINTF("CURL: Opening %s\n", file);
|
||||||
QSIMPLEQ_INIT(&s->free_state_waitq);
|
|
||||||
s->aio_context = bdrv_get_aio_context(bs);
|
s->aio_context = bdrv_get_aio_context(bs);
|
||||||
s->url = g_strdup(file);
|
s->url = g_strdup(file);
|
||||||
qemu_mutex_lock(&s->mutex);
|
state = curl_init_state(bs, s);
|
||||||
state = curl_find_state(s);
|
if (!state)
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
if (!state) {
|
|
||||||
goto out_noclean;
|
goto out_noclean;
|
||||||
}
|
|
||||||
|
|
||||||
// Get file size
|
// Get file size
|
||||||
|
|
||||||
if (curl_init_state(s, state) < 0) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->accept_range = false;
|
s->accept_range = false;
|
||||||
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
||||||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION,
|
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION,
|
||||||
@@ -801,28 +584,11 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s);
|
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s);
|
||||||
if (curl_easy_perform(state->curl))
|
if (curl_easy_perform(state->curl))
|
||||||
goto out;
|
goto out;
|
||||||
if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) {
|
curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d);
|
||||||
|
if (d)
|
||||||
|
s->len = (size_t)d;
|
||||||
|
else if(!s->len)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
/* Prior CURL 7.19.4 return value of 0 could mean that the file size is not
|
|
||||||
* know or the size is zero. From 7.19.4 CURL returns -1 if size is not
|
|
||||||
* known and zero if it is realy zero-length file. */
|
|
||||||
#if LIBCURL_VERSION_NUM >= 0x071304
|
|
||||||
if (d < 0) {
|
|
||||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
|
||||||
"Server didn't report file size.");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (d <= 0) {
|
|
||||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
|
||||||
"Unknown file size or zero-length file.");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
s->len = d;
|
|
||||||
|
|
||||||
if ((!strncasecmp(s->url, "http://", strlen("http://"))
|
if ((!strncasecmp(s->url, "http://", strlen("http://"))
|
||||||
|| !strncasecmp(s->url, "https://", strlen("https://")))
|
|| !strncasecmp(s->url, "https://", strlen("https://")))
|
||||||
&& !s->accept_range) {
|
&& !s->accept_range) {
|
||||||
@@ -830,11 +596,9 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
"Server does not support 'range' (byte ranges).");
|
"Server does not support 'range' (byte ranges).");
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
DPRINTF("CURL: Size = %" PRIu64 "\n", s->len);
|
DPRINTF("CURL: Size = %zd\n", s->len);
|
||||||
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
|
||||||
curl_clean_state(state);
|
curl_clean_state(state);
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
curl_easy_cleanup(state->curl);
|
curl_easy_cleanup(state->curl);
|
||||||
state->curl = NULL;
|
state->curl = NULL;
|
||||||
|
|
||||||
@@ -848,95 +612,94 @@ out:
|
|||||||
curl_easy_cleanup(state->curl);
|
curl_easy_cleanup(state->curl);
|
||||||
state->curl = NULL;
|
state->curl = NULL;
|
||||||
out_noclean:
|
out_noclean:
|
||||||
qemu_mutex_destroy(&s->mutex);
|
|
||||||
g_free(s->cookie);
|
g_free(s->cookie);
|
||||||
g_free(s->url);
|
g_free(s->url);
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb)
|
static const AIOCBInfo curl_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(CURLAIOCB),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static void curl_readv_bh_cb(void *p)
|
||||||
{
|
{
|
||||||
CURLState *state;
|
CURLState *state;
|
||||||
int running;
|
int running;
|
||||||
|
|
||||||
BDRVCURLState *s = bs->opaque;
|
CURLAIOCB *acb = p;
|
||||||
|
BDRVCURLState *s = acb->common.bs->opaque;
|
||||||
|
|
||||||
uint64_t start = acb->offset;
|
qemu_bh_delete(acb->bh);
|
||||||
uint64_t end;
|
acb->bh = NULL;
|
||||||
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
size_t start = acb->sector_num * SECTOR_SIZE;
|
||||||
|
size_t end;
|
||||||
|
|
||||||
// In case we have the requested data already (e.g. read-ahead),
|
// In case we have the requested data already (e.g. read-ahead),
|
||||||
// we can just call the callback and be done.
|
// we can just call the callback and be done.
|
||||||
if (curl_find_buf(s, start, acb->bytes, acb)) {
|
switch (curl_find_buf(s, start, acb->nb_sectors * SECTOR_SIZE, acb)) {
|
||||||
goto out;
|
case FIND_RET_OK:
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
// fall through
|
||||||
|
case FIND_RET_WAIT:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cache found, so let's start a new request
|
// No cache found, so let's start a new request
|
||||||
for (;;) {
|
state = curl_init_state(acb->common.bs, s);
|
||||||
state = curl_find_state(s);
|
if (!state) {
|
||||||
if (state) {
|
acb->common.cb(acb->common.opaque, -EIO);
|
||||||
break;
|
qemu_aio_unref(acb);
|
||||||
}
|
return;
|
||||||
QSIMPLEQ_INSERT_TAIL(&s->free_state_waitq, acb, next);
|
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
qemu_mutex_lock(&s->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curl_init_state(s, state) < 0) {
|
|
||||||
curl_clean_state(state);
|
|
||||||
acb->ret = -EIO;
|
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acb->start = 0;
|
acb->start = 0;
|
||||||
acb->end = MIN(acb->bytes, s->len - start);
|
acb->end = (acb->nb_sectors * SECTOR_SIZE);
|
||||||
|
|
||||||
state->buf_off = 0;
|
state->buf_off = 0;
|
||||||
g_free(state->orig_buf);
|
g_free(state->orig_buf);
|
||||||
state->buf_start = start;
|
state->buf_start = start;
|
||||||
state->buf_len = MIN(acb->end + s->readahead_size, s->len - start);
|
state->buf_len = acb->end + s->readahead_size;
|
||||||
end = start + state->buf_len - 1;
|
end = MIN(start + state->buf_len, s->len) - 1;
|
||||||
state->orig_buf = g_try_malloc(state->buf_len);
|
state->orig_buf = g_try_malloc(state->buf_len);
|
||||||
if (state->buf_len && state->orig_buf == NULL) {
|
if (state->buf_len && state->orig_buf == NULL) {
|
||||||
curl_clean_state(state);
|
curl_clean_state(state);
|
||||||
acb->ret = -ENOMEM;
|
acb->common.cb(acb->common.opaque, -ENOMEM);
|
||||||
goto out;
|
qemu_aio_unref(acb);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
state->acb[0] = acb;
|
state->acb[0] = acb;
|
||||||
|
|
||||||
snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
|
snprintf(state->range, 127, "%zd-%zd", start, end);
|
||||||
DPRINTF("CURL (AIO): Reading %" PRIu64 " at %" PRIu64 " (%s)\n",
|
DPRINTF("CURL (AIO): Reading %d at %zd (%s)\n",
|
||||||
acb->bytes, start, state->range);
|
(acb->nb_sectors * SECTOR_SIZE), start, state->range);
|
||||||
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range);
|
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range);
|
||||||
|
|
||||||
curl_multi_add_handle(s->multi, state->curl);
|
curl_multi_add_handle(s->multi, state->curl);
|
||||||
|
|
||||||
/* Tell curl it needs to kick things off */
|
/* Tell curl it needs to kick things off */
|
||||||
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
curl_multi_socket_action(s->multi, CURL_SOCKET_TIMEOUT, 0, &running);
|
||||||
|
|
||||||
out:
|
|
||||||
qemu_mutex_unlock(&s->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn curl_co_preadv(BlockDriverState *bs,
|
static BlockAIOCB *curl_aio_readv(BlockDriverState *bs,
|
||||||
uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
{
|
{
|
||||||
CURLAIOCB acb = {
|
CURLAIOCB *acb;
|
||||||
.co = qemu_coroutine_self(),
|
|
||||||
.ret = -EINPROGRESS,
|
|
||||||
.qiov = qiov,
|
|
||||||
.offset = offset,
|
|
||||||
.bytes = bytes
|
|
||||||
};
|
|
||||||
|
|
||||||
curl_setup_preadv(bs, &acb);
|
acb = qemu_aio_get(&curl_aiocb_info, bs, cb, opaque);
|
||||||
while (acb.ret == -EINPROGRESS) {
|
|
||||||
qemu_coroutine_yield();
|
acb->qiov = qiov;
|
||||||
}
|
acb->sector_num = sector_num;
|
||||||
return acb.ret;
|
acb->nb_sectors = nb_sectors;
|
||||||
|
|
||||||
|
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), curl_readv_bh_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
return &acb->common;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void curl_close(BlockDriverState *bs)
|
static void curl_close(BlockDriverState *bs)
|
||||||
@@ -945,7 +708,6 @@ static void curl_close(BlockDriverState *bs)
|
|||||||
|
|
||||||
DPRINTF("CURL: Close\n");
|
DPRINTF("CURL: Close\n");
|
||||||
curl_detach_aio_context(bs);
|
curl_detach_aio_context(bs);
|
||||||
qemu_mutex_destroy(&s->mutex);
|
|
||||||
|
|
||||||
g_free(s->cookie);
|
g_free(s->cookie);
|
||||||
g_free(s->url);
|
g_free(s->url);
|
||||||
@@ -967,7 +729,7 @@ static BlockDriver bdrv_http = {
|
|||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|
||||||
.bdrv_co_preadv = curl_co_preadv,
|
.bdrv_aio_readv = curl_aio_readv,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||||
@@ -983,7 +745,7 @@ static BlockDriver bdrv_https = {
|
|||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|
||||||
.bdrv_co_preadv = curl_co_preadv,
|
.bdrv_aio_readv = curl_aio_readv,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||||
@@ -999,7 +761,7 @@ static BlockDriver bdrv_ftp = {
|
|||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|
||||||
.bdrv_co_preadv = curl_co_preadv,
|
.bdrv_aio_readv = curl_aio_readv,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||||
@@ -1015,7 +777,23 @@ static BlockDriver bdrv_ftps = {
|
|||||||
.bdrv_close = curl_close,
|
.bdrv_close = curl_close,
|
||||||
.bdrv_getlength = curl_getlength,
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|
||||||
.bdrv_co_preadv = curl_co_preadv,
|
.bdrv_aio_readv = curl_aio_readv,
|
||||||
|
|
||||||
|
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||||
|
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||||
|
};
|
||||||
|
|
||||||
|
static BlockDriver bdrv_tftp = {
|
||||||
|
.format_name = "tftp",
|
||||||
|
.protocol_name = "tftp",
|
||||||
|
|
||||||
|
.instance_size = sizeof(BDRVCURLState),
|
||||||
|
.bdrv_parse_filename = curl_parse_filename,
|
||||||
|
.bdrv_file_open = curl_open,
|
||||||
|
.bdrv_close = curl_close,
|
||||||
|
.bdrv_getlength = curl_getlength,
|
||||||
|
|
||||||
|
.bdrv_aio_readv = curl_aio_readv,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = curl_detach_aio_context,
|
.bdrv_detach_aio_context = curl_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = curl_attach_aio_context,
|
.bdrv_attach_aio_context = curl_attach_aio_context,
|
||||||
@@ -1027,6 +805,7 @@ static void curl_block_init(void)
|
|||||||
bdrv_register(&bdrv_https);
|
bdrv_register(&bdrv_https);
|
||||||
bdrv_register(&bdrv_ftp);
|
bdrv_register(&bdrv_ftp);
|
||||||
bdrv_register(&bdrv_ftps);
|
bdrv_register(&bdrv_ftps);
|
||||||
|
bdrv_register(&bdrv_tftp);
|
||||||
}
|
}
|
||||||
|
|
||||||
block_init(curl_block_init);
|
block_init(curl_block_init);
|
||||||
|
|||||||
584
block/dictzip.c
Normal file
584
block/dictzip.c
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
/*
|
||||||
|
* DictZip Block driver for dictzip enabled gzip files
|
||||||
|
*
|
||||||
|
* Use the "dictzip" tool from the "dictd" package to create gzip files that
|
||||||
|
* contain the extra DictZip headers.
|
||||||
|
*
|
||||||
|
* dictzip(1) is a compression program which creates compressed files in the
|
||||||
|
* gzip format (see RFC 1952). However, unlike gzip(1), dictzip(1) compresses
|
||||||
|
* the file in pieces and stores an index to the pieces in the gzip header.
|
||||||
|
* This allows random access to the file at the granularity of the compressed
|
||||||
|
* pieces (currently about 64kB) while maintaining good compression ratios
|
||||||
|
* (within 5% of the expected ratio for dictionary data).
|
||||||
|
* dictd(8) uses files stored in this format.
|
||||||
|
*
|
||||||
|
* For details on DictZip see http://dict.org/.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009 Alexander Graf <agraf@suse.de>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "qemu-common.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
// #define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define dprintf(fmt, ...) do { printf("dzip: " fmt, ## __VA_ARGS__); } while (0)
|
||||||
|
#else
|
||||||
|
#define dprintf(fmt, ...) do { } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SECTOR_SIZE 512
|
||||||
|
#define Z_STREAM_COUNT 4
|
||||||
|
#define CACHE_COUNT 20
|
||||||
|
|
||||||
|
/* magic values */
|
||||||
|
|
||||||
|
#define GZ_MAGIC1 0x1f
|
||||||
|
#define GZ_MAGIC2 0x8b
|
||||||
|
#define DZ_MAGIC1 'R'
|
||||||
|
#define DZ_MAGIC2 'A'
|
||||||
|
|
||||||
|
#define GZ_FEXTRA 0x04 /* Optional field (random access index) */
|
||||||
|
#define GZ_FNAME 0x08 /* Original name */
|
||||||
|
#define GZ_COMMENT 0x10 /* Zero-terminated, human-readable comment */
|
||||||
|
#define GZ_FHCRC 0x02 /* Header CRC16 */
|
||||||
|
|
||||||
|
/* offsets */
|
||||||
|
|
||||||
|
#define GZ_ID 0 /* GZ_MAGIC (16bit) */
|
||||||
|
#define GZ_FLG 3 /* FLaGs (see above) */
|
||||||
|
#define GZ_XLEN 10 /* eXtra LENgth (16bit) */
|
||||||
|
#define GZ_SI 12 /* Subfield ID (16bit) */
|
||||||
|
#define GZ_VERSION 16 /* Version for subfield format */
|
||||||
|
#define GZ_CHUNKSIZE 18 /* Chunk size (16bit) */
|
||||||
|
#define GZ_CHUNKCNT 20 /* Number of chunks (16bit) */
|
||||||
|
#define GZ_RNDDATA 22 /* Random access data (16bit) */
|
||||||
|
|
||||||
|
#define GZ_99_CHUNKSIZE 18 /* Chunk size (32bit) */
|
||||||
|
#define GZ_99_CHUNKCNT 22 /* Number of chunks (32bit) */
|
||||||
|
#define GZ_99_FILESIZE 26 /* Size of unpacked file (64bit) */
|
||||||
|
#define GZ_99_RNDDATA 34 /* Random access data (32bit) */
|
||||||
|
|
||||||
|
struct BDRVDictZipState;
|
||||||
|
|
||||||
|
typedef struct DictZipAIOCB {
|
||||||
|
BlockAIOCB common;
|
||||||
|
struct BDRVDictZipState *s;
|
||||||
|
QEMUIOVector *qiov; /* QIOV of the original request */
|
||||||
|
QEMUIOVector *qiov_gz; /* QIOV of the gz subrequest */
|
||||||
|
QEMUBH *bh; /* BH for cache */
|
||||||
|
z_stream *zStream; /* stream to use for decoding */
|
||||||
|
int zStream_id; /* stream id of the above pointer */
|
||||||
|
size_t start; /* offset into the uncompressed file */
|
||||||
|
size_t len; /* uncompressed bytes to read */
|
||||||
|
uint8_t *gzipped; /* the gzipped data */
|
||||||
|
uint8_t *buf; /* cached result */
|
||||||
|
size_t gz_len; /* amount of gzip data */
|
||||||
|
size_t gz_start; /* uncompressed starting point of gzip data */
|
||||||
|
uint64_t offset; /* offset for "start" into the uncompressed chunk */
|
||||||
|
int chunks_len; /* amount of uncompressed data in all gzip data */
|
||||||
|
} DictZipAIOCB;
|
||||||
|
|
||||||
|
typedef struct dict_cache {
|
||||||
|
size_t start;
|
||||||
|
size_t len;
|
||||||
|
uint8_t *buf;
|
||||||
|
} DictCache;
|
||||||
|
|
||||||
|
typedef struct BDRVDictZipState {
|
||||||
|
BlockDriverState *hd;
|
||||||
|
z_stream zStream[Z_STREAM_COUNT];
|
||||||
|
DictCache cache[CACHE_COUNT];
|
||||||
|
int cache_index;
|
||||||
|
uint8_t stream_in_use;
|
||||||
|
uint64_t chunk_len;
|
||||||
|
uint32_t chunk_cnt;
|
||||||
|
uint16_t *chunks;
|
||||||
|
uint32_t *chunks32;
|
||||||
|
uint64_t *offsets;
|
||||||
|
int64_t file_len;
|
||||||
|
} BDRVDictZipState;
|
||||||
|
|
||||||
|
static int start_zStream(z_stream *zStream)
|
||||||
|
{
|
||||||
|
zStream->zalloc = NULL;
|
||||||
|
zStream->zfree = NULL;
|
||||||
|
zStream->opaque = NULL;
|
||||||
|
zStream->next_in = 0;
|
||||||
|
zStream->avail_in = 0;
|
||||||
|
zStream->next_out = NULL;
|
||||||
|
zStream->avail_out = 0;
|
||||||
|
|
||||||
|
return inflateInit2( zStream, -15 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static QemuOptsList runtime_opts = {
|
||||||
|
.name = "dzip",
|
||||||
|
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||||
|
.desc = {
|
||||||
|
{
|
||||||
|
.name = "filename",
|
||||||
|
.type = QEMU_OPT_STRING,
|
||||||
|
.help = "URL to the dictzip file",
|
||||||
|
},
|
||||||
|
{ /* end of list */ }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int dictzip_open(BlockDriverState *bs, QDict *options, int flags, Error **errp)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
const char *err = "Unknown (read error?)";
|
||||||
|
uint8_t magic[2];
|
||||||
|
char buf[100];
|
||||||
|
uint8_t header_flags;
|
||||||
|
uint16_t chunk_len16;
|
||||||
|
uint16_t chunk_cnt16;
|
||||||
|
uint32_t chunk_len32;
|
||||||
|
uint16_t header_ver;
|
||||||
|
uint16_t tmp_short;
|
||||||
|
uint64_t offset;
|
||||||
|
int chunks_len;
|
||||||
|
int headerLength = GZ_XLEN - 1;
|
||||||
|
int rnd_offs;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
QemuOpts *opts;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
const char *filename;
|
||||||
|
|
||||||
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
|
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||||
|
if (local_err != NULL) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = qemu_opt_get(opts, "filename");
|
||||||
|
|
||||||
|
if (!strncmp(filename, "dzip://", 7))
|
||||||
|
filename += 7;
|
||||||
|
else if (!strncmp(filename, "dzip:", 5))
|
||||||
|
filename += 5;
|
||||||
|
|
||||||
|
ret = bdrv_open(&s->hd, filename, NULL, NULL, flags | BDRV_O_PROTOCOL, NULL, &local_err);
|
||||||
|
if (ret < 0) {
|
||||||
|
error_propagate(errp, local_err);
|
||||||
|
qemu_opts_del(opts);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialize zlib streams */
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
if (start_zStream( &s->zStream[i] ) != Z_OK) {
|
||||||
|
err = s->zStream[i].msg;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gzip header */
|
||||||
|
if (bdrv_pread(s->hd, GZ_ID, &magic, sizeof(magic)) != sizeof(magic))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (!((magic[0] == GZ_MAGIC1) && (magic[1] == GZ_MAGIC2))) {
|
||||||
|
err = "No gzip file";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dzip header */
|
||||||
|
if (bdrv_pread(s->hd, GZ_FLG, &header_flags, 1) != 1)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (!(header_flags & GZ_FEXTRA)) {
|
||||||
|
err = "Not a dictzip file (wrong flags)";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extra length */
|
||||||
|
if (bdrv_pread(s->hd, GZ_XLEN, &tmp_short, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
headerLength += le16_to_cpu(tmp_short) + 2;
|
||||||
|
|
||||||
|
/* DictZip magic */
|
||||||
|
if (bdrv_pread(s->hd, GZ_SI, &magic, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (magic[0] != DZ_MAGIC1 || magic[1] != DZ_MAGIC2) {
|
||||||
|
err = "Not a dictzip file (missing extra magic)";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DictZip version */
|
||||||
|
if (bdrv_pread(s->hd, GZ_VERSION, &header_ver, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
header_ver = le16_to_cpu(header_ver);
|
||||||
|
|
||||||
|
switch (header_ver) {
|
||||||
|
case 1: /* Normal DictZip */
|
||||||
|
/* number of chunks */
|
||||||
|
if (bdrv_pread(s->hd, GZ_CHUNKSIZE, &chunk_len16, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_len = le16_to_cpu(chunk_len16);
|
||||||
|
|
||||||
|
/* chunk count */
|
||||||
|
if (bdrv_pread(s->hd, GZ_CHUNKCNT, &chunk_cnt16, 2) != 2)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_cnt = le16_to_cpu(chunk_cnt16);
|
||||||
|
chunks_len = sizeof(short) * s->chunk_cnt;
|
||||||
|
rnd_offs = GZ_RNDDATA;
|
||||||
|
break;
|
||||||
|
case 99: /* Special Alex pigz version */
|
||||||
|
/* number of chunks */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_CHUNKSIZE, &chunk_len32, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("chunk len [%#x] = %d\n", GZ_99_CHUNKSIZE, chunk_len32);
|
||||||
|
s->chunk_len = le32_to_cpu(chunk_len32);
|
||||||
|
|
||||||
|
/* chunk count */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_CHUNKCNT, &s->chunk_cnt, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->chunk_cnt = le32_to_cpu(s->chunk_cnt);
|
||||||
|
|
||||||
|
dprintf("chunk len | count = %"PRId64" | %d\n", s->chunk_len, s->chunk_cnt);
|
||||||
|
|
||||||
|
/* file size */
|
||||||
|
if (bdrv_pread(s->hd, GZ_99_FILESIZE, &s->file_len, 8) != 8)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->file_len = le64_to_cpu(s->file_len);
|
||||||
|
chunks_len = sizeof(int) * s->chunk_cnt;
|
||||||
|
rnd_offs = GZ_99_RNDDATA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = "Invalid DictZip version";
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* random access data */
|
||||||
|
s->chunks = g_malloc(chunks_len);
|
||||||
|
if (header_ver == 99)
|
||||||
|
s->chunks32 = (uint32_t *)s->chunks;
|
||||||
|
|
||||||
|
if (bdrv_pread(s->hd, rnd_offs, s->chunks, chunks_len) != chunks_len)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
/* orig filename */
|
||||||
|
if (header_flags & GZ_FNAME) {
|
||||||
|
if (bdrv_pread(s->hd, headerLength + 1, buf, sizeof(buf)) != sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
headerLength += strlen(buf) + 1;
|
||||||
|
|
||||||
|
if (strlen(buf) == sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("filename: %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* comment field */
|
||||||
|
if (header_flags & GZ_COMMENT) {
|
||||||
|
if (bdrv_pread(s->hd, headerLength, buf, sizeof(buf)) != sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
headerLength += strlen(buf) + 1;
|
||||||
|
|
||||||
|
if (strlen(buf) == sizeof(buf))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
dprintf("comment: %s\n", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_flags & GZ_FHCRC)
|
||||||
|
headerLength += 2;
|
||||||
|
|
||||||
|
/* uncompressed file length*/
|
||||||
|
if (!s->file_len) {
|
||||||
|
uint32_t file_len;
|
||||||
|
|
||||||
|
if (bdrv_pread(s->hd, bdrv_getlength(s->hd) - 4, &file_len, 4) != 4)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
s->file_len = le32_to_cpu(file_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compute offsets */
|
||||||
|
s->offsets = g_malloc(sizeof( *s->offsets ) * s->chunk_cnt);
|
||||||
|
|
||||||
|
for (offset = headerLength + 1, i = 0; i < s->chunk_cnt; i++) {
|
||||||
|
s->offsets[i] = offset;
|
||||||
|
switch (header_ver) {
|
||||||
|
case 1:
|
||||||
|
offset += le16_to_cpu(s->chunks[i]);
|
||||||
|
break;
|
||||||
|
case 99:
|
||||||
|
offset += le32_to_cpu(s->chunks32[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dprintf("chunk %#"PRIx64" - %#"PRIx64" = offset %#"PRIx64" -> %#"PRIx64"\n", i * s->chunk_len, (i+1) * s->chunk_len, s->offsets[i], offset);
|
||||||
|
}
|
||||||
|
qemu_opts_del(opts);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
fprintf(stderr, "DictZip: Error opening file: %s\n", err);
|
||||||
|
bdrv_unref(s->hd);
|
||||||
|
if (s->chunks)
|
||||||
|
g_free(s->chunks);
|
||||||
|
qemu_opts_del(opts);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This callback gets invoked when we have the result in cache already */
|
||||||
|
static void dictzip_cache_cb(void *opaque)
|
||||||
|
{
|
||||||
|
DictZipAIOCB *acb = (DictZipAIOCB *)opaque;
|
||||||
|
|
||||||
|
qemu_iovec_from_buf(acb->qiov, 0, acb->buf, acb->len);
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
qemu_bh_delete(acb->bh);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This callback gets invoked by the underlying block reader when we have
|
||||||
|
* all compressed data. We uncompress in here. */
|
||||||
|
static void dictzip_read_cb(void *opaque, int ret)
|
||||||
|
{
|
||||||
|
DictZipAIOCB *acb = (DictZipAIOCB *)opaque;
|
||||||
|
struct BDRVDictZipState *s = acb->s;
|
||||||
|
uint8_t *buf;
|
||||||
|
DictCache *cache;
|
||||||
|
int r, i;
|
||||||
|
|
||||||
|
buf = g_malloc(acb->chunks_len);
|
||||||
|
|
||||||
|
/* try to find zlib stream for decoding */
|
||||||
|
do {
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
if (!(s->stream_in_use & (1 << i))) {
|
||||||
|
s->stream_in_use |= (1 << i);
|
||||||
|
acb->zStream_id = i;
|
||||||
|
acb->zStream = &s->zStream[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(!acb->zStream);
|
||||||
|
|
||||||
|
/* sure, we could handle more streams, but this callback should be single
|
||||||
|
threaded and when it's not, we really want to know! */
|
||||||
|
assert(i == 0);
|
||||||
|
|
||||||
|
/* uncompress the chunk */
|
||||||
|
acb->zStream->next_in = acb->gzipped;
|
||||||
|
acb->zStream->avail_in = acb->gz_len;
|
||||||
|
acb->zStream->next_out = buf;
|
||||||
|
acb->zStream->avail_out = acb->chunks_len;
|
||||||
|
|
||||||
|
r = inflate( acb->zStream, Z_PARTIAL_FLUSH );
|
||||||
|
if ( (r != Z_OK) && (r != Z_STREAM_END) )
|
||||||
|
fprintf(stderr, "Error inflating: [%d] %s\n", r, acb->zStream->msg);
|
||||||
|
|
||||||
|
if ( r == Z_STREAM_END )
|
||||||
|
inflateReset(acb->zStream);
|
||||||
|
|
||||||
|
dprintf("inflating [%d] left: %d | %d bytes\n", r, acb->zStream->avail_in, acb->zStream->avail_out);
|
||||||
|
s->stream_in_use &= ~(1 << acb->zStream_id);
|
||||||
|
|
||||||
|
/* nofity the caller */
|
||||||
|
qemu_iovec_from_buf(acb->qiov, 0, buf + acb->offset, acb->len);
|
||||||
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
|
|
||||||
|
/* fill the cache */
|
||||||
|
cache = &s->cache[s->cache_index];
|
||||||
|
s->cache_index++;
|
||||||
|
if (s->cache_index == CACHE_COUNT)
|
||||||
|
s->cache_index = 0;
|
||||||
|
|
||||||
|
cache->len = 0;
|
||||||
|
if (cache->buf)
|
||||||
|
g_free(cache->buf);
|
||||||
|
cache->start = acb->gz_start;
|
||||||
|
cache->buf = buf;
|
||||||
|
cache->len = acb->chunks_len;
|
||||||
|
|
||||||
|
/* free occupied ressources */
|
||||||
|
g_free(acb->qiov_gz);
|
||||||
|
qemu_aio_unref(acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const AIOCBInfo dictzip_aiocb_info = {
|
||||||
|
.aiocb_size = sizeof(DictZipAIOCB),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* This is where we get a request from a caller to read something */
|
||||||
|
static BlockAIOCB *dictzip_aio_readv(BlockDriverState *bs,
|
||||||
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb, void *opaque)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
DictZipAIOCB *acb;
|
||||||
|
QEMUIOVector *qiov_gz;
|
||||||
|
struct iovec *iov;
|
||||||
|
uint8_t *buf;
|
||||||
|
size_t start = sector_num * SECTOR_SIZE;
|
||||||
|
size_t len = nb_sectors * SECTOR_SIZE;
|
||||||
|
size_t end = start + len;
|
||||||
|
size_t gz_start;
|
||||||
|
size_t gz_len;
|
||||||
|
int64_t gz_sector_num;
|
||||||
|
int gz_nb_sectors;
|
||||||
|
int first_chunk, last_chunk;
|
||||||
|
int first_offset;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
acb = qemu_aio_get(&dictzip_aiocb_info, bs, cb, opaque);
|
||||||
|
if (!acb)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Search Cache */
|
||||||
|
for (i = 0; i < CACHE_COUNT; i++) {
|
||||||
|
if (!s->cache[i].len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((start >= s->cache[i].start) &&
|
||||||
|
(end <= (s->cache[i].start + s->cache[i].len))) {
|
||||||
|
acb->buf = s->cache[i].buf + (start - s->cache[i].start);
|
||||||
|
acb->len = len;
|
||||||
|
acb->qiov = qiov;
|
||||||
|
acb->bh = qemu_bh_new(dictzip_cache_cb, acb);
|
||||||
|
qemu_bh_schedule(acb->bh);
|
||||||
|
|
||||||
|
return &acb->common;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No cache, so let's decode */
|
||||||
|
/* We need to read these chunks */
|
||||||
|
first_chunk = start / s->chunk_len;
|
||||||
|
first_offset = start - first_chunk * s->chunk_len;
|
||||||
|
last_chunk = end / s->chunk_len;
|
||||||
|
|
||||||
|
gz_start = s->offsets[first_chunk];
|
||||||
|
gz_len = 0;
|
||||||
|
for (i = first_chunk; i <= last_chunk; i++) {
|
||||||
|
if (s->chunks32)
|
||||||
|
gz_len += le32_to_cpu(s->chunks32[i]);
|
||||||
|
else
|
||||||
|
gz_len += le16_to_cpu(s->chunks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gz_sector_num = gz_start / SECTOR_SIZE;
|
||||||
|
gz_nb_sectors = (gz_len / SECTOR_SIZE);
|
||||||
|
|
||||||
|
/* account for tail and heads */
|
||||||
|
while ((gz_start + gz_len) > ((gz_sector_num + gz_nb_sectors) * SECTOR_SIZE))
|
||||||
|
gz_nb_sectors++;
|
||||||
|
|
||||||
|
/* Allocate qiov, iov and buf in one chunk so we only need to free qiov */
|
||||||
|
qiov_gz = g_malloc0(sizeof(QEMUIOVector) + sizeof(struct iovec) +
|
||||||
|
(gz_nb_sectors * SECTOR_SIZE));
|
||||||
|
iov = (struct iovec *)(((char *)qiov_gz) + sizeof(QEMUIOVector));
|
||||||
|
buf = ((uint8_t *)iov) + sizeof(struct iovec *);
|
||||||
|
|
||||||
|
/* Kick off the read by the backing file, so we can start decompressing */
|
||||||
|
iov->iov_base = (void *)buf;
|
||||||
|
iov->iov_len = gz_nb_sectors * 512;
|
||||||
|
qemu_iovec_init_external(qiov_gz, iov, 1);
|
||||||
|
|
||||||
|
dprintf("read %zd - %zd => %zd - %zd\n", start, end, gz_start, gz_start + gz_len);
|
||||||
|
|
||||||
|
acb->s = s;
|
||||||
|
acb->qiov = qiov;
|
||||||
|
acb->qiov_gz = qiov_gz;
|
||||||
|
acb->start = start;
|
||||||
|
acb->len = len;
|
||||||
|
acb->gzipped = buf + (gz_start % SECTOR_SIZE);
|
||||||
|
acb->gz_len = gz_len;
|
||||||
|
acb->gz_start = first_chunk * s->chunk_len;
|
||||||
|
acb->offset = first_offset;
|
||||||
|
acb->chunks_len = (last_chunk - first_chunk + 1) * s->chunk_len;
|
||||||
|
|
||||||
|
return bdrv_aio_readv(s->hd, gz_sector_num, qiov_gz, gz_nb_sectors,
|
||||||
|
dictzip_read_cb, acb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dictzip_close(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < CACHE_COUNT; i++) {
|
||||||
|
if (!s->cache[i].len)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
g_free(s->cache[i].buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < Z_STREAM_COUNT; i++) {
|
||||||
|
inflateEnd(&s->zStream[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s->chunks)
|
||||||
|
g_free(s->chunks);
|
||||||
|
|
||||||
|
if (s->offsets)
|
||||||
|
g_free(s->offsets);
|
||||||
|
|
||||||
|
dprintf("Close\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t dictzip_getlength(BlockDriverState *bs)
|
||||||
|
{
|
||||||
|
BDRVDictZipState *s = bs->opaque;
|
||||||
|
dprintf("getlength -> %ld\n", s->file_len);
|
||||||
|
return s->file_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BlockDriver bdrv_dictzip = {
|
||||||
|
.format_name = "dzip",
|
||||||
|
.protocol_name = "dzip",
|
||||||
|
|
||||||
|
.instance_size = sizeof(BDRVDictZipState),
|
||||||
|
.bdrv_file_open = dictzip_open,
|
||||||
|
.bdrv_close = dictzip_close,
|
||||||
|
.bdrv_getlength = dictzip_getlength,
|
||||||
|
|
||||||
|
.bdrv_aio_readv = dictzip_aio_readv,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void dictzip_block_init(void)
|
||||||
|
{
|
||||||
|
bdrv_register(&bdrv_dictzip);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_init(dictzip_block_init);
|
||||||
@@ -1,629 +0,0 @@
|
|||||||
/*
|
|
||||||
* Block Dirty Bitmap
|
|
||||||
*
|
|
||||||
* Copyright (c) 2016 Red Hat. Inc
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "trace.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include "block/blockjob.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A BdrvDirtyBitmap can be in three possible states:
|
|
||||||
* (1) successor is NULL and disabled is false: full r/w mode
|
|
||||||
* (2) successor is NULL and disabled is true: read only mode ("disabled")
|
|
||||||
* (3) successor is set: frozen mode.
|
|
||||||
* A frozen bitmap cannot be renamed, deleted, anonymized, cleared, set,
|
|
||||||
* or enabled. A frozen bitmap can only abdicate() or reclaim().
|
|
||||||
*/
|
|
||||||
struct BdrvDirtyBitmap {
|
|
||||||
QemuMutex *mutex;
|
|
||||||
HBitmap *bitmap; /* Dirty sector bitmap implementation */
|
|
||||||
HBitmap *meta; /* Meta dirty bitmap */
|
|
||||||
BdrvDirtyBitmap *successor; /* Anonymous child; implies frozen status */
|
|
||||||
char *name; /* Optional non-empty unique ID */
|
|
||||||
int64_t size; /* Size of the bitmap (Number of sectors) */
|
|
||||||
bool disabled; /* Bitmap is read-only */
|
|
||||||
int active_iterators; /* How many iterators are active */
|
|
||||||
QLIST_ENTRY(BdrvDirtyBitmap) list;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BdrvDirtyBitmapIter {
|
|
||||||
HBitmapIter hbi;
|
|
||||||
BdrvDirtyBitmap *bitmap;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void bdrv_dirty_bitmaps_lock(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(&bs->dirty_bitmap_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void bdrv_dirty_bitmaps_unlock(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
qemu_mutex_unlock(&bs->dirty_bitmap_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_lock(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_unlock(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL or dirty_bitmap lock taken. */
|
|
||||||
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bm;
|
|
||||||
|
|
||||||
assert(name);
|
|
||||||
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
|
|
||||||
if (bm->name && !strcmp(name, bm->name)) {
|
|
||||||
return bm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
|
||||||
g_free(bitmap->name);
|
|
||||||
bitmap->name = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
|
|
||||||
uint32_t granularity,
|
|
||||||
const char *name,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
int64_t bitmap_size;
|
|
||||||
BdrvDirtyBitmap *bitmap;
|
|
||||||
uint32_t sector_granularity;
|
|
||||||
|
|
||||||
assert((granularity & (granularity - 1)) == 0);
|
|
||||||
|
|
||||||
if (name && bdrv_find_dirty_bitmap(bs, name)) {
|
|
||||||
error_setg(errp, "Bitmap already exists: %s", name);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
sector_granularity = granularity >> BDRV_SECTOR_BITS;
|
|
||||||
assert(sector_granularity);
|
|
||||||
bitmap_size = bdrv_nb_sectors(bs);
|
|
||||||
if (bitmap_size < 0) {
|
|
||||||
error_setg_errno(errp, -bitmap_size, "could not get length of device");
|
|
||||||
errno = -bitmap_size;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
bitmap = g_new0(BdrvDirtyBitmap, 1);
|
|
||||||
bitmap->mutex = &bs->dirty_bitmap_mutex;
|
|
||||||
bitmap->bitmap = hbitmap_alloc(bitmap_size, ctz32(sector_granularity));
|
|
||||||
bitmap->size = bitmap_size;
|
|
||||||
bitmap->name = g_strdup(name);
|
|
||||||
bitmap->disabled = false;
|
|
||||||
bdrv_dirty_bitmaps_lock(bs);
|
|
||||||
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
|
|
||||||
bdrv_dirty_bitmaps_unlock(bs);
|
|
||||||
return bitmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* bdrv_create_meta_dirty_bitmap
|
|
||||||
*
|
|
||||||
* Create a meta dirty bitmap that tracks the changes of bits in @bitmap. I.e.
|
|
||||||
* when a dirty status bit in @bitmap is changed (either from reset to set or
|
|
||||||
* the other way around), its respective meta dirty bitmap bit will be marked
|
|
||||||
* dirty as well.
|
|
||||||
*
|
|
||||||
* @bitmap: the block dirty bitmap for which to create a meta dirty bitmap.
|
|
||||||
* @chunk_size: how many bytes of bitmap data does each bit in the meta bitmap
|
|
||||||
* track.
|
|
||||||
*/
|
|
||||||
void bdrv_create_meta_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
|
||||||
int chunk_size)
|
|
||||||
{
|
|
||||||
assert(!bitmap->meta);
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
bitmap->meta = hbitmap_create_meta(bitmap->bitmap,
|
|
||||||
chunk_size * BITS_PER_BYTE);
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_release_meta_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
assert(bitmap->meta);
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
hbitmap_free_meta(bitmap->bitmap);
|
|
||||||
bitmap->meta = NULL;
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_dirty_bitmap_get_meta_locked(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
uint64_t i;
|
|
||||||
int sectors_per_bit = 1 << hbitmap_granularity(bitmap->meta);
|
|
||||||
|
|
||||||
/* To optimize: we can make hbitmap to internally check the range in a
|
|
||||||
* coarse level, or at least do it word by word. */
|
|
||||||
for (i = sector; i < sector + nb_sectors; i += sectors_per_bit) {
|
|
||||||
if (hbitmap_get(bitmap->meta, i)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bdrv_dirty_bitmap_get_meta(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
bool dirty;
|
|
||||||
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
dirty = bdrv_dirty_bitmap_get_meta_locked(bs, bitmap, sector, nb_sectors);
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
|
|
||||||
return dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_reset_meta(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap, int64_t sector,
|
|
||||||
int nb_sectors)
|
|
||||||
{
|
|
||||||
qemu_mutex_lock(bitmap->mutex);
|
|
||||||
hbitmap_reset(bitmap->meta, sector, nb_sectors);
|
|
||||||
qemu_mutex_unlock(bitmap->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t bdrv_dirty_bitmap_size(const BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return bitmap->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *bdrv_dirty_bitmap_name(const BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return bitmap->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return bitmap->successor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return !(bitmap->disabled || bitmap->successor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
DirtyBitmapStatus bdrv_dirty_bitmap_status(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
if (bdrv_dirty_bitmap_frozen(bitmap)) {
|
|
||||||
return DIRTY_BITMAP_STATUS_FROZEN;
|
|
||||||
} else if (!bdrv_dirty_bitmap_enabled(bitmap)) {
|
|
||||||
return DIRTY_BITMAP_STATUS_DISABLED;
|
|
||||||
} else {
|
|
||||||
return DIRTY_BITMAP_STATUS_ACTIVE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a successor bitmap destined to replace this bitmap after an operation.
|
|
||||||
* Requires that the bitmap is not frozen and has no successor.
|
|
||||||
* Called with BQL taken.
|
|
||||||
*/
|
|
||||||
int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap, Error **errp)
|
|
||||||
{
|
|
||||||
uint64_t granularity;
|
|
||||||
BdrvDirtyBitmap *child;
|
|
||||||
|
|
||||||
if (bdrv_dirty_bitmap_frozen(bitmap)) {
|
|
||||||
error_setg(errp, "Cannot create a successor for a bitmap that is "
|
|
||||||
"currently frozen");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
assert(!bitmap->successor);
|
|
||||||
|
|
||||||
/* Create an anonymous successor */
|
|
||||||
granularity = bdrv_dirty_bitmap_granularity(bitmap);
|
|
||||||
child = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
|
|
||||||
if (!child) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Successor will be on or off based on our current state. */
|
|
||||||
child->disabled = bitmap->disabled;
|
|
||||||
|
|
||||||
/* Install the successor and freeze the parent */
|
|
||||||
bitmap->successor = child;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For a bitmap with a successor, yield our name to the successor,
|
|
||||||
* delete the old bitmap, and return a handle to the new bitmap.
|
|
||||||
* Called with BQL taken.
|
|
||||||
*/
|
|
||||||
BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
char *name;
|
|
||||||
BdrvDirtyBitmap *successor = bitmap->successor;
|
|
||||||
|
|
||||||
if (successor == NULL) {
|
|
||||||
error_setg(errp, "Cannot relinquish control if "
|
|
||||||
"there's no successor present");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = bitmap->name;
|
|
||||||
bitmap->name = NULL;
|
|
||||||
successor->name = name;
|
|
||||||
bitmap->successor = NULL;
|
|
||||||
bdrv_release_dirty_bitmap(bs, bitmap);
|
|
||||||
|
|
||||||
return successor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In cases of failure where we can no longer safely delete the parent,
|
|
||||||
* we may wish to re-join the parent and child/successor.
|
|
||||||
* The merged parent will be un-frozen, but not explicitly re-enabled.
|
|
||||||
* Called with BQL taken.
|
|
||||||
*/
|
|
||||||
BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *parent,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *successor = parent->successor;
|
|
||||||
|
|
||||||
if (!successor) {
|
|
||||||
error_setg(errp, "Cannot reclaim a successor when none is present");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hbitmap_merge(parent->bitmap, successor->bitmap)) {
|
|
||||||
error_setg(errp, "Merging of parent and successor bitmap failed");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
bdrv_release_dirty_bitmap(bs, successor);
|
|
||||||
parent->successor = NULL;
|
|
||||||
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Truncates _all_ bitmaps attached to a BDS.
|
|
||||||
* Called with BQL taken.
|
|
||||||
*/
|
|
||||||
void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bitmap;
|
|
||||||
uint64_t size = bdrv_nb_sectors(bs);
|
|
||||||
|
|
||||||
bdrv_dirty_bitmaps_lock(bs);
|
|
||||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
|
||||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
|
||||||
assert(!bitmap->active_iterators);
|
|
||||||
hbitmap_truncate(bitmap->bitmap, size);
|
|
||||||
bitmap->size = size;
|
|
||||||
}
|
|
||||||
bdrv_dirty_bitmaps_unlock(bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
static void bdrv_do_release_matching_dirty_bitmap(BlockDriverState *bs,
|
|
||||||
BdrvDirtyBitmap *bitmap,
|
|
||||||
bool only_named)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bm, *next;
|
|
||||||
bdrv_dirty_bitmaps_lock(bs);
|
|
||||||
QLIST_FOREACH_SAFE(bm, &bs->dirty_bitmaps, list, next) {
|
|
||||||
if ((!bitmap || bm == bitmap) && (!only_named || bm->name)) {
|
|
||||||
assert(!bm->active_iterators);
|
|
||||||
assert(!bdrv_dirty_bitmap_frozen(bm));
|
|
||||||
assert(!bm->meta);
|
|
||||||
QLIST_REMOVE(bm, list);
|
|
||||||
hbitmap_free(bm->bitmap);
|
|
||||||
g_free(bm->name);
|
|
||||||
g_free(bm);
|
|
||||||
|
|
||||||
if (bitmap) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bitmap) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
bdrv_dirty_bitmaps_unlock(bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
bdrv_do_release_matching_dirty_bitmap(bs, bitmap, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release all named dirty bitmaps attached to a BDS (for use in bdrv_close()).
|
|
||||||
* There must not be any frozen bitmaps attached.
|
|
||||||
* Called with BQL taken.
|
|
||||||
*/
|
|
||||||
void bdrv_release_named_dirty_bitmaps(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
bdrv_do_release_matching_dirty_bitmap(bs, NULL, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
|
||||||
bitmap->disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called with BQL taken. */
|
|
||||||
void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
assert(!bdrv_dirty_bitmap_frozen(bitmap));
|
|
||||||
bitmap->disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bm;
|
|
||||||
BlockDirtyInfoList *list = NULL;
|
|
||||||
BlockDirtyInfoList **plist = &list;
|
|
||||||
|
|
||||||
bdrv_dirty_bitmaps_lock(bs);
|
|
||||||
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
|
|
||||||
BlockDirtyInfo *info = g_new0(BlockDirtyInfo, 1);
|
|
||||||
BlockDirtyInfoList *entry = g_new0(BlockDirtyInfoList, 1);
|
|
||||||
info->count = bdrv_get_dirty_count(bm);
|
|
||||||
info->granularity = bdrv_dirty_bitmap_granularity(bm);
|
|
||||||
info->has_name = !!bm->name;
|
|
||||||
info->name = g_strdup(bm->name);
|
|
||||||
info->status = bdrv_dirty_bitmap_status(bm);
|
|
||||||
entry->value = info;
|
|
||||||
*plist = entry;
|
|
||||||
plist = &entry->next;
|
|
||||||
}
|
|
||||||
bdrv_dirty_bitmaps_unlock(bs);
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
|
||||||
int bdrv_get_dirty_locked(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
|
|
||||||
int64_t sector)
|
|
||||||
{
|
|
||||||
if (bitmap) {
|
|
||||||
return hbitmap_get(bitmap->bitmap, sector);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chooses a default granularity based on the existing cluster size,
|
|
||||||
* but clamped between [4K, 64K]. Defaults to 64K in the case that there
|
|
||||||
* is no cluster size information available.
|
|
||||||
*/
|
|
||||||
uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BlockDriverInfo bdi;
|
|
||||||
uint32_t granularity;
|
|
||||||
|
|
||||||
if (bdrv_get_info(bs, &bdi) >= 0 && bdi.cluster_size > 0) {
|
|
||||||
granularity = MAX(4096, bdi.cluster_size);
|
|
||||||
granularity = MIN(65536, granularity);
|
|
||||||
} else {
|
|
||||||
granularity = 65536;
|
|
||||||
}
|
|
||||||
|
|
||||||
return granularity;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t bdrv_dirty_bitmap_meta_granularity(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
BdrvDirtyBitmapIter *bdrv_dirty_iter_new(BdrvDirtyBitmap *bitmap,
|
|
||||||
uint64_t first_sector)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmapIter *iter = g_new(BdrvDirtyBitmapIter, 1);
|
|
||||||
hbitmap_iter_init(&iter->hbi, bitmap->bitmap, first_sector);
|
|
||||||
iter->bitmap = bitmap;
|
|
||||||
bitmap->active_iterators++;
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
BdrvDirtyBitmapIter *bdrv_dirty_meta_iter_new(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmapIter *iter = g_new(BdrvDirtyBitmapIter, 1);
|
|
||||||
hbitmap_iter_init(&iter->hbi, bitmap->meta, 0);
|
|
||||||
iter->bitmap = bitmap;
|
|
||||||
bitmap->active_iterators++;
|
|
||||||
return iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_iter_free(BdrvDirtyBitmapIter *iter)
|
|
||||||
{
|
|
||||||
if (!iter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(iter->bitmap->active_iterators > 0);
|
|
||||||
iter->bitmap->active_iterators--;
|
|
||||||
g_free(iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t bdrv_dirty_iter_next(BdrvDirtyBitmapIter *iter)
|
|
||||||
{
|
|
||||||
return hbitmap_iter_next(&iter->hbi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
|
||||||
void bdrv_set_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
|
|
||||||
int64_t cur_sector, int64_t nr_sectors)
|
|
||||||
{
|
|
||||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
|
||||||
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
|
||||||
int64_t cur_sector, int64_t nr_sectors)
|
|
||||||
{
|
|
||||||
bdrv_dirty_bitmap_lock(bitmap);
|
|
||||||
bdrv_set_dirty_bitmap_locked(bitmap, cur_sector, nr_sectors);
|
|
||||||
bdrv_dirty_bitmap_unlock(bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Called within bdrv_dirty_bitmap_lock..unlock */
|
|
||||||
void bdrv_reset_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
|
|
||||||
int64_t cur_sector, int64_t nr_sectors)
|
|
||||||
{
|
|
||||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
|
||||||
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,
|
|
||||||
int64_t cur_sector, int64_t nr_sectors)
|
|
||||||
{
|
|
||||||
bdrv_dirty_bitmap_lock(bitmap);
|
|
||||||
bdrv_reset_dirty_bitmap_locked(bitmap, cur_sector, nr_sectors);
|
|
||||||
bdrv_dirty_bitmap_unlock(bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap **out)
|
|
||||||
{
|
|
||||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
|
||||||
bdrv_dirty_bitmap_lock(bitmap);
|
|
||||||
if (!out) {
|
|
||||||
hbitmap_reset_all(bitmap->bitmap);
|
|
||||||
} else {
|
|
||||||
HBitmap *backup = bitmap->bitmap;
|
|
||||||
bitmap->bitmap = hbitmap_alloc(bitmap->size,
|
|
||||||
hbitmap_granularity(backup));
|
|
||||||
*out = backup;
|
|
||||||
}
|
|
||||||
bdrv_dirty_bitmap_unlock(bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_undo_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap, HBitmap *in)
|
|
||||||
{
|
|
||||||
HBitmap *tmp = bitmap->bitmap;
|
|
||||||
assert(bdrv_dirty_bitmap_enabled(bitmap));
|
|
||||||
bitmap->bitmap = in;
|
|
||||||
hbitmap_free(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bdrv_dirty_bitmap_serialization_size(const BdrvDirtyBitmap *bitmap,
|
|
||||||
uint64_t start, uint64_t count)
|
|
||||||
{
|
|
||||||
return hbitmap_serialization_size(bitmap->bitmap, start, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t bdrv_dirty_bitmap_serialization_align(const BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return hbitmap_serialization_granularity(bitmap->bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_serialize_part(const BdrvDirtyBitmap *bitmap,
|
|
||||||
uint8_t *buf, uint64_t start,
|
|
||||||
uint64_t count)
|
|
||||||
{
|
|
||||||
hbitmap_serialize_part(bitmap->bitmap, buf, start, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_deserialize_part(BdrvDirtyBitmap *bitmap,
|
|
||||||
uint8_t *buf, uint64_t start,
|
|
||||||
uint64_t count, bool finish)
|
|
||||||
{
|
|
||||||
hbitmap_deserialize_part(bitmap->bitmap, buf, start, count, finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_deserialize_zeroes(BdrvDirtyBitmap *bitmap,
|
|
||||||
uint64_t start, uint64_t count,
|
|
||||||
bool finish)
|
|
||||||
{
|
|
||||||
hbitmap_deserialize_zeroes(bitmap->bitmap, start, count, finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_dirty_bitmap_deserialize_finish(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
hbitmap_deserialize_finish(bitmap->bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
|
|
||||||
int64_t nr_sectors)
|
|
||||||
{
|
|
||||||
BdrvDirtyBitmap *bitmap;
|
|
||||||
|
|
||||||
if (QLIST_EMPTY(&bs->dirty_bitmaps)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bdrv_dirty_bitmaps_lock(bs);
|
|
||||||
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
|
|
||||||
if (!bdrv_dirty_bitmap_enabled(bitmap)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
|
|
||||||
}
|
|
||||||
bdrv_dirty_bitmaps_unlock(bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advance a BdrvDirtyBitmapIter to an arbitrary offset.
|
|
||||||
*/
|
|
||||||
void bdrv_set_dirty_iter(BdrvDirtyBitmapIter *iter, int64_t sector_num)
|
|
||||||
{
|
|
||||||
hbitmap_iter_init(&iter->hbi, iter->hbi.hb, sector_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t bdrv_get_dirty_count(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return hbitmap_count(bitmap->bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t bdrv_get_meta_dirty_count(BdrvDirtyBitmap *bitmap)
|
|
||||||
{
|
|
||||||
return hbitmap_count(bitmap->meta);
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* DMG bzip2 uncompression
|
|
||||||
*
|
|
||||||
* Copyright (c) 2004 Johannes E. Schindelin
|
|
||||||
* Copyright (c) 2016 Red Hat, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "dmg.h"
|
|
||||||
#include <bzlib.h>
|
|
||||||
|
|
||||||
static int dmg_uncompress_bz2_do(char *next_in, unsigned int avail_in,
|
|
||||||
char *next_out, unsigned int avail_out)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
uint64_t total_out;
|
|
||||||
bz_stream bzstream = {};
|
|
||||||
|
|
||||||
ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
|
||||||
if (ret != BZ_OK) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
bzstream.next_in = next_in;
|
|
||||||
bzstream.avail_in = avail_in;
|
|
||||||
bzstream.next_out = next_out;
|
|
||||||
bzstream.avail_out = avail_out;
|
|
||||||
ret = BZ2_bzDecompress(&bzstream);
|
|
||||||
total_out = ((uint64_t)bzstream.total_out_hi32 << 32) +
|
|
||||||
bzstream.total_out_lo32;
|
|
||||||
BZ2_bzDecompressEnd(&bzstream);
|
|
||||||
if (ret != BZ_STREAM_END ||
|
|
||||||
total_out != avail_out) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((constructor))
|
|
||||||
static void dmg_bz2_init(void)
|
|
||||||
{
|
|
||||||
assert(!dmg_uncompress_bz2);
|
|
||||||
dmg_uncompress_bz2 = dmg_uncompress_bz2_do;
|
|
||||||
}
|
|
||||||
140
block/dmg.c
140
block/dmg.c
@@ -21,17 +21,15 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/bswap.h"
|
#include "qemu/bswap.h"
|
||||||
#include "qemu/error-report.h"
|
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "dmg.h"
|
#include <zlib.h>
|
||||||
|
#ifdef CONFIG_BZIP2
|
||||||
int (*dmg_uncompress_bz2)(char *next_in, unsigned int avail_in,
|
#include <bzlib.h>
|
||||||
char *next_out, unsigned int avail_out);
|
#endif
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
/* Limit chunk sizes to prevent unreasonable amounts of memory being used
|
/* Limit chunk sizes to prevent unreasonable amounts of memory being used
|
||||||
@@ -41,6 +39,31 @@ enum {
|
|||||||
DMG_SECTORCOUNTS_MAX = DMG_LENGTHS_MAX / 512,
|
DMG_SECTORCOUNTS_MAX = DMG_LENGTHS_MAX / 512,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct BDRVDMGState {
|
||||||
|
CoMutex lock;
|
||||||
|
/* each chunk contains a certain number of sectors,
|
||||||
|
* offsets[i] is the offset in the .dmg file,
|
||||||
|
* lengths[i] is the length of the compressed chunk,
|
||||||
|
* sectors[i] is the sector beginning at offsets[i],
|
||||||
|
* sectorcounts[i] is the number of sectors in that chunk,
|
||||||
|
* the sectors array is ordered
|
||||||
|
* 0<=i<n_chunks */
|
||||||
|
|
||||||
|
uint32_t n_chunks;
|
||||||
|
uint32_t* types;
|
||||||
|
uint64_t* offsets;
|
||||||
|
uint64_t* lengths;
|
||||||
|
uint64_t* sectors;
|
||||||
|
uint64_t* sectorcounts;
|
||||||
|
uint32_t current_chunk;
|
||||||
|
uint8_t *compressed_chunk;
|
||||||
|
uint8_t *uncompressed_chunk;
|
||||||
|
z_stream zstream;
|
||||||
|
#ifdef CONFIG_BZIP2
|
||||||
|
bz_stream bzstream;
|
||||||
|
#endif
|
||||||
|
} BDRVDMGState;
|
||||||
|
|
||||||
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
@@ -128,9 +151,8 @@ static void update_max_chunk_size(BDRVDMGState *s, uint32_t chunk,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t dmg_find_koly_offset(BdrvChild *file, Error **errp)
|
static int64_t dmg_find_koly_offset(BlockDriverState *file_bs, Error **errp)
|
||||||
{
|
{
|
||||||
BlockDriverState *file_bs = file->bs;
|
|
||||||
int64_t length;
|
int64_t length;
|
||||||
int64_t offset = 0;
|
int64_t offset = 0;
|
||||||
uint8_t buffer[515];
|
uint8_t buffer[515];
|
||||||
@@ -154,7 +176,7 @@ static int64_t dmg_find_koly_offset(BdrvChild *file, Error **errp)
|
|||||||
offset = length - 511 - 512;
|
offset = length - 511 - 512;
|
||||||
}
|
}
|
||||||
length = length < 515 ? length : 515;
|
length = length < 515 ? length : 515;
|
||||||
ret = bdrv_pread(file, offset, buffer, length);
|
ret = bdrv_pread(file_bs, offset, buffer, length);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg_errno(errp, -ret, "Failed while reading UDIF trailer");
|
error_setg_errno(errp, -ret, "Failed while reading UDIF trailer");
|
||||||
return ret;
|
return ret;
|
||||||
@@ -185,9 +207,10 @@ static bool dmg_is_known_block_type(uint32_t entry_type)
|
|||||||
case 0x00000001: /* uncompressed */
|
case 0x00000001: /* uncompressed */
|
||||||
case 0x00000002: /* zeroes */
|
case 0x00000002: /* zeroes */
|
||||||
case 0x80000005: /* zlib */
|
case 0x80000005: /* zlib */
|
||||||
return true;
|
#ifdef CONFIG_BZIP2
|
||||||
case 0x80000006: /* bzip2 */
|
case 0x80000006: /* bzip2 */
|
||||||
return !!dmg_uncompress_bz2;
|
#endif
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -413,19 +436,7 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
int64_t offset;
|
int64_t offset;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_file,
|
bs->read_only = 1;
|
||||||
false, errp);
|
|
||||||
if (!bs->file) {
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = bdrv_set_read_only(bs, true, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
block_module_load_one("dmg-bz2");
|
|
||||||
|
|
||||||
s->n_chunks = 0;
|
s->n_chunks = 0;
|
||||||
s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
|
s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
|
||||||
/* used by dmg_read_mish_block to keep track of the current I/O position */
|
/* used by dmg_read_mish_block to keep track of the current I/O position */
|
||||||
@@ -502,9 +513,9 @@ static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* initialize zlib engine */
|
/* initialize zlib engine */
|
||||||
s->compressed_chunk = qemu_try_blockalign(bs->file->bs,
|
s->compressed_chunk = qemu_try_blockalign(bs->file,
|
||||||
ds.max_compressed_size + 1);
|
ds.max_compressed_size + 1);
|
||||||
s->uncompressed_chunk = qemu_try_blockalign(bs->file->bs,
|
s->uncompressed_chunk = qemu_try_blockalign(bs->file,
|
||||||
512 * ds.max_sectors_per_chunk);
|
512 * ds.max_sectors_per_chunk);
|
||||||
if (s->compressed_chunk == NULL || s->uncompressed_chunk == NULL) {
|
if (s->compressed_chunk == NULL || s->uncompressed_chunk == NULL) {
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
@@ -532,11 +543,6 @@ fail:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dmg_refresh_limits(BlockDriverState *bs, Error **errp)
|
|
||||||
{
|
|
||||||
bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int is_sector_in_chunk(BDRVDMGState* s,
|
static inline int is_sector_in_chunk(BDRVDMGState* s,
|
||||||
uint32_t chunk_num, uint64_t sector_num)
|
uint32_t chunk_num, uint64_t sector_num)
|
||||||
{
|
{
|
||||||
@@ -572,6 +578,9 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|||||||
if (!is_sector_in_chunk(s, s->current_chunk, sector_num)) {
|
if (!is_sector_in_chunk(s, s->current_chunk, sector_num)) {
|
||||||
int ret;
|
int ret;
|
||||||
uint32_t chunk = search_chunk(s, sector_num);
|
uint32_t chunk = search_chunk(s, sector_num);
|
||||||
|
#ifdef CONFIG_BZIP2
|
||||||
|
uint64_t total_out;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (chunk >= s->n_chunks) {
|
if (chunk >= s->n_chunks) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -602,10 +611,8 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
break; }
|
break; }
|
||||||
|
#ifdef CONFIG_BZIP2
|
||||||
case 0x80000006: /* bzip2 compressed */
|
case 0x80000006: /* bzip2 compressed */
|
||||||
if (!dmg_uncompress_bz2) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* we need to buffer, because only the chunk as whole can be
|
/* we need to buffer, because only the chunk as whole can be
|
||||||
* inflated. */
|
* inflated. */
|
||||||
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
||||||
@@ -614,15 +621,24 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = dmg_uncompress_bz2((char *)s->compressed_chunk,
|
ret = BZ2_bzDecompressInit(&s->bzstream, 0, 0);
|
||||||
(unsigned int) s->lengths[chunk],
|
if (ret != BZ_OK) {
|
||||||
(char *)s->uncompressed_chunk,
|
return -1;
|
||||||
(unsigned int)
|
}
|
||||||
(512 * s->sectorcounts[chunk]));
|
s->bzstream.next_in = (char *)s->compressed_chunk;
|
||||||
if (ret < 0) {
|
s->bzstream.avail_in = (unsigned int) s->lengths[chunk];
|
||||||
return ret;
|
s->bzstream.next_out = (char *)s->uncompressed_chunk;
|
||||||
|
s->bzstream.avail_out = (unsigned int) 512 * s->sectorcounts[chunk];
|
||||||
|
ret = BZ2_bzDecompress(&s->bzstream);
|
||||||
|
total_out = ((uint64_t)s->bzstream.total_out_hi32 << 32) +
|
||||||
|
s->bzstream.total_out_lo32;
|
||||||
|
BZ2_bzDecompressEnd(&s->bzstream);
|
||||||
|
if (ret != BZ_STREAM_END ||
|
||||||
|
total_out != 512 * s->sectorcounts[chunk]) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
#endif /* CONFIG_BZIP2 */
|
||||||
case 1: /* copy */
|
case 1: /* copy */
|
||||||
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
||||||
s->uncompressed_chunk, s->lengths[chunk]);
|
s->uncompressed_chunk, s->lengths[chunk]);
|
||||||
@@ -640,42 +656,38 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn
|
static int dmg_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
dmg_co_preadv(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
|
uint8_t *buf, int nb_sectors)
|
||||||
QEMUIOVector *qiov, int flags)
|
|
||||||
{
|
{
|
||||||
BDRVDMGState *s = bs->opaque;
|
BDRVDMGState *s = bs->opaque;
|
||||||
uint64_t sector_num = offset >> BDRV_SECTOR_BITS;
|
int i;
|
||||||
int nb_sectors = bytes >> BDRV_SECTOR_BITS;
|
|
||||||
int ret, i;
|
|
||||||
|
|
||||||
assert((offset & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
assert((bytes & (BDRV_SECTOR_SIZE - 1)) == 0);
|
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->lock);
|
|
||||||
|
|
||||||
for (i = 0; i < nb_sectors; i++) {
|
for (i = 0; i < nb_sectors; i++) {
|
||||||
uint32_t sector_offset_in_chunk;
|
uint32_t sector_offset_in_chunk;
|
||||||
void *data;
|
|
||||||
|
|
||||||
if (dmg_read_chunk(bs, sector_num + i) != 0) {
|
if (dmg_read_chunk(bs, sector_num + i) != 0) {
|
||||||
ret = -EIO;
|
return -1;
|
||||||
goto fail;
|
|
||||||
}
|
}
|
||||||
/* Special case: current chunk is all zeroes. Do not perform a memcpy as
|
/* Special case: current chunk is all zeroes. Do not perform a memcpy as
|
||||||
* s->uncompressed_chunk may be too small to cover the large all-zeroes
|
* s->uncompressed_chunk may be too small to cover the large all-zeroes
|
||||||
* section. dmg_read_chunk is called to find s->current_chunk */
|
* section. dmg_read_chunk is called to find s->current_chunk */
|
||||||
if (s->types[s->current_chunk] == 2) { /* all zeroes block entry */
|
if (s->types[s->current_chunk] == 2) { /* all zeroes block entry */
|
||||||
qemu_iovec_memset(qiov, i * 512, 0, 512);
|
memset(buf + i * 512, 0, 512);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sector_offset_in_chunk = sector_num + i - s->sectors[s->current_chunk];
|
sector_offset_in_chunk = sector_num + i - s->sectors[s->current_chunk];
|
||||||
data = s->uncompressed_chunk + sector_offset_in_chunk * 512;
|
memcpy(buf + i * 512,
|
||||||
qemu_iovec_from_buf(qiov, i * 512, data, 512);
|
s->uncompressed_chunk + sector_offset_in_chunk * 512, 512);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ret = 0;
|
static coroutine_fn int dmg_co_read(BlockDriverState *bs, int64_t sector_num,
|
||||||
fail:
|
uint8_t *buf, int nb_sectors)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
BDRVDMGState *s = bs->opaque;
|
||||||
|
qemu_co_mutex_lock(&s->lock);
|
||||||
|
ret = dmg_read(bs, sector_num, buf, nb_sectors);
|
||||||
qemu_co_mutex_unlock(&s->lock);
|
qemu_co_mutex_unlock(&s->lock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -700,9 +712,7 @@ static BlockDriver bdrv_dmg = {
|
|||||||
.instance_size = sizeof(BDRVDMGState),
|
.instance_size = sizeof(BDRVDMGState),
|
||||||
.bdrv_probe = dmg_probe,
|
.bdrv_probe = dmg_probe,
|
||||||
.bdrv_open = dmg_open,
|
.bdrv_open = dmg_open,
|
||||||
.bdrv_refresh_limits = dmg_refresh_limits,
|
.bdrv_read = dmg_co_read,
|
||||||
.bdrv_child_perm = bdrv_format_default_perms,
|
|
||||||
.bdrv_co_preadv = dmg_co_preadv,
|
|
||||||
.bdrv_close = dmg_close,
|
.bdrv_close = dmg_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
59
block/dmg.h
59
block/dmg.h
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Header for DMG driver
|
|
||||||
*
|
|
||||||
* Copyright (c) 2004-2006 Fabrice Bellard
|
|
||||||
* Copyright (c) 2016 Red hat, Inc.
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BLOCK_DMG_H
|
|
||||||
#define BLOCK_DMG_H
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "block/block_int.h"
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
typedef struct BDRVDMGState {
|
|
||||||
CoMutex lock;
|
|
||||||
/* each chunk contains a certain number of sectors,
|
|
||||||
* offsets[i] is the offset in the .dmg file,
|
|
||||||
* lengths[i] is the length of the compressed chunk,
|
|
||||||
* sectors[i] is the sector beginning at offsets[i],
|
|
||||||
* sectorcounts[i] is the number of sectors in that chunk,
|
|
||||||
* the sectors array is ordered
|
|
||||||
* 0<=i<n_chunks */
|
|
||||||
|
|
||||||
uint32_t n_chunks;
|
|
||||||
uint32_t *types;
|
|
||||||
uint64_t *offsets;
|
|
||||||
uint64_t *lengths;
|
|
||||||
uint64_t *sectors;
|
|
||||||
uint64_t *sectorcounts;
|
|
||||||
uint32_t current_chunk;
|
|
||||||
uint8_t *compressed_chunk;
|
|
||||||
uint8_t *uncompressed_chunk;
|
|
||||||
z_stream zstream;
|
|
||||||
} BDRVDMGState;
|
|
||||||
|
|
||||||
extern int (*dmg_uncompress_bz2)(char *next_in, unsigned int avail_in,
|
|
||||||
char *next_out, unsigned int avail_out);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
1220
block/gluster.c
1220
block/gluster.c
File diff suppressed because it is too large
Load Diff
2681
block/io.c
2681
block/io.c
File diff suppressed because it is too large
Load Diff
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* QEMU Block driver for iSCSI images (static options)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017 Peter Lieven <pl@kamp.de>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
|
||||||
#include "qemu/config-file.h"
|
|
||||||
|
|
||||||
static QemuOptsList qemu_iscsi_opts = {
|
|
||||||
.name = "iscsi",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(qemu_iscsi_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "user",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "username for CHAP authentication to target",
|
|
||||||
},{
|
|
||||||
.name = "password",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "password for CHAP authentication to target",
|
|
||||||
},{
|
|
||||||
.name = "password-secret",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of the secret providing password for CHAP "
|
|
||||||
"authentication to target",
|
|
||||||
},{
|
|
||||||
.name = "header-digest",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "HeaderDigest setting. "
|
|
||||||
"{CRC32C|CRC32C-NONE|NONE-CRC32C|NONE}",
|
|
||||||
},{
|
|
||||||
.name = "initiator-name",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Initiator iqn name to use when connecting",
|
|
||||||
},{
|
|
||||||
.name = "timeout",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Request timeout in seconds (default 0 = no timeout)",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void iscsi_block_opts_init(void)
|
|
||||||
{
|
|
||||||
qemu_add_opts(&qemu_iscsi_opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
block_init(iscsi_block_opts_init);
|
|
||||||
1232
block/iscsi.c
1232
block/iscsi.c
File diff suppressed because it is too large
Load Diff
@@ -7,14 +7,11 @@
|
|||||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/aio.h"
|
#include "block/aio.h"
|
||||||
#include "qemu/queue.h"
|
#include "qemu/queue.h"
|
||||||
#include "block/block.h"
|
|
||||||
#include "block/raw-aio.h"
|
#include "block/raw-aio.h"
|
||||||
#include "qemu/event_notifier.h"
|
#include "qemu/event_notifier.h"
|
||||||
#include "qemu/coroutine.h"
|
|
||||||
|
|
||||||
#include <libaio.h>
|
#include <libaio.h>
|
||||||
|
|
||||||
@@ -28,10 +25,11 @@
|
|||||||
*/
|
*/
|
||||||
#define MAX_EVENTS 128
|
#define MAX_EVENTS 128
|
||||||
|
|
||||||
|
#define MAX_QUEUED_IO 128
|
||||||
|
|
||||||
struct qemu_laiocb {
|
struct qemu_laiocb {
|
||||||
BlockAIOCB common;
|
BlockAIOCB common;
|
||||||
Coroutine *co;
|
struct qemu_laio_state *ctx;
|
||||||
LinuxAioState *ctx;
|
|
||||||
struct iocb iocb;
|
struct iocb iocb;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
size_t nbytes;
|
size_t nbytes;
|
||||||
@@ -42,28 +40,26 @@ struct qemu_laiocb {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int plugged;
|
int plugged;
|
||||||
unsigned int in_queue;
|
unsigned int n;
|
||||||
unsigned int in_flight;
|
|
||||||
bool blocked;
|
bool blocked;
|
||||||
QSIMPLEQ_HEAD(, qemu_laiocb) pending;
|
QSIMPLEQ_HEAD(, qemu_laiocb) pending;
|
||||||
} LaioQueue;
|
} LaioQueue;
|
||||||
|
|
||||||
struct LinuxAioState {
|
struct qemu_laio_state {
|
||||||
AioContext *aio_context;
|
|
||||||
|
|
||||||
io_context_t ctx;
|
io_context_t ctx;
|
||||||
EventNotifier e;
|
EventNotifier e;
|
||||||
|
|
||||||
/* io queue for submit at batch. Protected by AioContext lock. */
|
/* io queue for submit at batch */
|
||||||
LaioQueue io_q;
|
LaioQueue io_q;
|
||||||
|
|
||||||
/* I/O completion processing. Only runs in I/O thread. */
|
/* I/O completion processing */
|
||||||
QEMUBH *completion_bh;
|
QEMUBH *completion_bh;
|
||||||
|
struct io_event events[MAX_EVENTS];
|
||||||
int event_idx;
|
int event_idx;
|
||||||
int event_max;
|
int event_max;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void ioq_submit(LinuxAioState *s);
|
static void ioq_submit(struct qemu_laio_state *s);
|
||||||
|
|
||||||
static inline ssize_t io_event_ret(struct io_event *ev)
|
static inline ssize_t io_event_ret(struct io_event *ev)
|
||||||
{
|
{
|
||||||
@@ -73,7 +69,8 @@ static inline ssize_t io_event_ret(struct io_event *ev)
|
|||||||
/*
|
/*
|
||||||
* Completes an AIO request (calls the callback and frees the ACB).
|
* Completes an AIO request (calls the callback and frees the ACB).
|
||||||
*/
|
*/
|
||||||
static void qemu_laio_process_completion(struct qemu_laiocb *laiocb)
|
static void qemu_laio_process_completion(struct qemu_laio_state *s,
|
||||||
|
struct qemu_laiocb *laiocb)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@@ -87,191 +84,74 @@ static void qemu_laio_process_completion(struct qemu_laiocb *laiocb)
|
|||||||
qemu_iovec_memset(laiocb->qiov, ret, 0,
|
qemu_iovec_memset(laiocb->qiov, ret, 0,
|
||||||
laiocb->qiov->size - ret);
|
laiocb->qiov->size - ret);
|
||||||
} else {
|
} else {
|
||||||
ret = -ENOSPC;
|
ret = -EINVAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
laiocb->ret = ret;
|
|
||||||
if (laiocb->co) {
|
|
||||||
/* If the coroutine is already entered it must be in ioq_submit() and
|
|
||||||
* will notice laio->ret has been filled in when it eventually runs
|
|
||||||
* later. Coroutines cannot be entered recursively so avoid doing
|
|
||||||
* that!
|
|
||||||
*/
|
|
||||||
if (!qemu_coroutine_entered(laiocb->co)) {
|
|
||||||
aio_co_wake(laiocb->co);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
laiocb->common.cb(laiocb->common.opaque, ret);
|
laiocb->common.cb(laiocb->common.opaque, ret);
|
||||||
|
|
||||||
qemu_aio_unref(laiocb);
|
qemu_aio_unref(laiocb);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* The completion BH fetches completed I/O requests and invokes their
|
||||||
* aio_ring buffer which is shared between userspace and kernel.
|
* callbacks.
|
||||||
*
|
|
||||||
* This copied from linux/fs/aio.c, common header does not exist
|
|
||||||
* but AIO exists for ages so we assume ABI is stable.
|
|
||||||
*/
|
|
||||||
struct aio_ring {
|
|
||||||
unsigned id; /* kernel internal index number */
|
|
||||||
unsigned nr; /* number of io_events */
|
|
||||||
unsigned head; /* Written to by userland or by kernel. */
|
|
||||||
unsigned tail;
|
|
||||||
|
|
||||||
unsigned magic;
|
|
||||||
unsigned compat_features;
|
|
||||||
unsigned incompat_features;
|
|
||||||
unsigned header_length; /* size of aio_ring */
|
|
||||||
|
|
||||||
struct io_event io_events[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* io_getevents_peek:
|
|
||||||
* @ctx: AIO context
|
|
||||||
* @events: pointer on events array, output value
|
|
||||||
|
|
||||||
* Returns the number of completed events and sets a pointer
|
|
||||||
* on events array. This function does not update the internal
|
|
||||||
* ring buffer, only reads head and tail. When @events has been
|
|
||||||
* processed io_getevents_commit() must be called.
|
|
||||||
*/
|
|
||||||
static inline unsigned int io_getevents_peek(io_context_t ctx,
|
|
||||||
struct io_event **events)
|
|
||||||
{
|
|
||||||
struct aio_ring *ring = (struct aio_ring *)ctx;
|
|
||||||
unsigned int head = ring->head, tail = ring->tail;
|
|
||||||
unsigned int nr;
|
|
||||||
|
|
||||||
nr = tail >= head ? tail - head : ring->nr - head;
|
|
||||||
*events = ring->io_events + head;
|
|
||||||
/* To avoid speculative loads of s->events[i] before observing tail.
|
|
||||||
Paired with smp_wmb() inside linux/fs/aio.c: aio_complete(). */
|
|
||||||
smp_rmb();
|
|
||||||
|
|
||||||
return nr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* io_getevents_commit:
|
|
||||||
* @ctx: AIO context
|
|
||||||
* @nr: the number of events on which head should be advanced
|
|
||||||
*
|
|
||||||
* Advances head of a ring buffer.
|
|
||||||
*/
|
|
||||||
static inline void io_getevents_commit(io_context_t ctx, unsigned int nr)
|
|
||||||
{
|
|
||||||
struct aio_ring *ring = (struct aio_ring *)ctx;
|
|
||||||
|
|
||||||
if (nr) {
|
|
||||||
ring->head = (ring->head + nr) % ring->nr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* io_getevents_advance_and_peek:
|
|
||||||
* @ctx: AIO context
|
|
||||||
* @events: pointer on events array, output value
|
|
||||||
* @nr: the number of events on which head should be advanced
|
|
||||||
*
|
|
||||||
* Advances head of a ring buffer and returns number of elements left.
|
|
||||||
*/
|
|
||||||
static inline unsigned int
|
|
||||||
io_getevents_advance_and_peek(io_context_t ctx,
|
|
||||||
struct io_event **events,
|
|
||||||
unsigned int nr)
|
|
||||||
{
|
|
||||||
io_getevents_commit(ctx, nr);
|
|
||||||
return io_getevents_peek(ctx, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* qemu_laio_process_completions:
|
|
||||||
* @s: AIO state
|
|
||||||
*
|
|
||||||
* Fetches completed I/O requests and invokes their callbacks.
|
|
||||||
*
|
*
|
||||||
* The function is somewhat tricky because it supports nested event loops, for
|
* The function is somewhat tricky because it supports nested event loops, for
|
||||||
* example when a request callback invokes aio_poll(). In order to do this,
|
* example when a request callback invokes aio_poll(). In order to do this,
|
||||||
* indices are kept in LinuxAioState. Function schedules BH completion so it
|
* the completion events array and index are kept in qemu_laio_state. The BH
|
||||||
* can be called again in a nested event loop. When there are no events left
|
* reschedules itself as long as there are completions pending so it will
|
||||||
* to complete the BH is being canceled.
|
* either be called again in a nested event loop or will be called after all
|
||||||
|
* events have been completed. When there are no events left to complete, the
|
||||||
|
* BH returns without rescheduling.
|
||||||
*/
|
*/
|
||||||
static void qemu_laio_process_completions(LinuxAioState *s)
|
static void qemu_laio_completion_bh(void *opaque)
|
||||||
{
|
{
|
||||||
struct io_event *events;
|
struct qemu_laio_state *s = opaque;
|
||||||
|
|
||||||
|
/* Fetch more completion events when empty */
|
||||||
|
if (s->event_idx == s->event_max) {
|
||||||
|
do {
|
||||||
|
struct timespec ts = { 0 };
|
||||||
|
s->event_max = io_getevents(s->ctx, MAX_EVENTS, MAX_EVENTS,
|
||||||
|
s->events, &ts);
|
||||||
|
} while (s->event_max == -EINTR);
|
||||||
|
|
||||||
|
s->event_idx = 0;
|
||||||
|
if (s->event_max <= 0) {
|
||||||
|
s->event_max = 0;
|
||||||
|
return; /* no more events */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Reschedule so nested event loops see currently pending completions */
|
/* Reschedule so nested event loops see currently pending completions */
|
||||||
qemu_bh_schedule(s->completion_bh);
|
qemu_bh_schedule(s->completion_bh);
|
||||||
|
|
||||||
while ((s->event_max = io_getevents_advance_and_peek(s->ctx, &events,
|
/* Process completion events */
|
||||||
s->event_idx))) {
|
while (s->event_idx < s->event_max) {
|
||||||
for (s->event_idx = 0; s->event_idx < s->event_max; ) {
|
struct iocb *iocb = s->events[s->event_idx].obj;
|
||||||
struct iocb *iocb = events[s->event_idx].obj;
|
|
||||||
struct qemu_laiocb *laiocb =
|
struct qemu_laiocb *laiocb =
|
||||||
container_of(iocb, struct qemu_laiocb, iocb);
|
container_of(iocb, struct qemu_laiocb, iocb);
|
||||||
|
|
||||||
laiocb->ret = io_event_ret(&events[s->event_idx]);
|
laiocb->ret = io_event_ret(&s->events[s->event_idx]);
|
||||||
|
|
||||||
/* Change counters one-by-one because we can be nested. */
|
|
||||||
s->io_q.in_flight--;
|
|
||||||
s->event_idx++;
|
s->event_idx++;
|
||||||
qemu_laio_process_completion(laiocb);
|
|
||||||
}
|
qemu_laio_process_completion(s, laiocb);
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_bh_cancel(s->completion_bh);
|
|
||||||
|
|
||||||
/* If we are nested we have to notify the level above that we are done
|
|
||||||
* by setting event_max to zero, upper level will then jump out of it's
|
|
||||||
* own `for` loop. If we are the last all counters droped to zero. */
|
|
||||||
s->event_max = 0;
|
|
||||||
s->event_idx = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void qemu_laio_process_completions_and_submit(LinuxAioState *s)
|
|
||||||
{
|
|
||||||
qemu_laio_process_completions(s);
|
|
||||||
|
|
||||||
aio_context_acquire(s->aio_context);
|
|
||||||
if (!s->io_q.plugged && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
if (!s->io_q.plugged && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
||||||
ioq_submit(s);
|
ioq_submit(s);
|
||||||
}
|
}
|
||||||
aio_context_release(s->aio_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void qemu_laio_completion_bh(void *opaque)
|
|
||||||
{
|
|
||||||
LinuxAioState *s = opaque;
|
|
||||||
|
|
||||||
qemu_laio_process_completions_and_submit(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qemu_laio_completion_cb(EventNotifier *e)
|
static void qemu_laio_completion_cb(EventNotifier *e)
|
||||||
{
|
{
|
||||||
LinuxAioState *s = container_of(e, LinuxAioState, e);
|
struct qemu_laio_state *s = container_of(e, struct qemu_laio_state, e);
|
||||||
|
|
||||||
if (event_notifier_test_and_clear(&s->e)) {
|
if (event_notifier_test_and_clear(&s->e)) {
|
||||||
qemu_laio_process_completions_and_submit(s);
|
qemu_bh_schedule(s->completion_bh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool qemu_laio_poll_cb(void *opaque)
|
|
||||||
{
|
|
||||||
EventNotifier *e = opaque;
|
|
||||||
LinuxAioState *s = container_of(e, LinuxAioState, e);
|
|
||||||
struct io_event *events;
|
|
||||||
|
|
||||||
if (!io_getevents_peek(s->ctx, &events)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_laio_process_completions_and_submit(s);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void laio_cancel(BlockAIOCB *blockacb)
|
static void laio_cancel(BlockAIOCB *blockacb)
|
||||||
{
|
{
|
||||||
struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
|
struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb;
|
||||||
@@ -300,26 +180,22 @@ static void ioq_init(LaioQueue *io_q)
|
|||||||
{
|
{
|
||||||
QSIMPLEQ_INIT(&io_q->pending);
|
QSIMPLEQ_INIT(&io_q->pending);
|
||||||
io_q->plugged = 0;
|
io_q->plugged = 0;
|
||||||
io_q->in_queue = 0;
|
io_q->n = 0;
|
||||||
io_q->in_flight = 0;
|
|
||||||
io_q->blocked = false;
|
io_q->blocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ioq_submit(LinuxAioState *s)
|
static void ioq_submit(struct qemu_laio_state *s)
|
||||||
{
|
{
|
||||||
int ret, len;
|
int ret, len;
|
||||||
struct qemu_laiocb *aiocb;
|
struct qemu_laiocb *aiocb;
|
||||||
struct iocb *iocbs[MAX_EVENTS];
|
struct iocb *iocbs[MAX_QUEUED_IO];
|
||||||
QSIMPLEQ_HEAD(, qemu_laiocb) completed;
|
QSIMPLEQ_HEAD(, qemu_laiocb) completed;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (s->io_q.in_flight >= MAX_EVENTS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
len = 0;
|
len = 0;
|
||||||
QSIMPLEQ_FOREACH(aiocb, &s->io_q.pending, next) {
|
QSIMPLEQ_FOREACH(aiocb, &s->io_q.pending, next) {
|
||||||
iocbs[len++] = &aiocb->iocb;
|
iocbs[len++] = &aiocb->iocb;
|
||||||
if (s->io_q.in_flight + len >= MAX_EVENTS) {
|
if (len == MAX_QUEUED_IO) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,56 +205,55 @@ static void ioq_submit(LinuxAioState *s)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
/* Fail the first request, retry the rest */
|
abort();
|
||||||
aiocb = QSIMPLEQ_FIRST(&s->io_q.pending);
|
|
||||||
QSIMPLEQ_REMOVE_HEAD(&s->io_q.pending, next);
|
|
||||||
s->io_q.in_queue--;
|
|
||||||
aiocb->ret = ret;
|
|
||||||
qemu_laio_process_completion(aiocb);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s->io_q.in_flight += ret;
|
s->io_q.n -= ret;
|
||||||
s->io_q.in_queue -= ret;
|
|
||||||
aiocb = container_of(iocbs[ret - 1], struct qemu_laiocb, iocb);
|
aiocb = container_of(iocbs[ret - 1], struct qemu_laiocb, iocb);
|
||||||
QSIMPLEQ_SPLIT_AFTER(&s->io_q.pending, aiocb, next, &completed);
|
QSIMPLEQ_SPLIT_AFTER(&s->io_q.pending, aiocb, next, &completed);
|
||||||
} while (ret == len && !QSIMPLEQ_EMPTY(&s->io_q.pending));
|
} while (ret == len && !QSIMPLEQ_EMPTY(&s->io_q.pending));
|
||||||
s->io_q.blocked = (s->io_q.in_queue > 0);
|
s->io_q.blocked = (s->io_q.n > 0);
|
||||||
|
|
||||||
if (s->io_q.in_flight) {
|
|
||||||
/* We can try to complete something just right away if there are
|
|
||||||
* still requests in-flight. */
|
|
||||||
qemu_laio_process_completions(s);
|
|
||||||
/*
|
|
||||||
* Even we have completed everything (in_flight == 0), the queue can
|
|
||||||
* have still pended requests (in_queue > 0). We do not attempt to
|
|
||||||
* repeat submission to avoid IO hang. The reason is simple: s->e is
|
|
||||||
* still set and completion callback will be called shortly and all
|
|
||||||
* pended requests will be submitted from there.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void laio_io_plug(BlockDriverState *bs, LinuxAioState *s)
|
void laio_io_plug(BlockDriverState *bs, void *aio_ctx)
|
||||||
{
|
{
|
||||||
|
struct qemu_laio_state *s = aio_ctx;
|
||||||
|
|
||||||
s->io_q.plugged++;
|
s->io_q.plugged++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void laio_io_unplug(BlockDriverState *bs, LinuxAioState *s)
|
void laio_io_unplug(BlockDriverState *bs, void *aio_ctx, bool unplug)
|
||||||
{
|
{
|
||||||
assert(s->io_q.plugged);
|
struct qemu_laio_state *s = aio_ctx;
|
||||||
if (--s->io_q.plugged == 0 &&
|
|
||||||
!s->io_q.blocked && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
assert(s->io_q.plugged > 0 || !unplug);
|
||||||
|
|
||||||
|
if (unplug && --s->io_q.plugged > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s->io_q.blocked && !QSIMPLEQ_EMPTY(&s->io_q.pending)) {
|
||||||
ioq_submit(s);
|
ioq_submit(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset,
|
BlockAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd,
|
||||||
int type)
|
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
||||||
|
BlockCompletionFunc *cb, void *opaque, int type)
|
||||||
{
|
{
|
||||||
LinuxAioState *s = laiocb->ctx;
|
struct qemu_laio_state *s = aio_ctx;
|
||||||
struct iocb *iocbs = &laiocb->iocb;
|
struct qemu_laiocb *laiocb;
|
||||||
QEMUIOVector *qiov = laiocb->qiov;
|
struct iocb *iocbs;
|
||||||
|
off_t offset = sector_num * 512;
|
||||||
|
|
||||||
|
laiocb = qemu_aio_get(&laio_aiocb_info, bs, cb, opaque);
|
||||||
|
laiocb->nbytes = nb_sectors * 512;
|
||||||
|
laiocb->ctx = s;
|
||||||
|
laiocb->ret = -EINPROGRESS;
|
||||||
|
laiocb->is_read = (type == QEMU_AIO_READ);
|
||||||
|
laiocb->qiov = qiov;
|
||||||
|
|
||||||
|
iocbs = &laiocb->iocb;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case QEMU_AIO_WRITE:
|
case QEMU_AIO_WRITE:
|
||||||
@@ -391,88 +266,42 @@ static int laio_do_submit(int fd, struct qemu_laiocb *laiocb, off_t offset,
|
|||||||
default:
|
default:
|
||||||
fprintf(stderr, "%s: invalid AIO request type 0x%x.\n",
|
fprintf(stderr, "%s: invalid AIO request type 0x%x.\n",
|
||||||
__func__, type);
|
__func__, type);
|
||||||
return -EIO;
|
goto out_free_aiocb;
|
||||||
}
|
}
|
||||||
io_set_eventfd(&laiocb->iocb, event_notifier_get_fd(&s->e));
|
io_set_eventfd(&laiocb->iocb, event_notifier_get_fd(&s->e));
|
||||||
|
|
||||||
QSIMPLEQ_INSERT_TAIL(&s->io_q.pending, laiocb, next);
|
QSIMPLEQ_INSERT_TAIL(&s->io_q.pending, laiocb, next);
|
||||||
s->io_q.in_queue++;
|
s->io_q.n++;
|
||||||
if (!s->io_q.blocked &&
|
if (!s->io_q.blocked &&
|
||||||
(!s->io_q.plugged ||
|
(!s->io_q.plugged || s->io_q.n >= MAX_QUEUED_IO)) {
|
||||||
s->io_q.in_flight + s->io_q.in_queue >= MAX_EVENTS)) {
|
|
||||||
ioq_submit(s);
|
ioq_submit(s);
|
||||||
}
|
}
|
||||||
|
return &laiocb->common;
|
||||||
|
|
||||||
return 0;
|
out_free_aiocb:
|
||||||
}
|
|
||||||
|
|
||||||
int coroutine_fn laio_co_submit(BlockDriverState *bs, LinuxAioState *s, int fd,
|
|
||||||
uint64_t offset, QEMUIOVector *qiov, int type)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
struct qemu_laiocb laiocb = {
|
|
||||||
.co = qemu_coroutine_self(),
|
|
||||||
.nbytes = qiov->size,
|
|
||||||
.ctx = s,
|
|
||||||
.ret = -EINPROGRESS,
|
|
||||||
.is_read = (type == QEMU_AIO_READ),
|
|
||||||
.qiov = qiov,
|
|
||||||
};
|
|
||||||
|
|
||||||
ret = laio_do_submit(fd, &laiocb, offset, type);
|
|
||||||
if (ret < 0) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (laiocb.ret == -EINPROGRESS) {
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
|
||||||
return laiocb.ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockAIOCB *laio_submit(BlockDriverState *bs, LinuxAioState *s, int fd,
|
|
||||||
int64_t sector_num, QEMUIOVector *qiov, int nb_sectors,
|
|
||||||
BlockCompletionFunc *cb, void *opaque, int type)
|
|
||||||
{
|
|
||||||
struct qemu_laiocb *laiocb;
|
|
||||||
off_t offset = sector_num * BDRV_SECTOR_SIZE;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
laiocb = qemu_aio_get(&laio_aiocb_info, bs, cb, opaque);
|
|
||||||
laiocb->nbytes = nb_sectors * BDRV_SECTOR_SIZE;
|
|
||||||
laiocb->ctx = s;
|
|
||||||
laiocb->ret = -EINPROGRESS;
|
|
||||||
laiocb->is_read = (type == QEMU_AIO_READ);
|
|
||||||
laiocb->qiov = qiov;
|
|
||||||
|
|
||||||
ret = laio_do_submit(fd, laiocb, offset, type);
|
|
||||||
if (ret < 0) {
|
|
||||||
qemu_aio_unref(laiocb);
|
qemu_aio_unref(laiocb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
return &laiocb->common;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void laio_detach_aio_context(LinuxAioState *s, AioContext *old_context)
|
void laio_detach_aio_context(void *s_, AioContext *old_context)
|
||||||
{
|
{
|
||||||
aio_set_event_notifier(old_context, &s->e, false, NULL, NULL);
|
struct qemu_laio_state *s = s_;
|
||||||
|
|
||||||
|
aio_set_event_notifier(old_context, &s->e, NULL);
|
||||||
qemu_bh_delete(s->completion_bh);
|
qemu_bh_delete(s->completion_bh);
|
||||||
s->aio_context = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void laio_attach_aio_context(LinuxAioState *s, AioContext *new_context)
|
void laio_attach_aio_context(void *s_, AioContext *new_context)
|
||||||
{
|
{
|
||||||
s->aio_context = new_context;
|
struct qemu_laio_state *s = s_;
|
||||||
|
|
||||||
s->completion_bh = aio_bh_new(new_context, qemu_laio_completion_bh, s);
|
s->completion_bh = aio_bh_new(new_context, qemu_laio_completion_bh, s);
|
||||||
aio_set_event_notifier(new_context, &s->e, false,
|
aio_set_event_notifier(new_context, &s->e, qemu_laio_completion_cb);
|
||||||
qemu_laio_completion_cb,
|
|
||||||
qemu_laio_poll_cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxAioState *laio_init(void)
|
void *laio_init(void)
|
||||||
{
|
{
|
||||||
LinuxAioState *s;
|
struct qemu_laio_state *s;
|
||||||
|
|
||||||
s = g_malloc0(sizeof(*s));
|
s = g_malloc0(sizeof(*s));
|
||||||
if (event_notifier_init(&s->e, false) < 0) {
|
if (event_notifier_init(&s->e, false) < 0) {
|
||||||
@@ -494,8 +323,10 @@ out_free_state:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void laio_cleanup(LinuxAioState *s)
|
void laio_cleanup(void *s_)
|
||||||
{
|
{
|
||||||
|
struct qemu_laio_state *s = s_;
|
||||||
|
|
||||||
event_notifier_cleanup(&s->e);
|
event_notifier_cleanup(&s->e);
|
||||||
|
|
||||||
if (io_destroy(s->ctx) != 0) {
|
if (io_destroy(s->ctx) != 0) {
|
||||||
|
|||||||
1168
block/mirror.c
1168
block/mirror.c
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* QEMU Block driver for NBD
|
* QEMU Block driver for NBD
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016 Red Hat, Inc.
|
|
||||||
* Copyright (C) 2008 Bull S.A.S.
|
* Copyright (C) 2008 Bull S.A.S.
|
||||||
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
||||||
*
|
*
|
||||||
@@ -27,102 +26,91 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "nbd-client.h"
|
#include "nbd-client.h"
|
||||||
|
#include "qemu/sockets.h"
|
||||||
|
|
||||||
#define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs))
|
#define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs))
|
||||||
#define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs))
|
#define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs))
|
||||||
|
|
||||||
static void nbd_recv_coroutines_enter_all(NBDClientSession *s)
|
static void nbd_recv_coroutines_enter_all(NbdClientSession *s)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||||
if (s->recv_coroutine[i]) {
|
if (s->recv_coroutine[i]) {
|
||||||
aio_co_wake(s->recv_coroutine[i]);
|
qemu_coroutine_enter(s->recv_coroutine[i], NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_teardown_connection(BlockDriverState *bs)
|
static void nbd_teardown_connection(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
|
|
||||||
if (!client->ioc) { /* Already closed */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* finish any pending coroutines */
|
/* finish any pending coroutines */
|
||||||
qio_channel_shutdown(client->ioc,
|
shutdown(client->sock, 2);
|
||||||
QIO_CHANNEL_SHUTDOWN_BOTH,
|
nbd_recv_coroutines_enter_all(client);
|
||||||
NULL);
|
|
||||||
BDRV_POLL_WHILE(bs, client->read_reply_co);
|
|
||||||
|
|
||||||
nbd_client_detach_aio_context(bs);
|
nbd_client_detach_aio_context(bs);
|
||||||
object_unref(OBJECT(client->sioc));
|
closesocket(client->sock);
|
||||||
client->sioc = NULL;
|
client->sock = -1;
|
||||||
object_unref(OBJECT(client->ioc));
|
|
||||||
client->ioc = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn void nbd_read_reply_entry(void *opaque)
|
static void nbd_reply_ready(void *opaque)
|
||||||
{
|
{
|
||||||
NBDClientSession *s = opaque;
|
BlockDriverState *bs = opaque;
|
||||||
|
NbdClientSession *s = nbd_get_client_session(bs);
|
||||||
uint64_t i;
|
uint64_t i;
|
||||||
int ret;
|
int ret;
|
||||||
Error *local_err = NULL;
|
|
||||||
|
|
||||||
for (;;) {
|
if (s->reply.handle == 0) {
|
||||||
assert(s->reply.handle == 0);
|
/* No reply already in flight. Fetch a header. It is possible
|
||||||
ret = nbd_receive_reply(s->ioc, &s->reply, &local_err);
|
* that another thread has done the same thing in parallel, so
|
||||||
if (ret < 0) {
|
* the socket is not readable anymore.
|
||||||
error_report_err(local_err);
|
*/
|
||||||
|
ret = nbd_receive_reply(s->sock, &s->reply);
|
||||||
|
if (ret == -EAGAIN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
s->reply.handle = 0;
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
if (ret <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* There's no need for a mutex on the receive side, because the
|
/* There's no need for a mutex on the receive side, because the
|
||||||
* handler acts as a synchronization point and ensures that only
|
* handler acts as a synchronization point and ensures that only
|
||||||
* one coroutine is called until the reply finishes.
|
* one coroutine is called until the reply finishes. */
|
||||||
*/
|
|
||||||
i = HANDLE_TO_INDEX(s, s->reply.handle);
|
i = HANDLE_TO_INDEX(s, s->reply.handle);
|
||||||
if (i >= MAX_NBD_REQUESTS || !s->recv_coroutine[i]) {
|
if (i >= MAX_NBD_REQUESTS) {
|
||||||
break;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We're woken up by the recv_coroutine itself. Note that there
|
if (s->recv_coroutine[i]) {
|
||||||
* is no race between yielding and reentering read_reply_co. This
|
qemu_coroutine_enter(s->recv_coroutine[i], NULL);
|
||||||
* is because:
|
return;
|
||||||
*
|
|
||||||
* - if recv_coroutine[i] runs on the same AioContext, it is only
|
|
||||||
* entered after we yield
|
|
||||||
*
|
|
||||||
* - if recv_coroutine[i] runs on a different AioContext, reentering
|
|
||||||
* read_reply_co happens through a bottom half, which can only
|
|
||||||
* run after we yield.
|
|
||||||
*/
|
|
||||||
aio_co_wake(s->recv_coroutine[i]);
|
|
||||||
qemu_coroutine_yield();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nbd_recv_coroutines_enter_all(s);
|
fail:
|
||||||
s->read_reply_co = NULL;
|
nbd_teardown_connection(bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_restart_write(void *opaque)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs = opaque;
|
||||||
|
|
||||||
|
qemu_coroutine_enter(nbd_get_client_session(bs)->send_coroutine, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_co_send_request(BlockDriverState *bs,
|
static int nbd_co_send_request(BlockDriverState *bs,
|
||||||
NBDRequest *request,
|
struct nbd_request *request,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov, int offset)
|
||||||
{
|
{
|
||||||
NBDClientSession *s = nbd_get_client_session(bs);
|
NbdClientSession *s = nbd_get_client_session(bs);
|
||||||
|
AioContext *aio_context;
|
||||||
int rc, ret, i;
|
int rc, ret, i;
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->send_mutex);
|
qemu_co_mutex_lock(&s->send_mutex);
|
||||||
while (s->in_flight == MAX_NBD_REQUESTS) {
|
|
||||||
qemu_co_queue_wait(&s->free_sema, &s->send_mutex);
|
|
||||||
}
|
|
||||||
s->in_flight++;
|
|
||||||
|
|
||||||
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
for (i = 0; i < MAX_NBD_REQUESTS; i++) {
|
||||||
if (s->recv_coroutine[i] == NULL) {
|
if (s->recv_coroutine[i] == NULL) {
|
||||||
@@ -131,50 +119,53 @@ static int nbd_co_send_request(BlockDriverState *bs,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g_assert(qemu_in_coroutine());
|
|
||||||
assert(i < MAX_NBD_REQUESTS);
|
assert(i < MAX_NBD_REQUESTS);
|
||||||
request->handle = INDEX_TO_HANDLE(s, i);
|
request->handle = INDEX_TO_HANDLE(s, i);
|
||||||
|
s->send_coroutine = qemu_coroutine_self();
|
||||||
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
|
|
||||||
if (!s->ioc) {
|
aio_set_fd_handler(aio_context, s->sock,
|
||||||
qemu_co_mutex_unlock(&s->send_mutex);
|
nbd_reply_ready, nbd_restart_write, bs);
|
||||||
return -EPIPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qiov) {
|
if (qiov) {
|
||||||
qio_channel_set_cork(s->ioc, true);
|
if (!s->is_unix) {
|
||||||
rc = nbd_send_request(s->ioc, request);
|
socket_set_cork(s->sock, 1);
|
||||||
|
}
|
||||||
|
rc = nbd_send_request(s->sock, request);
|
||||||
if (rc >= 0) {
|
if (rc >= 0) {
|
||||||
ret = nbd_rwv(s->ioc, qiov->iov, qiov->niov, request->len, false,
|
ret = qemu_co_sendv(s->sock, qiov->iov, qiov->niov,
|
||||||
NULL);
|
offset, request->len);
|
||||||
if (ret != request->len) {
|
if (ret != request->len) {
|
||||||
rc = -EIO;
|
rc = -EIO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qio_channel_set_cork(s->ioc, false);
|
if (!s->is_unix) {
|
||||||
} else {
|
socket_set_cork(s->sock, 0);
|
||||||
rc = nbd_send_request(s->ioc, request);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
rc = nbd_send_request(s->sock, request);
|
||||||
|
}
|
||||||
|
aio_set_fd_handler(aio_context, s->sock, nbd_reply_ready, NULL, bs);
|
||||||
|
s->send_coroutine = NULL;
|
||||||
qemu_co_mutex_unlock(&s->send_mutex);
|
qemu_co_mutex_unlock(&s->send_mutex);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_co_receive_reply(NBDClientSession *s,
|
static void nbd_co_receive_reply(NbdClientSession *s,
|
||||||
NBDRequest *request,
|
struct nbd_request *request, struct nbd_reply *reply,
|
||||||
NBDReply *reply,
|
QEMUIOVector *qiov, int offset)
|
||||||
QEMUIOVector *qiov)
|
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Wait until we're woken up by nbd_read_reply_entry. */
|
/* Wait until we're woken up by the read handler. TODO: perhaps
|
||||||
|
* peek at the next reply and avoid yielding if it's ours? */
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
*reply = s->reply;
|
*reply = s->reply;
|
||||||
if (reply->handle != request->handle ||
|
if (reply->handle != request->handle) {
|
||||||
!s->ioc) {
|
|
||||||
reply->error = EIO;
|
reply->error = EIO;
|
||||||
} else {
|
} else {
|
||||||
if (qiov && reply->error == 0) {
|
if (qiov && reply->error == 0) {
|
||||||
ret = nbd_rwv(s->ioc, qiov->iov, qiov->niov, request->len, true,
|
ret = qemu_co_recvv(s->sock, qiov->iov, qiov->niov,
|
||||||
NULL);
|
offset, request->len);
|
||||||
if (ret != request->len) {
|
if (ret != request->len) {
|
||||||
reply->error = EIO;
|
reply->error = EIO;
|
||||||
}
|
}
|
||||||
@@ -185,236 +176,230 @@ static void nbd_co_receive_reply(NBDClientSession *s,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_coroutine_end(BlockDriverState *bs,
|
static void nbd_coroutine_start(NbdClientSession *s,
|
||||||
NBDRequest *request)
|
struct nbd_request *request)
|
||||||
|
{
|
||||||
|
/* Poor man semaphore. The free_sema is locked when no other request
|
||||||
|
* can be accepted, and unlocked after receiving one reply. */
|
||||||
|
if (s->in_flight >= MAX_NBD_REQUESTS - 1) {
|
||||||
|
qemu_co_mutex_lock(&s->free_sema);
|
||||||
|
assert(s->in_flight < MAX_NBD_REQUESTS);
|
||||||
|
}
|
||||||
|
s->in_flight++;
|
||||||
|
|
||||||
|
/* s->recv_coroutine[i] is set as soon as we get the send_lock. */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nbd_coroutine_end(NbdClientSession *s,
|
||||||
|
struct nbd_request *request)
|
||||||
{
|
{
|
||||||
NBDClientSession *s = nbd_get_client_session(bs);
|
|
||||||
int i = HANDLE_TO_INDEX(s, request->handle);
|
int i = HANDLE_TO_INDEX(s, request->handle);
|
||||||
|
|
||||||
s->recv_coroutine[i] = NULL;
|
s->recv_coroutine[i] = NULL;
|
||||||
|
if (s->in_flight-- == MAX_NBD_REQUESTS) {
|
||||||
/* Kick the read_reply_co to get the next reply. */
|
qemu_co_mutex_unlock(&s->free_sema);
|
||||||
if (s->read_reply_co) {
|
|
||||||
aio_co_wake(s->read_reply_co);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_co_mutex_lock(&s->send_mutex);
|
|
||||||
s->in_flight--;
|
|
||||||
qemu_co_queue_next(&s->free_sema);
|
|
||||||
qemu_co_mutex_unlock(&s->send_mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
static int nbd_co_readv_1(BlockDriverState *bs, int64_t sector_num,
|
||||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int offset)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
NBDRequest request = {
|
struct nbd_request request = { .type = NBD_CMD_READ };
|
||||||
.type = NBD_CMD_READ,
|
struct nbd_reply reply;
|
||||||
.from = offset,
|
|
||||||
.len = bytes,
|
|
||||||
};
|
|
||||||
NBDReply reply;
|
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
assert(bytes <= NBD_MAX_BUFFER_SIZE);
|
request.from = sector_num * 512;
|
||||||
assert(!flags);
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
ret = nbd_co_send_request(bs, &request, NULL);
|
nbd_coroutine_start(client, &request);
|
||||||
|
ret = nbd_co_send_request(bs, &request, NULL, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
reply.error = -ret;
|
reply.error = -ret;
|
||||||
} else {
|
} else {
|
||||||
nbd_co_receive_reply(client, &request, &reply, qiov);
|
nbd_co_receive_reply(client, &request, &reply, qiov, offset);
|
||||||
}
|
}
|
||||||
nbd_coroutine_end(bs, &request);
|
nbd_coroutine_end(client, &request);
|
||||||
return -reply.error;
|
return -reply.error;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
static int nbd_co_writev_1(BlockDriverState *bs, int64_t sector_num,
|
||||||
uint64_t bytes, QEMUIOVector *qiov, int flags)
|
int nb_sectors, QEMUIOVector *qiov,
|
||||||
|
int offset)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
NBDRequest request = {
|
struct nbd_request request = { .type = NBD_CMD_WRITE };
|
||||||
.type = NBD_CMD_WRITE,
|
struct nbd_reply reply;
|
||||||
.from = offset,
|
|
||||||
.len = bytes,
|
|
||||||
};
|
|
||||||
NBDReply reply;
|
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
if (flags & BDRV_REQ_FUA) {
|
if (!bdrv_enable_write_cache(bs) &&
|
||||||
assert(client->nbdflags & NBD_FLAG_SEND_FUA);
|
(client->nbdflags & NBD_FLAG_SEND_FUA)) {
|
||||||
request.flags |= NBD_CMD_FLAG_FUA;
|
request.type |= NBD_CMD_FLAG_FUA;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(bytes <= NBD_MAX_BUFFER_SIZE);
|
request.from = sector_num * 512;
|
||||||
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
ret = nbd_co_send_request(bs, &request, qiov);
|
nbd_coroutine_start(client, &request);
|
||||||
|
ret = nbd_co_send_request(bs, &request, qiov, offset);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
reply.error = -ret;
|
reply.error = -ret;
|
||||||
} else {
|
} else {
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
||||||
}
|
}
|
||||||
nbd_coroutine_end(bs, &request);
|
nbd_coroutine_end(client, &request);
|
||||||
return -reply.error;
|
return -reply.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
/* qemu-nbd has a limit of slightly less than 1M per request. Try to
|
||||||
int count, BdrvRequestFlags flags)
|
* remain aligned to 4K. */
|
||||||
|
#define NBD_MAX_SECTORS 2040
|
||||||
|
|
||||||
|
int nbd_client_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
ssize_t ret;
|
int offset = 0;
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
int ret;
|
||||||
NBDRequest request = {
|
while (nb_sectors > NBD_MAX_SECTORS) {
|
||||||
.type = NBD_CMD_WRITE_ZEROES,
|
ret = nbd_co_readv_1(bs, sector_num, NBD_MAX_SECTORS, qiov, offset);
|
||||||
.from = offset,
|
|
||||||
.len = count,
|
|
||||||
};
|
|
||||||
NBDReply reply;
|
|
||||||
|
|
||||||
if (!(client->nbdflags & NBD_FLAG_SEND_WRITE_ZEROES)) {
|
|
||||||
return -ENOTSUP;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & BDRV_REQ_FUA) {
|
|
||||||
assert(client->nbdflags & NBD_FLAG_SEND_FUA);
|
|
||||||
request.flags |= NBD_CMD_FLAG_FUA;
|
|
||||||
}
|
|
||||||
if (!(flags & BDRV_REQ_MAY_UNMAP)) {
|
|
||||||
request.flags |= NBD_CMD_FLAG_NO_HOLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = nbd_co_send_request(bs, &request, NULL);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
reply.error = -ret;
|
return ret;
|
||||||
} else {
|
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
|
||||||
}
|
}
|
||||||
nbd_coroutine_end(bs, &request);
|
offset += NBD_MAX_SECTORS * 512;
|
||||||
return -reply.error;
|
sector_num += NBD_MAX_SECTORS;
|
||||||
|
nb_sectors -= NBD_MAX_SECTORS;
|
||||||
|
}
|
||||||
|
return nbd_co_readv_1(bs, sector_num, nb_sectors, qiov, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nbd_client_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
int ret;
|
||||||
|
while (nb_sectors > NBD_MAX_SECTORS) {
|
||||||
|
ret = nbd_co_writev_1(bs, sector_num, NBD_MAX_SECTORS, qiov, offset);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
offset += NBD_MAX_SECTORS * 512;
|
||||||
|
sector_num += NBD_MAX_SECTORS;
|
||||||
|
nb_sectors -= NBD_MAX_SECTORS;
|
||||||
|
}
|
||||||
|
return nbd_co_writev_1(bs, sector_num, nb_sectors, qiov, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_co_flush(BlockDriverState *bs)
|
int nbd_client_co_flush(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
NBDRequest request = { .type = NBD_CMD_FLUSH };
|
struct nbd_request request = { .type = NBD_CMD_FLUSH };
|
||||||
NBDReply reply;
|
struct nbd_reply reply;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) {
|
if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client->nbdflags & NBD_FLAG_SEND_FUA) {
|
||||||
|
request.type |= NBD_CMD_FLAG_FUA;
|
||||||
|
}
|
||||||
|
|
||||||
request.from = 0;
|
request.from = 0;
|
||||||
request.len = 0;
|
request.len = 0;
|
||||||
|
|
||||||
ret = nbd_co_send_request(bs, &request, NULL);
|
nbd_coroutine_start(client, &request);
|
||||||
|
ret = nbd_co_send_request(bs, &request, NULL, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
reply.error = -ret;
|
reply.error = -ret;
|
||||||
} else {
|
} else {
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
||||||
}
|
}
|
||||||
nbd_coroutine_end(bs, &request);
|
nbd_coroutine_end(client, &request);
|
||||||
return -reply.error;
|
return -reply.error;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count)
|
int nbd_client_co_discard(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
NBDRequest request = {
|
struct nbd_request request = { .type = NBD_CMD_TRIM };
|
||||||
.type = NBD_CMD_TRIM,
|
struct nbd_reply reply;
|
||||||
.from = offset,
|
|
||||||
.len = count,
|
|
||||||
};
|
|
||||||
NBDReply reply;
|
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) {
|
if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
request.from = sector_num * 512;
|
||||||
|
request.len = nb_sectors * 512;
|
||||||
|
|
||||||
ret = nbd_co_send_request(bs, &request, NULL);
|
nbd_coroutine_start(client, &request);
|
||||||
|
ret = nbd_co_send_request(bs, &request, NULL, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
reply.error = -ret;
|
reply.error = -ret;
|
||||||
} else {
|
} else {
|
||||||
nbd_co_receive_reply(client, &request, &reply, NULL);
|
nbd_co_receive_reply(client, &request, &reply, NULL, 0);
|
||||||
}
|
}
|
||||||
nbd_coroutine_end(bs, &request);
|
nbd_coroutine_end(client, &request);
|
||||||
return -reply.error;
|
return -reply.error;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nbd_client_detach_aio_context(BlockDriverState *bs)
|
void nbd_client_detach_aio_context(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
aio_set_fd_handler(bdrv_get_aio_context(bs),
|
||||||
qio_channel_detach_aio_context(QIO_CHANNEL(client->sioc));
|
nbd_get_client_session(bs)->sock, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
||||||
AioContext *new_context)
|
AioContext *new_context)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
aio_set_fd_handler(new_context, nbd_get_client_session(bs)->sock,
|
||||||
qio_channel_attach_aio_context(QIO_CHANNEL(client->sioc), new_context);
|
nbd_reply_ready, NULL, bs);
|
||||||
aio_co_schedule(new_context, client->read_reply_co);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nbd_client_close(BlockDriverState *bs)
|
void nbd_client_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
NBDRequest request = { .type = NBD_CMD_DISC };
|
struct nbd_request request = {
|
||||||
|
.type = NBD_CMD_DISC,
|
||||||
|
.from = 0,
|
||||||
|
.len = 0
|
||||||
|
};
|
||||||
|
|
||||||
if (client->ioc == NULL) {
|
if (client->sock == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nbd_send_request(client->ioc, &request);
|
nbd_send_request(client->sock, &request);
|
||||||
|
|
||||||
nbd_teardown_connection(bs);
|
nbd_teardown_connection(bs);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nbd_client_init(BlockDriverState *bs,
|
int nbd_client_init(BlockDriverState *bs, int sock, const char *export,
|
||||||
QIOChannelSocket *sioc,
|
|
||||||
const char *export,
|
|
||||||
QCryptoTLSCreds *tlscreds,
|
|
||||||
const char *hostname,
|
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NbdClientSession *client = nbd_get_client_session(bs);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* NBD handshake */
|
/* NBD handshake */
|
||||||
logout("session init %s\n", export);
|
logout("session init %s\n", export);
|
||||||
qio_channel_set_blocking(QIO_CHANNEL(sioc), true, NULL);
|
qemu_set_block(sock);
|
||||||
|
ret = nbd_receive_negotiate(sock, export,
|
||||||
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
|
&client->nbdflags, &client->size, errp);
|
||||||
&client->nbdflags,
|
|
||||||
tlscreds, hostname,
|
|
||||||
&client->ioc,
|
|
||||||
&client->size, errp);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
logout("Failed to negotiate with the NBD server\n");
|
logout("Failed to negotiate with the NBD server\n");
|
||||||
|
closesocket(sock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (client->nbdflags & NBD_FLAG_SEND_FUA) {
|
|
||||||
bs->supported_write_flags = BDRV_REQ_FUA;
|
|
||||||
bs->supported_zero_flags |= BDRV_REQ_FUA;
|
|
||||||
}
|
|
||||||
if (client->nbdflags & NBD_FLAG_SEND_WRITE_ZEROES) {
|
|
||||||
bs->supported_zero_flags |= BDRV_REQ_MAY_UNMAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_co_mutex_init(&client->send_mutex);
|
qemu_co_mutex_init(&client->send_mutex);
|
||||||
qemu_co_queue_init(&client->free_sema);
|
qemu_co_mutex_init(&client->free_sema);
|
||||||
client->sioc = sioc;
|
client->sock = sock;
|
||||||
object_ref(OBJECT(client->sioc));
|
|
||||||
|
|
||||||
if (!client->ioc) {
|
|
||||||
client->ioc = QIO_CHANNEL(sioc);
|
|
||||||
object_ref(OBJECT(client->ioc));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now that we're connected, set the socket to be non-blocking and
|
/* Now that we're connected, set the socket to be non-blocking and
|
||||||
* kick the reply mechanism. */
|
* kick the reply mechanism. */
|
||||||
qio_channel_set_blocking(QIO_CHANNEL(sioc), false, NULL);
|
qemu_set_nonblock(sock);
|
||||||
client->read_reply_co = qemu_coroutine_create(nbd_read_reply_entry, client);
|
|
||||||
nbd_client_attach_aio_context(bs, bdrv_get_aio_context(bs));
|
nbd_client_attach_aio_context(bs, bdrv_get_aio_context(bs));
|
||||||
|
|
||||||
logout("Established connection with NBD server\n");
|
logout("Established connection with NBD server\n");
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "block/nbd.h"
|
#include "block/nbd.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "io/channel-socket.h"
|
|
||||||
|
|
||||||
/* #define DEBUG_NBD */
|
/* #define DEBUG_NBD */
|
||||||
|
|
||||||
@@ -17,39 +16,35 @@
|
|||||||
|
|
||||||
#define MAX_NBD_REQUESTS 16
|
#define MAX_NBD_REQUESTS 16
|
||||||
|
|
||||||
typedef struct NBDClientSession {
|
typedef struct NbdClientSession {
|
||||||
QIOChannelSocket *sioc; /* The master data channel */
|
int sock;
|
||||||
QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
|
uint32_t nbdflags;
|
||||||
uint16_t nbdflags;
|
|
||||||
off_t size;
|
off_t size;
|
||||||
|
|
||||||
CoMutex send_mutex;
|
CoMutex send_mutex;
|
||||||
CoQueue free_sema;
|
CoMutex free_sema;
|
||||||
Coroutine *read_reply_co;
|
Coroutine *send_coroutine;
|
||||||
int in_flight;
|
int in_flight;
|
||||||
|
|
||||||
Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
|
Coroutine *recv_coroutine[MAX_NBD_REQUESTS];
|
||||||
NBDReply reply;
|
struct nbd_reply reply;
|
||||||
} NBDClientSession;
|
|
||||||
|
|
||||||
NBDClientSession *nbd_get_client_session(BlockDriverState *bs);
|
bool is_unix;
|
||||||
|
} NbdClientSession;
|
||||||
|
|
||||||
int nbd_client_init(BlockDriverState *bs,
|
NbdClientSession *nbd_get_client_session(BlockDriverState *bs);
|
||||||
QIOChannelSocket *sock,
|
|
||||||
const char *export_name,
|
int nbd_client_init(BlockDriverState *bs, int sock, const char *export_name,
|
||||||
QCryptoTLSCreds *tlscreds,
|
|
||||||
const char *hostname,
|
|
||||||
Error **errp);
|
Error **errp);
|
||||||
void nbd_client_close(BlockDriverState *bs);
|
void nbd_client_close(BlockDriverState *bs);
|
||||||
|
|
||||||
int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count);
|
int nbd_client_co_discard(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors);
|
||||||
int nbd_client_co_flush(BlockDriverState *bs);
|
int nbd_client_co_flush(BlockDriverState *bs);
|
||||||
int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
int nbd_client_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
int nb_sectors, QEMUIOVector *qiov);
|
||||||
int nbd_client_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
|
int nbd_client_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||||
int count, BdrvRequestFlags flags);
|
int nb_sectors, QEMUIOVector *qiov);
|
||||||
int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset,
|
|
||||||
uint64_t bytes, QEMUIOVector *qiov, int flags);
|
|
||||||
|
|
||||||
void nbd_client_detach_aio_context(BlockDriverState *bs);
|
void nbd_client_detach_aio_context(BlockDriverState *bs);
|
||||||
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
||||||
|
|||||||
450
block/nbd.c
450
block/nbd.c
@@ -26,28 +26,24 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "block/nbd-client.h"
|
#include "block/nbd-client.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qemu/uri.h"
|
#include "qemu/uri.h"
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "qemu/module.h"
|
#include "qemu/module.h"
|
||||||
#include "qapi-visit.h"
|
#include "qemu/sockets.h"
|
||||||
#include "qapi/qobject-input-visitor.h"
|
|
||||||
#include "qapi/qobject-output-visitor.h"
|
|
||||||
#include "qapi/qmp/qdict.h"
|
#include "qapi/qmp/qdict.h"
|
||||||
#include "qapi/qmp/qjson.h"
|
#include "qapi/qmp/qjson.h"
|
||||||
|
#include "qapi/qmp/qint.h"
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#define EN_OPTSTR ":exportname="
|
#define EN_OPTSTR ":exportname="
|
||||||
|
|
||||||
typedef struct BDRVNBDState {
|
typedef struct BDRVNBDState {
|
||||||
NBDClientSession client;
|
NbdClientSession client;
|
||||||
|
QemuOpts *socket_opts;
|
||||||
/* For nbd_refresh_filename() */
|
|
||||||
SocketAddress *saddr;
|
|
||||||
char *export, *tlscredsid;
|
|
||||||
} BDRVNBDState;
|
} BDRVNBDState;
|
||||||
|
|
||||||
static int nbd_parse_uri(const char *filename, QDict *options)
|
static int nbd_parse_uri(const char *filename, QDict *options)
|
||||||
@@ -78,7 +74,7 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||||||
p = uri->path ? uri->path : "/";
|
p = uri->path ? uri->path : "/";
|
||||||
p += strspn(p, "/");
|
p += strspn(p, "/");
|
||||||
if (p[0]) {
|
if (p[0]) {
|
||||||
qdict_put_str(options, "export", p);
|
qdict_put(options, "export", qstring_from_str(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
qp = query_params_parse(uri->query);
|
qp = query_params_parse(uri->query);
|
||||||
@@ -93,12 +89,9 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
qdict_put_str(options, "server.type", "unix");
|
qdict_put(options, "path", qstring_from_str(qp->p[0].value));
|
||||||
qdict_put_str(options, "server.path", qp->p[0].value);
|
|
||||||
} else {
|
} else {
|
||||||
QString *host;
|
QString *host;
|
||||||
char *port_str;
|
|
||||||
|
|
||||||
/* nbd[+tcp]://host[:port]/export */
|
/* nbd[+tcp]://host[:port]/export */
|
||||||
if (!uri->server) {
|
if (!uri->server) {
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
@@ -113,13 +106,13 @@ static int nbd_parse_uri(const char *filename, QDict *options)
|
|||||||
host = qstring_from_str(uri->server);
|
host = qstring_from_str(uri->server);
|
||||||
}
|
}
|
||||||
|
|
||||||
qdict_put_str(options, "server.type", "inet");
|
qdict_put(options, "host", host);
|
||||||
qdict_put(options, "server.host", host);
|
if (uri->port) {
|
||||||
|
char* port_str = g_strdup_printf("%d", uri->port);
|
||||||
port_str = g_strdup_printf("%d", uri->port ?: NBD_DEFAULT_PORT);
|
qdict_put(options, "port", qstring_from_str(port_str));
|
||||||
qdict_put_str(options, "server.port", port_str);
|
|
||||||
g_free(port_str);
|
g_free(port_str);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (qp) {
|
if (qp) {
|
||||||
@@ -129,26 +122,6 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool nbd_has_filename_options_conflict(QDict *options, Error **errp)
|
|
||||||
{
|
|
||||||
const QDictEntry *e;
|
|
||||||
|
|
||||||
for (e = qdict_first(options); e; e = qdict_next(options, e)) {
|
|
||||||
if (!strcmp(e->key, "host") ||
|
|
||||||
!strcmp(e->key, "port") ||
|
|
||||||
!strcmp(e->key, "path") ||
|
|
||||||
!strcmp(e->key, "export") ||
|
|
||||||
strstart(e->key, "server.", NULL))
|
|
||||||
{
|
|
||||||
error_setg(errp, "Option '%s' cannot be used with a file name",
|
|
||||||
e->key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nbd_parse_filename(const char *filename, QDict *options,
|
static void nbd_parse_filename(const char *filename, QDict *options,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
@@ -157,7 +130,12 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
|||||||
const char *host_spec;
|
const char *host_spec;
|
||||||
const char *unixpath;
|
const char *unixpath;
|
||||||
|
|
||||||
if (nbd_has_filename_options_conflict(options, errp)) {
|
if (qdict_haskey(options, "host")
|
||||||
|
|| qdict_haskey(options, "port")
|
||||||
|
|| qdict_haskey(options, "path"))
|
||||||
|
{
|
||||||
|
error_setg(errp, "host/port/path and a file name may not be specified "
|
||||||
|
"at the same time");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +157,7 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
|||||||
export_name[0] = 0; /* truncate 'file' */
|
export_name[0] = 0; /* truncate 'file' */
|
||||||
export_name += strlen(EN_OPTSTR);
|
export_name += strlen(EN_OPTSTR);
|
||||||
|
|
||||||
qdict_put_str(options, "export", export_name);
|
qdict_put(options, "export", qstring_from_str(export_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* extract the host_spec - fail if it's not nbd:... */
|
/* extract the host_spec - fail if it's not nbd:... */
|
||||||
@@ -194,19 +172,17 @@ static void nbd_parse_filename(const char *filename, QDict *options,
|
|||||||
|
|
||||||
/* are we a UNIX or TCP socket? */
|
/* are we a UNIX or TCP socket? */
|
||||||
if (strstart(host_spec, "unix:", &unixpath)) {
|
if (strstart(host_spec, "unix:", &unixpath)) {
|
||||||
qdict_put_str(options, "server.type", "unix");
|
qdict_put(options, "path", qstring_from_str(unixpath));
|
||||||
qdict_put_str(options, "server.path", unixpath);
|
|
||||||
} else {
|
} else {
|
||||||
InetSocketAddress *addr = g_new(InetSocketAddress, 1);
|
InetSocketAddress *addr = NULL;
|
||||||
|
|
||||||
if (inet_parse(addr, host_spec, errp)) {
|
addr = inet_parse(host_spec, errp);
|
||||||
goto out_inet;
|
if (!addr) {
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
qdict_put_str(options, "server.type", "inet");
|
qdict_put(options, "host", qstring_from_str(addr->host));
|
||||||
qdict_put_str(options, "server.host", addr->host);
|
qdict_put(options, "port", qstring_from_str(addr->port));
|
||||||
qdict_put_str(options, "server.port", addr->port);
|
|
||||||
out_inet:
|
|
||||||
qapi_free_InetSocketAddress(addr);
|
qapi_free_InetSocketAddress(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,255 +190,110 @@ out:
|
|||||||
g_free(file);
|
g_free(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool nbd_process_legacy_socket_options(QDict *output_options,
|
static void nbd_config(BDRVNBDState *s, QDict *options, char **export,
|
||||||
QemuOpts *legacy_opts,
|
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
const char *path = qemu_opt_get(legacy_opts, "path");
|
|
||||||
const char *host = qemu_opt_get(legacy_opts, "host");
|
|
||||||
const char *port = qemu_opt_get(legacy_opts, "port");
|
|
||||||
const QDictEntry *e;
|
|
||||||
|
|
||||||
if (!path && !host && !port) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (e = qdict_first(output_options); e; e = qdict_next(output_options, e))
|
|
||||||
{
|
|
||||||
if (strstart(e->key, "server.", NULL)) {
|
|
||||||
error_setg(errp, "Cannot use 'server' and path/host/port at the "
|
|
||||||
"same time");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path && host) {
|
|
||||||
error_setg(errp, "path and host may not be used at the same time");
|
|
||||||
return false;
|
|
||||||
} else if (path) {
|
|
||||||
if (port) {
|
|
||||||
error_setg(errp, "port may not be used without host");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qdict_put_str(output_options, "server.type", "unix");
|
|
||||||
qdict_put_str(output_options, "server.path", path);
|
|
||||||
} else if (host) {
|
|
||||||
qdict_put_str(output_options, "server.type", "inet");
|
|
||||||
qdict_put_str(output_options, "server.host", host);
|
|
||||||
qdict_put_str(output_options, "server.port",
|
|
||||||
port ?: stringify(NBD_DEFAULT_PORT));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SocketAddress *nbd_config(BDRVNBDState *s, QDict *options,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
SocketAddress *saddr = NULL;
|
|
||||||
QDict *addr = NULL;
|
|
||||||
QObject *crumpled_addr = NULL;
|
|
||||||
Visitor *iv = NULL;
|
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
|
||||||
qdict_extract_subqdict(options, &addr, "server.");
|
if (qdict_haskey(options, "path") == qdict_haskey(options, "host")) {
|
||||||
if (!qdict_size(addr)) {
|
if (qdict_haskey(options, "path")) {
|
||||||
error_setg(errp, "NBD server address missing");
|
error_setg(errp, "path and host may not be used at the same time.");
|
||||||
goto done;
|
} else {
|
||||||
|
error_setg(errp, "one of path and host must be specified.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
crumpled_addr = qdict_crumple(addr, errp);
|
s->client.is_unix = qdict_haskey(options, "path");
|
||||||
if (!crumpled_addr) {
|
s->socket_opts = qemu_opts_create(&socket_optslist, NULL, 0,
|
||||||
goto done;
|
&error_abort);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
qemu_opts_absorb_qdict(s->socket_opts, options, &local_err);
|
||||||
* FIXME .numeric, .to, .ipv4 or .ipv6 don't work with -drive
|
|
||||||
* server.type=inet. .to doesn't matter, it's ignored anyway.
|
|
||||||
* That's because when @options come from -blockdev or
|
|
||||||
* blockdev_add, members are typed according to the QAPI schema,
|
|
||||||
* but when they come from -drive, they're all QString. The
|
|
||||||
* visitor expects the former.
|
|
||||||
*/
|
|
||||||
iv = qobject_input_visitor_new(crumpled_addr);
|
|
||||||
visit_type_SocketAddress(iv, NULL, &saddr, &local_err);
|
|
||||||
if (local_err) {
|
if (local_err) {
|
||||||
error_propagate(errp, local_err);
|
error_propagate(errp, local_err);
|
||||||
goto done;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
done:
|
if (!qemu_opt_get(s->socket_opts, "port")) {
|
||||||
QDECREF(addr);
|
qemu_opt_set_number(s->socket_opts, "port", NBD_DEFAULT_PORT,
|
||||||
qobject_decref(crumpled_addr);
|
&error_abort);
|
||||||
visit_free(iv);
|
}
|
||||||
return saddr;
|
|
||||||
|
*export = g_strdup(qdict_get_try_str(options, "export"));
|
||||||
|
if (*export) {
|
||||||
|
qdict_del(options, "export");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NBDClientSession *nbd_get_client_session(BlockDriverState *bs)
|
NbdClientSession *nbd_get_client_session(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
return &s->client;
|
return &s->client;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QIOChannelSocket *nbd_establish_connection(SocketAddress *saddr,
|
static int nbd_establish_connection(BlockDriverState *bs, Error **errp)
|
||||||
Error **errp)
|
|
||||||
{
|
{
|
||||||
QIOChannelSocket *sioc;
|
BDRVNBDState *s = bs->opaque;
|
||||||
Error *local_err = NULL;
|
int sock;
|
||||||
|
|
||||||
sioc = qio_channel_socket_new();
|
if (s->client.is_unix) {
|
||||||
qio_channel_set_name(QIO_CHANNEL(sioc), "nbd-client");
|
sock = unix_connect_opts(s->socket_opts, errp, NULL, NULL);
|
||||||
|
} else {
|
||||||
qio_channel_socket_connect_sync(sioc,
|
sock = inet_connect_opts(s->socket_opts, errp, NULL, NULL);
|
||||||
saddr,
|
if (sock >= 0) {
|
||||||
&local_err);
|
socket_set_nodelay(sock);
|
||||||
if (local_err) {
|
}
|
||||||
object_unref(OBJECT(sioc));
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qio_channel_set_delay(QIO_CHANNEL(sioc), false);
|
/* Failed to establish connection */
|
||||||
|
if (sock < 0) {
|
||||||
|
logout("Failed to establish connection to NBD server\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
return sioc;
|
return sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
|
|
||||||
{
|
|
||||||
Object *obj;
|
|
||||||
QCryptoTLSCreds *creds;
|
|
||||||
|
|
||||||
obj = object_resolve_path_component(
|
|
||||||
object_get_objects_root(), id);
|
|
||||||
if (!obj) {
|
|
||||||
error_setg(errp, "No TLS credentials with id '%s'",
|
|
||||||
id);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
creds = (QCryptoTLSCreds *)
|
|
||||||
object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS);
|
|
||||||
if (!creds) {
|
|
||||||
error_setg(errp, "Object with id '%s' is not TLS credentials",
|
|
||||||
id);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
|
|
||||||
error_setg(errp,
|
|
||||||
"Expecting TLS credentials with a client endpoint");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
object_ref(obj);
|
|
||||||
return creds;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static QemuOptsList nbd_runtime_opts = {
|
|
||||||
.name = "nbd",
|
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(nbd_runtime_opts.head),
|
|
||||||
.desc = {
|
|
||||||
{
|
|
||||||
.name = "host",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "TCP host to connect to",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "port",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "TCP port to connect to",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "path",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Unix socket path to connect to",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "export",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "Name of the NBD export to open",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "tls-creds",
|
|
||||||
.type = QEMU_OPT_STRING,
|
|
||||||
.help = "ID of the TLS credentials to use",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
static int nbd_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
QemuOpts *opts = NULL;
|
char *export = NULL;
|
||||||
|
int result, sock;
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
QIOChannelSocket *sioc = NULL;
|
|
||||||
QCryptoTLSCreds *tlscreds = NULL;
|
|
||||||
const char *hostname = NULL;
|
|
||||||
int ret = -EINVAL;
|
|
||||||
|
|
||||||
opts = qemu_opts_create(&nbd_runtime_opts, NULL, 0, &error_abort);
|
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
|
||||||
if (local_err) {
|
|
||||||
error_propagate(errp, local_err);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Translate @host, @port, and @path to a SocketAddress */
|
|
||||||
if (!nbd_process_legacy_socket_options(options, opts, errp)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pop the config into our state object. Exit if invalid. */
|
/* Pop the config into our state object. Exit if invalid. */
|
||||||
s->saddr = nbd_config(s, options, errp);
|
nbd_config(s, options, &export, &local_err);
|
||||||
if (!s->saddr) {
|
if (local_err) {
|
||||||
goto error;
|
error_propagate(errp, local_err);
|
||||||
}
|
return -EINVAL;
|
||||||
|
|
||||||
s->export = g_strdup(qemu_opt_get(opts, "export"));
|
|
||||||
|
|
||||||
s->tlscredsid = g_strdup(qemu_opt_get(opts, "tls-creds"));
|
|
||||||
if (s->tlscredsid) {
|
|
||||||
tlscreds = nbd_get_tls_creds(s->tlscredsid, errp);
|
|
||||||
if (!tlscreds) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO SOCKET_ADDRESS_KIND_FD where fd has AF_INET or AF_INET6 */
|
|
||||||
if (s->saddr->type != SOCKET_ADDRESS_TYPE_INET) {
|
|
||||||
error_setg(errp, "TLS only supported over IP sockets");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
hostname = s->saddr->u.inet.host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* establish TCP connection, return error if it fails
|
/* establish TCP connection, return error if it fails
|
||||||
* TODO: Configurable retry-until-timeout behaviour.
|
* TODO: Configurable retry-until-timeout behaviour.
|
||||||
*/
|
*/
|
||||||
sioc = nbd_establish_connection(s->saddr, errp);
|
sock = nbd_establish_connection(bs, errp);
|
||||||
if (!sioc) {
|
if (sock < 0) {
|
||||||
ret = -ECONNREFUSED;
|
g_free(export);
|
||||||
goto error;
|
return sock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NBD handshake */
|
/* NBD handshake */
|
||||||
ret = nbd_client_init(bs, sioc, s->export,
|
result = nbd_client_init(bs, sock, export, errp);
|
||||||
tlscreds, hostname, errp);
|
g_free(export);
|
||||||
error:
|
return result;
|
||||||
if (sioc) {
|
}
|
||||||
object_unref(OBJECT(sioc));
|
|
||||||
}
|
static int nbd_co_readv(BlockDriverState *bs, int64_t sector_num,
|
||||||
if (tlscreds) {
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
object_unref(OBJECT(tlscreds));
|
{
|
||||||
}
|
return nbd_client_co_readv(bs, sector_num, nb_sectors, qiov);
|
||||||
if (ret < 0) {
|
}
|
||||||
qapi_free_SocketAddress(s->saddr);
|
|
||||||
g_free(s->export);
|
static int nbd_co_writev(BlockDriverState *bs, int64_t sector_num,
|
||||||
g_free(s->tlscredsid);
|
int nb_sectors, QEMUIOVector *qiov)
|
||||||
}
|
{
|
||||||
qemu_opts_del(opts);
|
return nbd_client_co_writev(bs, sector_num, nb_sectors, qiov);
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nbd_co_flush(BlockDriverState *bs)
|
static int nbd_co_flush(BlockDriverState *bs)
|
||||||
@@ -472,20 +303,22 @@ static int nbd_co_flush(BlockDriverState *bs)
|
|||||||
|
|
||||||
static void nbd_refresh_limits(BlockDriverState *bs, Error **errp)
|
static void nbd_refresh_limits(BlockDriverState *bs, Error **errp)
|
||||||
{
|
{
|
||||||
bs->bl.max_pdiscard = NBD_MAX_BUFFER_SIZE;
|
bs->bl.max_discard = UINT32_MAX >> BDRV_SECTOR_BITS;
|
||||||
bs->bl.max_pwrite_zeroes = NBD_MAX_BUFFER_SIZE;
|
bs->bl.max_transfer_length = UINT32_MAX >> BDRV_SECTOR_BITS;
|
||||||
bs->bl.max_transfer = NBD_MAX_BUFFER_SIZE;
|
}
|
||||||
|
|
||||||
|
static int nbd_co_discard(BlockDriverState *bs, int64_t sector_num,
|
||||||
|
int nb_sectors)
|
||||||
|
{
|
||||||
|
return nbd_client_co_discard(bs, sector_num, nb_sectors);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_close(BlockDriverState *bs)
|
static void nbd_close(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
BDRVNBDState *s = bs->opaque;
|
||||||
|
|
||||||
|
qemu_opts_del(s->socket_opts);
|
||||||
nbd_client_close(bs);
|
nbd_client_close(bs);
|
||||||
|
|
||||||
qapi_free_SocketAddress(s->saddr);
|
|
||||||
g_free(s->export);
|
|
||||||
g_free(s->tlscredsid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int64_t nbd_getlength(BlockDriverState *bs)
|
static int64_t nbd_getlength(BlockDriverState *bs)
|
||||||
@@ -506,54 +339,48 @@ static void nbd_attach_aio_context(BlockDriverState *bs,
|
|||||||
nbd_client_attach_aio_context(bs, new_context);
|
nbd_client_attach_aio_context(bs, new_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nbd_refresh_filename(BlockDriverState *bs, QDict *options)
|
static void nbd_refresh_filename(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
BDRVNBDState *s = bs->opaque;
|
|
||||||
QDict *opts = qdict_new();
|
QDict *opts = qdict_new();
|
||||||
QObject *saddr_qdict;
|
const char *path = qdict_get_try_str(bs->options, "path");
|
||||||
Visitor *ov;
|
const char *host = qdict_get_try_str(bs->options, "host");
|
||||||
const char *host = NULL, *port = NULL, *path = NULL;
|
const char *port = qdict_get_try_str(bs->options, "port");
|
||||||
|
const char *export = qdict_get_try_str(bs->options, "export");
|
||||||
|
|
||||||
if (s->saddr->type == SOCKET_ADDRESS_TYPE_INET) {
|
qdict_put_obj(opts, "driver", QOBJECT(qstring_from_str("nbd")));
|
||||||
const InetSocketAddress *inet = &s->saddr->u.inet;
|
|
||||||
if (!inet->has_ipv4 && !inet->has_ipv6 && !inet->has_to) {
|
|
||||||
host = inet->host;
|
|
||||||
port = inet->port;
|
|
||||||
}
|
|
||||||
} else if (s->saddr->type == SOCKET_ADDRESS_TYPE_UNIX) {
|
|
||||||
path = s->saddr->u.q_unix.path;
|
|
||||||
} /* else can't represent as pseudo-filename */
|
|
||||||
|
|
||||||
qdict_put_str(opts, "driver", "nbd");
|
if (path && export) {
|
||||||
|
|
||||||
if (path && s->export) {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"nbd+unix:///%s?socket=%s", s->export, path);
|
"nbd+unix:///%s?socket=%s", export, path);
|
||||||
} else if (path && !s->export) {
|
} else if (path && !export) {
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"nbd+unix://?socket=%s", path);
|
"nbd+unix://?socket=%s", path);
|
||||||
} else if (host && s->export) {
|
} else if (!path && export && port) {
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"nbd://%s:%s/%s", host, port, s->export);
|
"nbd://%s:%s/%s", host, port, export);
|
||||||
} else if (host && !s->export) {
|
} else if (!path && export && !port) {
|
||||||
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
|
"nbd://%s/%s", host, export);
|
||||||
|
} else if (!path && !export && port) {
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
"nbd://%s:%s", host, port);
|
"nbd://%s:%s", host, port);
|
||||||
|
} else if (!path && !export && !port) {
|
||||||
|
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
||||||
|
"nbd://%s", host);
|
||||||
}
|
}
|
||||||
|
|
||||||
ov = qobject_output_visitor_new(&saddr_qdict);
|
if (path) {
|
||||||
visit_type_SocketAddress(ov, NULL, &s->saddr, &error_abort);
|
qdict_put_obj(opts, "path", QOBJECT(qstring_from_str(path)));
|
||||||
visit_complete(ov, &saddr_qdict);
|
} else if (port) {
|
||||||
visit_free(ov);
|
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(host)));
|
||||||
qdict_put_obj(opts, "server", saddr_qdict);
|
qdict_put_obj(opts, "port", QOBJECT(qstring_from_str(port)));
|
||||||
|
} else {
|
||||||
if (s->export) {
|
qdict_put_obj(opts, "host", QOBJECT(qstring_from_str(host)));
|
||||||
qdict_put_str(opts, "export", s->export);
|
|
||||||
}
|
}
|
||||||
if (s->tlscredsid) {
|
if (export) {
|
||||||
qdict_put_str(opts, "tls-creds", s->tlscredsid);
|
qdict_put_obj(opts, "export", QOBJECT(qstring_from_str(export)));
|
||||||
}
|
}
|
||||||
|
|
||||||
qdict_flatten(opts);
|
|
||||||
bs->full_open_options = opts;
|
bs->full_open_options = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,12 +390,11 @@ static BlockDriver bdrv_nbd = {
|
|||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
.bdrv_parse_filename = nbd_parse_filename,
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
|
||||||
.bdrv_close = nbd_close,
|
.bdrv_close = nbd_close,
|
||||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
.bdrv_co_discard = nbd_co_discard,
|
||||||
.bdrv_refresh_limits = nbd_refresh_limits,
|
.bdrv_refresh_limits = nbd_refresh_limits,
|
||||||
.bdrv_getlength = nbd_getlength,
|
.bdrv_getlength = nbd_getlength,
|
||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
@@ -582,12 +408,11 @@ static BlockDriver bdrv_nbd_tcp = {
|
|||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
.bdrv_parse_filename = nbd_parse_filename,
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
|
||||||
.bdrv_close = nbd_close,
|
.bdrv_close = nbd_close,
|
||||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
.bdrv_co_discard = nbd_co_discard,
|
||||||
.bdrv_refresh_limits = nbd_refresh_limits,
|
.bdrv_refresh_limits = nbd_refresh_limits,
|
||||||
.bdrv_getlength = nbd_getlength,
|
.bdrv_getlength = nbd_getlength,
|
||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
@@ -601,12 +426,11 @@ static BlockDriver bdrv_nbd_unix = {
|
|||||||
.instance_size = sizeof(BDRVNBDState),
|
.instance_size = sizeof(BDRVNBDState),
|
||||||
.bdrv_parse_filename = nbd_parse_filename,
|
.bdrv_parse_filename = nbd_parse_filename,
|
||||||
.bdrv_file_open = nbd_open,
|
.bdrv_file_open = nbd_open,
|
||||||
.bdrv_co_preadv = nbd_client_co_preadv,
|
.bdrv_co_readv = nbd_co_readv,
|
||||||
.bdrv_co_pwritev = nbd_client_co_pwritev,
|
.bdrv_co_writev = nbd_co_writev,
|
||||||
.bdrv_co_pwrite_zeroes = nbd_client_co_pwrite_zeroes,
|
|
||||||
.bdrv_close = nbd_close,
|
.bdrv_close = nbd_close,
|
||||||
.bdrv_co_flush_to_os = nbd_co_flush,
|
.bdrv_co_flush_to_os = nbd_co_flush,
|
||||||
.bdrv_co_pdiscard = nbd_client_co_pdiscard,
|
.bdrv_co_discard = nbd_co_discard,
|
||||||
.bdrv_refresh_limits = nbd_refresh_limits,
|
.bdrv_refresh_limits = nbd_refresh_limits,
|
||||||
.bdrv_getlength = nbd_getlength,
|
.bdrv_getlength = nbd_getlength,
|
||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
|
|||||||
638
block/nfs.c
638
block/nfs.c
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* QEMU Block driver for native access to files on NFS shares
|
* QEMU Block driver for native access to files on NFS shares
|
||||||
*
|
*
|
||||||
* Copyright (c) 2014-2016 Peter Lieven <pl@kamp.de>
|
* Copyright (c) 2014 Peter Lieven <pl@kamp.de>
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -22,30 +22,20 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "config-host.h"
|
||||||
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
#include "qemu/config-file.h"
|
#include "qemu/config-file.h"
|
||||||
#include "qemu/error-report.h"
|
#include "qemu/error-report.h"
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "qemu/iov.h"
|
#include "qemu/iov.h"
|
||||||
#include "qemu/uri.h"
|
#include "qemu/uri.h"
|
||||||
#include "qemu/cutils.h"
|
|
||||||
#include "sysemu/sysemu.h"
|
#include "sysemu/sysemu.h"
|
||||||
#include "qapi/qmp/qdict.h"
|
|
||||||
#include "qapi/qmp/qstring.h"
|
|
||||||
#include "qapi-visit.h"
|
|
||||||
#include "qapi/qobject-input-visitor.h"
|
|
||||||
#include "qapi/qobject-output-visitor.h"
|
|
||||||
#include <nfsc/libnfs.h>
|
#include <nfsc/libnfs.h>
|
||||||
|
|
||||||
|
|
||||||
#define QEMU_NFS_MAX_READAHEAD_SIZE 1048576
|
#define QEMU_NFS_MAX_READAHEAD_SIZE 1048576
|
||||||
#define QEMU_NFS_MAX_PAGECACHE_SIZE (8388608 / NFS_BLKSIZE)
|
|
||||||
#define QEMU_NFS_MAX_DEBUG_LEVEL 2
|
|
||||||
|
|
||||||
typedef struct NFSClient {
|
typedef struct NFSClient {
|
||||||
struct nfs_context *context;
|
struct nfs_context *context;
|
||||||
@@ -53,148 +43,30 @@ typedef struct NFSClient {
|
|||||||
int events;
|
int events;
|
||||||
bool has_zero_init;
|
bool has_zero_init;
|
||||||
AioContext *aio_context;
|
AioContext *aio_context;
|
||||||
QemuMutex mutex;
|
|
||||||
blkcnt_t st_blocks;
|
|
||||||
bool cache_used;
|
|
||||||
NFSServer *server;
|
|
||||||
char *path;
|
|
||||||
int64_t uid, gid, tcp_syncnt, readahead, pagecache, debug;
|
|
||||||
} NFSClient;
|
} NFSClient;
|
||||||
|
|
||||||
typedef struct NFSRPC {
|
typedef struct NFSRPC {
|
||||||
BlockDriverState *bs;
|
|
||||||
int ret;
|
int ret;
|
||||||
int complete;
|
int complete;
|
||||||
QEMUIOVector *iov;
|
QEMUIOVector *iov;
|
||||||
struct stat *st;
|
struct stat *st;
|
||||||
Coroutine *co;
|
Coroutine *co;
|
||||||
|
QEMUBH *bh;
|
||||||
NFSClient *client;
|
NFSClient *client;
|
||||||
} NFSRPC;
|
} NFSRPC;
|
||||||
|
|
||||||
static int nfs_parse_uri(const char *filename, QDict *options, Error **errp)
|
|
||||||
{
|
|
||||||
URI *uri = NULL;
|
|
||||||
QueryParams *qp = NULL;
|
|
||||||
int ret = -EINVAL, i;
|
|
||||||
|
|
||||||
uri = uri_parse(filename);
|
|
||||||
if (!uri) {
|
|
||||||
error_setg(errp, "Invalid URI specified");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (strcmp(uri->scheme, "nfs") != 0) {
|
|
||||||
error_setg(errp, "URI scheme must be 'nfs'");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!uri->server) {
|
|
||||||
error_setg(errp, "missing hostname in URI");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!uri->path) {
|
|
||||||
error_setg(errp, "missing file path in URI");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
qp = query_params_parse(uri->query);
|
|
||||||
if (!qp) {
|
|
||||||
error_setg(errp, "could not parse query parameters");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
qdict_put_str(options, "server.host", uri->server);
|
|
||||||
qdict_put_str(options, "server.type", "inet");
|
|
||||||
qdict_put_str(options, "path", uri->path);
|
|
||||||
|
|
||||||
for (i = 0; i < qp->n; i++) {
|
|
||||||
unsigned long long val;
|
|
||||||
if (!qp->p[i].value) {
|
|
||||||
error_setg(errp, "Value for NFS parameter expected: %s",
|
|
||||||
qp->p[i].name);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (parse_uint_full(qp->p[i].value, &val, 0)) {
|
|
||||||
error_setg(errp, "Illegal value for NFS parameter: %s",
|
|
||||||
qp->p[i].name);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
if (!strcmp(qp->p[i].name, "uid")) {
|
|
||||||
qdict_put_str(options, "user", qp->p[i].value);
|
|
||||||
} else if (!strcmp(qp->p[i].name, "gid")) {
|
|
||||||
qdict_put_str(options, "group", qp->p[i].value);
|
|
||||||
} else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
|
|
||||||
qdict_put_str(options, "tcp-syn-count", qp->p[i].value);
|
|
||||||
} else if (!strcmp(qp->p[i].name, "readahead")) {
|
|
||||||
qdict_put_str(options, "readahead-size", qp->p[i].value);
|
|
||||||
} else if (!strcmp(qp->p[i].name, "pagecache")) {
|
|
||||||
qdict_put_str(options, "page-cache-size", qp->p[i].value);
|
|
||||||
} else if (!strcmp(qp->p[i].name, "debug")) {
|
|
||||||
qdict_put_str(options, "debug", qp->p[i].value);
|
|
||||||
} else {
|
|
||||||
error_setg(errp, "Unknown NFS parameter name: %s",
|
|
||||||
qp->p[i].name);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = 0;
|
|
||||||
out:
|
|
||||||
if (qp) {
|
|
||||||
query_params_free(qp);
|
|
||||||
}
|
|
||||||
if (uri) {
|
|
||||||
uri_free(uri);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool nfs_has_filename_options_conflict(QDict *options, Error **errp)
|
|
||||||
{
|
|
||||||
const QDictEntry *qe;
|
|
||||||
|
|
||||||
for (qe = qdict_first(options); qe; qe = qdict_next(options, qe)) {
|
|
||||||
if (!strcmp(qe->key, "host") ||
|
|
||||||
!strcmp(qe->key, "path") ||
|
|
||||||
!strcmp(qe->key, "user") ||
|
|
||||||
!strcmp(qe->key, "group") ||
|
|
||||||
!strcmp(qe->key, "tcp-syn-count") ||
|
|
||||||
!strcmp(qe->key, "readahead-size") ||
|
|
||||||
!strcmp(qe->key, "page-cache-size") ||
|
|
||||||
!strcmp(qe->key, "debug") ||
|
|
||||||
strstart(qe->key, "server.", NULL))
|
|
||||||
{
|
|
||||||
error_setg(errp, "Option %s cannot be used with a filename",
|
|
||||||
qe->key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_parse_filename(const char *filename, QDict *options,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
if (nfs_has_filename_options_conflict(options, errp)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
nfs_parse_uri(filename, options, errp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_process_read(void *arg);
|
static void nfs_process_read(void *arg);
|
||||||
static void nfs_process_write(void *arg);
|
static void nfs_process_write(void *arg);
|
||||||
|
|
||||||
/* Called with QemuMutex held. */
|
|
||||||
static void nfs_set_events(NFSClient *client)
|
static void nfs_set_events(NFSClient *client)
|
||||||
{
|
{
|
||||||
int ev = nfs_which_events(client->context);
|
int ev = nfs_which_events(client->context);
|
||||||
if (ev != client->events) {
|
if (ev != client->events) {
|
||||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
aio_set_fd_handler(client->aio_context,
|
||||||
false,
|
nfs_get_fd(client->context),
|
||||||
(ev & POLLIN) ? nfs_process_read : NULL,
|
(ev & POLLIN) ? nfs_process_read : NULL,
|
||||||
(ev & POLLOUT) ? nfs_process_write : NULL,
|
(ev & POLLOUT) ? nfs_process_write : NULL,
|
||||||
NULL, client);
|
client);
|
||||||
|
|
||||||
}
|
}
|
||||||
client->events = ev;
|
client->events = ev;
|
||||||
@@ -203,48 +75,39 @@ static void nfs_set_events(NFSClient *client)
|
|||||||
static void nfs_process_read(void *arg)
|
static void nfs_process_read(void *arg)
|
||||||
{
|
{
|
||||||
NFSClient *client = arg;
|
NFSClient *client = arg;
|
||||||
|
|
||||||
qemu_mutex_lock(&client->mutex);
|
|
||||||
nfs_service(client->context, POLLIN);
|
nfs_service(client->context, POLLIN);
|
||||||
nfs_set_events(client);
|
nfs_set_events(client);
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfs_process_write(void *arg)
|
static void nfs_process_write(void *arg)
|
||||||
{
|
{
|
||||||
NFSClient *client = arg;
|
NFSClient *client = arg;
|
||||||
|
|
||||||
qemu_mutex_lock(&client->mutex);
|
|
||||||
nfs_service(client->context, POLLOUT);
|
nfs_service(client->context, POLLOUT);
|
||||||
nfs_set_events(client);
|
nfs_set_events(client);
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfs_co_init_task(BlockDriverState *bs, NFSRPC *task)
|
static void nfs_co_init_task(NFSClient *client, NFSRPC *task)
|
||||||
{
|
{
|
||||||
*task = (NFSRPC) {
|
*task = (NFSRPC) {
|
||||||
.co = qemu_coroutine_self(),
|
.co = qemu_coroutine_self(),
|
||||||
.bs = bs,
|
.client = client,
|
||||||
.client = bs->opaque,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nfs_co_generic_bh_cb(void *opaque)
|
static void nfs_co_generic_bh_cb(void *opaque)
|
||||||
{
|
{
|
||||||
NFSRPC *task = opaque;
|
NFSRPC *task = opaque;
|
||||||
|
|
||||||
task->complete = 1;
|
task->complete = 1;
|
||||||
aio_co_wake(task->co);
|
qemu_bh_delete(task->bh);
|
||||||
|
qemu_coroutine_enter(task->co, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called (via nfs_service) with QemuMutex held. */
|
|
||||||
static void
|
static void
|
||||||
nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
||||||
void *private_data)
|
void *private_data)
|
||||||
{
|
{
|
||||||
NFSRPC *task = private_data;
|
NFSRPC *task = private_data;
|
||||||
task->ret = ret;
|
task->ret = ret;
|
||||||
assert(!task->st);
|
|
||||||
if (task->ret > 0 && task->iov) {
|
if (task->ret > 0 && task->iov) {
|
||||||
if (task->ret <= task->iov->size) {
|
if (task->ret <= task->iov->size) {
|
||||||
qemu_iovec_from_buf(task->iov, 0, data, task->ret);
|
qemu_iovec_from_buf(task->iov, 0, data, task->ret);
|
||||||
@@ -252,33 +115,40 @@ nfs_co_generic_cb(int ret, struct nfs_context *nfs, void *data,
|
|||||||
task->ret = -EIO;
|
task->ret = -EIO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (task->ret == 0 && task->st) {
|
||||||
|
memcpy(task->st, data, sizeof(struct stat));
|
||||||
|
}
|
||||||
if (task->ret < 0) {
|
if (task->ret < 0) {
|
||||||
error_report("NFS Error: %s", nfs_get_error(nfs));
|
error_report("NFS Error: %s", nfs_get_error(nfs));
|
||||||
}
|
}
|
||||||
aio_bh_schedule_oneshot(task->client->aio_context,
|
if (task->co) {
|
||||||
|
task->bh = aio_bh_new(task->client->aio_context,
|
||||||
nfs_co_generic_bh_cb, task);
|
nfs_co_generic_bh_cb, task);
|
||||||
|
qemu_bh_schedule(task->bh);
|
||||||
|
} else {
|
||||||
|
task->complete = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, uint64_t offset,
|
static int coroutine_fn nfs_co_readv(BlockDriverState *bs,
|
||||||
uint64_t bytes, QEMUIOVector *iov,
|
int64_t sector_num, int nb_sectors,
|
||||||
int flags)
|
QEMUIOVector *iov)
|
||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
NFSRPC task;
|
NFSRPC task;
|
||||||
|
|
||||||
nfs_co_init_task(bs, &task);
|
nfs_co_init_task(client, &task);
|
||||||
task.iov = iov;
|
task.iov = iov;
|
||||||
|
|
||||||
qemu_mutex_lock(&client->mutex);
|
|
||||||
if (nfs_pread_async(client->context, client->fh,
|
if (nfs_pread_async(client->context, client->fh,
|
||||||
offset, bytes, nfs_co_generic_cb, &task) != 0) {
|
sector_num * BDRV_SECTOR_SIZE,
|
||||||
qemu_mutex_unlock(&client->mutex);
|
nb_sectors * BDRV_SECTOR_SIZE,
|
||||||
|
nfs_co_generic_cb, &task) != 0) {
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
while (!task.complete) {
|
while (!task.complete) {
|
||||||
|
nfs_set_events(client);
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,50 +164,39 @@ static int coroutine_fn nfs_co_preadv(BlockDriverState *bs, uint64_t offset,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int coroutine_fn nfs_co_pwritev(BlockDriverState *bs, uint64_t offset,
|
static int coroutine_fn nfs_co_writev(BlockDriverState *bs,
|
||||||
uint64_t bytes, QEMUIOVector *iov,
|
int64_t sector_num, int nb_sectors,
|
||||||
int flags)
|
QEMUIOVector *iov)
|
||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
NFSRPC task;
|
NFSRPC task;
|
||||||
char *buf = NULL;
|
char *buf = NULL;
|
||||||
bool my_buffer = false;
|
|
||||||
|
|
||||||
nfs_co_init_task(bs, &task);
|
nfs_co_init_task(client, &task);
|
||||||
|
|
||||||
if (iov->niov != 1) {
|
buf = g_try_malloc(nb_sectors * BDRV_SECTOR_SIZE);
|
||||||
buf = g_try_malloc(bytes);
|
if (nb_sectors && buf == NULL) {
|
||||||
if (bytes && buf == NULL) {
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
qemu_iovec_to_buf(iov, 0, buf, bytes);
|
|
||||||
my_buffer = true;
|
|
||||||
} else {
|
|
||||||
buf = iov->iov[0].iov_base;
|
|
||||||
}
|
|
||||||
|
|
||||||
qemu_mutex_lock(&client->mutex);
|
qemu_iovec_to_buf(iov, 0, buf, nb_sectors * BDRV_SECTOR_SIZE);
|
||||||
|
|
||||||
if (nfs_pwrite_async(client->context, client->fh,
|
if (nfs_pwrite_async(client->context, client->fh,
|
||||||
offset, bytes, buf,
|
sector_num * BDRV_SECTOR_SIZE,
|
||||||
nfs_co_generic_cb, &task) != 0) {
|
nb_sectors * BDRV_SECTOR_SIZE,
|
||||||
qemu_mutex_unlock(&client->mutex);
|
buf, nfs_co_generic_cb, &task) != 0) {
|
||||||
if (my_buffer) {
|
|
||||||
g_free(buf);
|
g_free(buf);
|
||||||
}
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
while (!task.complete) {
|
while (!task.complete) {
|
||||||
|
nfs_set_events(client);
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (my_buffer) {
|
|
||||||
g_free(buf);
|
g_free(buf);
|
||||||
}
|
|
||||||
|
|
||||||
if (task.ret != bytes) {
|
if (task.ret != nb_sectors * BDRV_SECTOR_SIZE) {
|
||||||
return task.ret < 0 ? task.ret : -EIO;
|
return task.ret < 0 ? task.ret : -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,62 +208,30 @@ static int coroutine_fn nfs_co_flush(BlockDriverState *bs)
|
|||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
NFSRPC task;
|
NFSRPC task;
|
||||||
|
|
||||||
nfs_co_init_task(bs, &task);
|
nfs_co_init_task(client, &task);
|
||||||
|
|
||||||
qemu_mutex_lock(&client->mutex);
|
|
||||||
if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
|
if (nfs_fsync_async(client->context, client->fh, nfs_co_generic_cb,
|
||||||
&task) != 0) {
|
&task) != 0) {
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
nfs_set_events(client);
|
|
||||||
qemu_mutex_unlock(&client->mutex);
|
|
||||||
while (!task.complete) {
|
while (!task.complete) {
|
||||||
|
nfs_set_events(client);
|
||||||
qemu_coroutine_yield();
|
qemu_coroutine_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
return task.ret;
|
return task.ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO Convert to fine grained options */
|
||||||
static QemuOptsList runtime_opts = {
|
static QemuOptsList runtime_opts = {
|
||||||
.name = "nfs",
|
.name = "nfs",
|
||||||
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
.head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
|
||||||
.desc = {
|
.desc = {
|
||||||
{
|
{
|
||||||
.name = "path",
|
.name = "filename",
|
||||||
.type = QEMU_OPT_STRING,
|
.type = QEMU_OPT_STRING,
|
||||||
.help = "Path of the image on the host",
|
.help = "URL to the NFS file",
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "user",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "UID value to use when talking to the server",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "group",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "GID value to use when talking to the server",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "tcp-syn-count",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Number of SYNs to send during the session establish",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "readahead-size",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Set the readahead size in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "page-cache-size",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Set the pagecache size in bytes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "debug",
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "Set the NFS debug level (max 2)",
|
|
||||||
},
|
},
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
@@ -414,8 +241,9 @@ static void nfs_detach_aio_context(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
|
|
||||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
aio_set_fd_handler(client->aio_context,
|
||||||
false, NULL, NULL, NULL, NULL);
|
nfs_get_fd(client->context),
|
||||||
|
NULL, NULL, NULL);
|
||||||
client->events = 0;
|
client->events = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,8 +262,9 @@ static void nfs_client_close(NFSClient *client)
|
|||||||
if (client->fh) {
|
if (client->fh) {
|
||||||
nfs_close(client->context, client->fh);
|
nfs_close(client->context, client->fh);
|
||||||
}
|
}
|
||||||
aio_set_fd_handler(client->aio_context, nfs_get_fd(client->context),
|
aio_set_fd_handler(client->aio_context,
|
||||||
false, NULL, NULL, NULL, NULL);
|
nfs_get_fd(client->context),
|
||||||
|
NULL, NULL, NULL);
|
||||||
nfs_destroy_context(client->context);
|
nfs_destroy_context(client->context);
|
||||||
}
|
}
|
||||||
memset(client, 0, sizeof(NFSClient));
|
memset(client, 0, sizeof(NFSClient));
|
||||||
@@ -445,75 +274,27 @@ static void nfs_file_close(BlockDriverState *bs)
|
|||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
nfs_client_close(client);
|
nfs_client_close(client);
|
||||||
qemu_mutex_destroy(&client->mutex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NFSServer *nfs_config(QDict *options, Error **errp)
|
static int64_t nfs_client_open(NFSClient *client, const char *filename,
|
||||||
|
int flags, Error **errp)
|
||||||
{
|
{
|
||||||
NFSServer *server = NULL;
|
int ret = -EINVAL, i;
|
||||||
QDict *addr = NULL;
|
|
||||||
QObject *crumpled_addr = NULL;
|
|
||||||
Visitor *iv = NULL;
|
|
||||||
Error *local_error = NULL;
|
|
||||||
|
|
||||||
qdict_extract_subqdict(options, &addr, "server.");
|
|
||||||
if (!qdict_size(addr)) {
|
|
||||||
error_setg(errp, "NFS server address missing");
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
crumpled_addr = qdict_crumple(addr, errp);
|
|
||||||
if (!crumpled_addr) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Caution: this works only because all scalar members of
|
|
||||||
* NFSServer are QString in @crumpled_addr. The visitor expects
|
|
||||||
* @crumpled_addr to be typed according to the QAPI schema. It
|
|
||||||
* is when @options come from -blockdev or blockdev_add. But when
|
|
||||||
* they come from -drive, they're all QString.
|
|
||||||
*/
|
|
||||||
iv = qobject_input_visitor_new(crumpled_addr);
|
|
||||||
visit_type_NFSServer(iv, NULL, &server, &local_error);
|
|
||||||
if (local_error) {
|
|
||||||
error_propagate(errp, local_error);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
QDECREF(addr);
|
|
||||||
qobject_decref(crumpled_addr);
|
|
||||||
visit_free(iv);
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|
||||||
int flags, int open_flags, Error **errp)
|
|
||||||
{
|
|
||||||
int ret = -EINVAL;
|
|
||||||
QemuOpts *opts = NULL;
|
|
||||||
Error *local_err = NULL;
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
URI *uri;
|
||||||
|
QueryParams *qp = NULL;
|
||||||
char *file = NULL, *strp = NULL;
|
char *file = NULL, *strp = NULL;
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
uri = uri_parse(filename);
|
||||||
qemu_opts_absorb_qdict(opts, options, &local_err);
|
if (!uri) {
|
||||||
if (local_err) {
|
error_setg(errp, "Invalid URL specified");
|
||||||
error_propagate(errp, local_err);
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (!uri->server) {
|
||||||
client->path = g_strdup(qemu_opt_get(opts, "path"));
|
error_setg(errp, "Invalid URL specified");
|
||||||
if (!client->path) {
|
|
||||||
ret = -EINVAL;
|
|
||||||
error_setg(errp, "No path was specified");
|
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
strp = strrchr(uri->path, '/');
|
||||||
strp = strrchr(client->path, '/');
|
|
||||||
if (strp == NULL) {
|
if (strp == NULL) {
|
||||||
error_setg(errp, "Invalid URL specified");
|
error_setg(errp, "Invalid URL specified");
|
||||||
goto fail;
|
goto fail;
|
||||||
@@ -521,89 +302,48 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||||||
file = g_strdup(strp);
|
file = g_strdup(strp);
|
||||||
*strp = 0;
|
*strp = 0;
|
||||||
|
|
||||||
/* Pop the config into our state object, Exit if invalid */
|
|
||||||
client->server = nfs_config(options, errp);
|
|
||||||
if (!client->server) {
|
|
||||||
ret = -EINVAL;
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->context = nfs_init_context();
|
client->context = nfs_init_context();
|
||||||
if (client->context == NULL) {
|
if (client->context == NULL) {
|
||||||
error_setg(errp, "Failed to init NFS context");
|
error_setg(errp, "Failed to init NFS context");
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qemu_opt_get(opts, "user")) {
|
qp = query_params_parse(uri->query);
|
||||||
client->uid = qemu_opt_get_number(opts, "user", 0);
|
for (i = 0; i < qp->n; i++) {
|
||||||
nfs_set_uid(client->context, client->uid);
|
unsigned long long val;
|
||||||
|
if (!qp->p[i].value) {
|
||||||
|
error_setg(errp, "Value for NFS parameter expected: %s",
|
||||||
|
qp->p[i].name);
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (parse_uint_full(qp->p[i].value, &val, 0)) {
|
||||||
if (qemu_opt_get(opts, "group")) {
|
error_setg(errp, "Illegal value for NFS parameter: %s",
|
||||||
client->gid = qemu_opt_get_number(opts, "group", 0);
|
qp->p[i].name);
|
||||||
nfs_set_gid(client->context, client->gid);
|
goto fail;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(qp->p[i].name, "uid")) {
|
||||||
if (qemu_opt_get(opts, "tcp-syn-count")) {
|
nfs_set_uid(client->context, val);
|
||||||
client->tcp_syncnt = qemu_opt_get_number(opts, "tcp-syn-count", 0);
|
} else if (!strcmp(qp->p[i].name, "gid")) {
|
||||||
nfs_set_tcp_syncnt(client->context, client->tcp_syncnt);
|
nfs_set_gid(client->context, val);
|
||||||
}
|
} else if (!strcmp(qp->p[i].name, "tcp-syncnt")) {
|
||||||
|
nfs_set_tcp_syncnt(client->context, val);
|
||||||
#ifdef LIBNFS_FEATURE_READAHEAD
|
#ifdef LIBNFS_FEATURE_READAHEAD
|
||||||
if (qemu_opt_get(opts, "readahead-size")) {
|
} else if (!strcmp(qp->p[i].name, "readahead")) {
|
||||||
if (open_flags & BDRV_O_NOCACHE) {
|
if (val > QEMU_NFS_MAX_READAHEAD_SIZE) {
|
||||||
error_setg(errp, "Cannot enable NFS readahead "
|
error_report("NFS Warning: Truncating NFS readahead"
|
||||||
"if cache.direct = on");
|
" size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
|
||||||
|
val = QEMU_NFS_MAX_READAHEAD_SIZE;
|
||||||
|
}
|
||||||
|
nfs_set_readahead(client->context, val);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
error_setg(errp, "Unknown NFS parameter name: %s",
|
||||||
|
qp->p[i].name);
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
client->readahead = qemu_opt_get_number(opts, "readahead-size", 0);
|
|
||||||
if (client->readahead > QEMU_NFS_MAX_READAHEAD_SIZE) {
|
|
||||||
error_report("NFS Warning: Truncating NFS readahead "
|
|
||||||
"size to %d", QEMU_NFS_MAX_READAHEAD_SIZE);
|
|
||||||
client->readahead = QEMU_NFS_MAX_READAHEAD_SIZE;
|
|
||||||
}
|
}
|
||||||
nfs_set_readahead(client->context, client->readahead);
|
|
||||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
|
||||||
nfs_set_pagecache_ttl(client->context, 0);
|
|
||||||
#endif
|
|
||||||
client->cache_used = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
ret = nfs_mount(client->context, uri->server, uri->path);
|
||||||
if (qemu_opt_get(opts, "page-cache-size")) {
|
|
||||||
if (open_flags & BDRV_O_NOCACHE) {
|
|
||||||
error_setg(errp, "Cannot enable NFS pagecache "
|
|
||||||
"if cache.direct = on");
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
client->pagecache = qemu_opt_get_number(opts, "page-cache-size", 0);
|
|
||||||
if (client->pagecache > QEMU_NFS_MAX_PAGECACHE_SIZE) {
|
|
||||||
error_report("NFS Warning: Truncating NFS pagecache "
|
|
||||||
"size to %d pages", QEMU_NFS_MAX_PAGECACHE_SIZE);
|
|
||||||
client->pagecache = QEMU_NFS_MAX_PAGECACHE_SIZE;
|
|
||||||
}
|
|
||||||
nfs_set_pagecache(client->context, client->pagecache);
|
|
||||||
nfs_set_pagecache_ttl(client->context, 0);
|
|
||||||
client->cache_used = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBNFS_FEATURE_DEBUG
|
|
||||||
if (qemu_opt_get(opts, "debug")) {
|
|
||||||
client->debug = qemu_opt_get_number(opts, "debug", 0);
|
|
||||||
/* limit the maximum debug level to avoid potential flooding
|
|
||||||
* of our log files. */
|
|
||||||
if (client->debug > QEMU_NFS_MAX_DEBUG_LEVEL) {
|
|
||||||
error_report("NFS Warning: Limiting NFS debug level "
|
|
||||||
"to %d", QEMU_NFS_MAX_DEBUG_LEVEL);
|
|
||||||
client->debug = QEMU_NFS_MAX_DEBUG_LEVEL;
|
|
||||||
}
|
|
||||||
nfs_set_debug(client->context, client->debug);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ret = nfs_mount(client->context, client->server->host, client->path);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
error_setg(errp, "Failed to mount nfs share: %s",
|
error_setg(errp, "Failed to mount nfs share: %s",
|
||||||
nfs_get_error(client->context));
|
nfs_get_error(client->context));
|
||||||
@@ -634,15 +374,15 @@ static int64_t nfs_client_open(NFSClient *client, QDict *options,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
|
ret = DIV_ROUND_UP(st.st_size, BDRV_SECTOR_SIZE);
|
||||||
client->st_blocks = st.st_blocks;
|
|
||||||
client->has_zero_init = S_ISREG(st.st_mode);
|
client->has_zero_init = S_ISREG(st.st_mode);
|
||||||
*strp = '/';
|
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
nfs_client_close(client);
|
nfs_client_close(client);
|
||||||
out:
|
out:
|
||||||
qemu_opts_del(opts);
|
if (qp) {
|
||||||
|
query_params_free(qp);
|
||||||
|
}
|
||||||
|
uri_free(uri);
|
||||||
g_free(file);
|
g_free(file);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -651,18 +391,28 @@ static int nfs_file_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
Error **errp) {
|
Error **errp) {
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
int64_t ret;
|
int64_t ret;
|
||||||
|
QemuOpts *opts;
|
||||||
|
Error *local_err = NULL;
|
||||||
|
|
||||||
client->aio_context = bdrv_get_aio_context(bs);
|
client->aio_context = bdrv_get_aio_context(bs);
|
||||||
|
|
||||||
ret = nfs_client_open(client, options,
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
qemu_opts_absorb_qdict(opts, options, &local_err);
|
||||||
bs->open_flags, errp);
|
if (local_err) {
|
||||||
if (ret < 0) {
|
error_propagate(errp, local_err);
|
||||||
return ret;
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
ret = nfs_client_open(client, qemu_opt_get(opts, "filename"),
|
||||||
|
(flags & BDRV_O_RDWR) ? O_RDWR : O_RDONLY,
|
||||||
|
errp);
|
||||||
|
if (ret < 0) {
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
qemu_mutex_init(&client->mutex);
|
|
||||||
bs->total_sectors = ret;
|
bs->total_sectors = ret;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
out:
|
||||||
|
qemu_opts_del(opts);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +434,6 @@ static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
int64_t total_size = 0;
|
int64_t total_size = 0;
|
||||||
NFSClient *client = g_new0(NFSClient, 1);
|
NFSClient *client = g_new0(NFSClient, 1);
|
||||||
QDict *options = NULL;
|
|
||||||
|
|
||||||
client->aio_context = qemu_get_aio_context();
|
client->aio_context = qemu_get_aio_context();
|
||||||
|
|
||||||
@@ -692,20 +441,13 @@ static int nfs_file_create(const char *url, QemuOpts *opts, Error **errp)
|
|||||||
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
total_size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
|
||||||
BDRV_SECTOR_SIZE);
|
BDRV_SECTOR_SIZE);
|
||||||
|
|
||||||
options = qdict_new();
|
ret = nfs_client_open(client, url, O_CREAT, errp);
|
||||||
ret = nfs_parse_uri(url, options, errp);
|
|
||||||
if (ret < 0) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = nfs_client_open(client, options, O_CREAT, 0, errp);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
ret = nfs_ftruncate(client->context, client->fh, total_size);
|
ret = nfs_ftruncate(client->context, client->fh, total_size);
|
||||||
nfs_client_close(client);
|
nfs_client_close(client);
|
||||||
out:
|
out:
|
||||||
QDECREF(options);
|
|
||||||
g_free(client);
|
g_free(client);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -716,168 +458,38 @@ static int nfs_has_zero_init(BlockDriverState *bs)
|
|||||||
return client->has_zero_init;
|
return client->has_zero_init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called (via nfs_service) with QemuMutex held. */
|
|
||||||
static void
|
|
||||||
nfs_get_allocated_file_size_cb(int ret, struct nfs_context *nfs, void *data,
|
|
||||||
void *private_data)
|
|
||||||
{
|
|
||||||
NFSRPC *task = private_data;
|
|
||||||
task->ret = ret;
|
|
||||||
if (task->ret == 0) {
|
|
||||||
memcpy(task->st, data, sizeof(struct stat));
|
|
||||||
}
|
|
||||||
if (task->ret < 0) {
|
|
||||||
error_report("NFS Error: %s", nfs_get_error(nfs));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set task->complete before reading bs->wakeup. */
|
|
||||||
atomic_mb_set(&task->complete, 1);
|
|
||||||
bdrv_wakeup(task->bs);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
|
static int64_t nfs_get_allocated_file_size(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
NFSRPC task = {0};
|
NFSRPC task = {0};
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (bdrv_is_read_only(bs) &&
|
|
||||||
!(bs->open_flags & BDRV_O_NOCACHE)) {
|
|
||||||
return client->st_blocks * 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
task.bs = bs;
|
|
||||||
task.st = &st;
|
task.st = &st;
|
||||||
if (nfs_fstat_async(client->context, client->fh, nfs_get_allocated_file_size_cb,
|
if (nfs_fstat_async(client->context, client->fh, nfs_co_generic_cb,
|
||||||
&task) != 0) {
|
&task) != 0) {
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (!task.complete) {
|
||||||
nfs_set_events(client);
|
nfs_set_events(client);
|
||||||
BDRV_POLL_WHILE(bs, !task.complete);
|
aio_poll(client->aio_context, true);
|
||||||
|
}
|
||||||
|
|
||||||
return (task.ret < 0 ? task.ret : st.st_blocks * 512);
|
return (task.ret < 0 ? task.ret : st.st_blocks * st.st_blksize);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nfs_file_truncate(BlockDriverState *bs, int64_t offset, Error **errp)
|
static int nfs_file_truncate(BlockDriverState *bs, int64_t offset)
|
||||||
{
|
{
|
||||||
NFSClient *client = bs->opaque;
|
NFSClient *client = bs->opaque;
|
||||||
int ret;
|
return nfs_ftruncate(client->context, client->fh, offset);
|
||||||
|
|
||||||
ret = nfs_ftruncate(client->context, client->fh, offset);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg_errno(errp, -ret, "Failed to truncate file");
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note that this will not re-establish a connection with the NFS server
|
|
||||||
* - it is effectively a NOP. */
|
|
||||||
static int nfs_reopen_prepare(BDRVReopenState *state,
|
|
||||||
BlockReopenQueue *queue, Error **errp)
|
|
||||||
{
|
|
||||||
NFSClient *client = state->bs->opaque;
|
|
||||||
struct stat st;
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (state->flags & BDRV_O_RDWR && bdrv_is_read_only(state->bs)) {
|
|
||||||
error_setg(errp, "Cannot open a read-only mount as read-write");
|
|
||||||
return -EACCES;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((state->flags & BDRV_O_NOCACHE) && client->cache_used) {
|
|
||||||
error_setg(errp, "Cannot disable cache if libnfs readahead or"
|
|
||||||
" pagecache is enabled");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update cache for read-only reopens */
|
|
||||||
if (!(state->flags & BDRV_O_RDWR)) {
|
|
||||||
ret = nfs_fstat(client->context, client->fh, &st);
|
|
||||||
if (ret < 0) {
|
|
||||||
error_setg(errp, "Failed to fstat file: %s",
|
|
||||||
nfs_get_error(client->context));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
client->st_blocks = st.st_blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nfs_refresh_filename(BlockDriverState *bs, QDict *options)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
QDict *opts = qdict_new();
|
|
||||||
QObject *server_qdict;
|
|
||||||
Visitor *ov;
|
|
||||||
|
|
||||||
qdict_put_str(opts, "driver", "nfs");
|
|
||||||
|
|
||||||
if (client->uid && !client->gid) {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
|
||||||
"nfs://%s%s?uid=%" PRId64, client->server->host, client->path,
|
|
||||||
client->uid);
|
|
||||||
} else if (!client->uid && client->gid) {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
|
||||||
"nfs://%s%s?gid=%" PRId64, client->server->host, client->path,
|
|
||||||
client->gid);
|
|
||||||
} else if (client->uid && client->gid) {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
|
||||||
"nfs://%s%s?uid=%" PRId64 "&gid=%" PRId64,
|
|
||||||
client->server->host, client->path, client->uid, client->gid);
|
|
||||||
} else {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename),
|
|
||||||
"nfs://%s%s", client->server->host, client->path);
|
|
||||||
}
|
|
||||||
|
|
||||||
ov = qobject_output_visitor_new(&server_qdict);
|
|
||||||
visit_type_NFSServer(ov, NULL, &client->server, &error_abort);
|
|
||||||
visit_complete(ov, &server_qdict);
|
|
||||||
qdict_put_obj(opts, "server", server_qdict);
|
|
||||||
qdict_put_str(opts, "path", client->path);
|
|
||||||
|
|
||||||
if (client->uid) {
|
|
||||||
qdict_put_int(opts, "user", client->uid);
|
|
||||||
}
|
|
||||||
if (client->gid) {
|
|
||||||
qdict_put_int(opts, "group", client->gid);
|
|
||||||
}
|
|
||||||
if (client->tcp_syncnt) {
|
|
||||||
qdict_put_int(opts, "tcp-syn-cnt", client->tcp_syncnt);
|
|
||||||
}
|
|
||||||
if (client->readahead) {
|
|
||||||
qdict_put_int(opts, "readahead-size", client->readahead);
|
|
||||||
}
|
|
||||||
if (client->pagecache) {
|
|
||||||
qdict_put_int(opts, "page-cache-size", client->pagecache);
|
|
||||||
}
|
|
||||||
if (client->debug) {
|
|
||||||
qdict_put_int(opts, "debug", client->debug);
|
|
||||||
}
|
|
||||||
|
|
||||||
visit_free(ov);
|
|
||||||
qdict_flatten(opts);
|
|
||||||
bs->full_open_options = opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
|
||||||
static void nfs_invalidate_cache(BlockDriverState *bs,
|
|
||||||
Error **errp)
|
|
||||||
{
|
|
||||||
NFSClient *client = bs->opaque;
|
|
||||||
nfs_pagecache_invalidate(client->context, client->fh);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static BlockDriver bdrv_nfs = {
|
static BlockDriver bdrv_nfs = {
|
||||||
.format_name = "nfs",
|
.format_name = "nfs",
|
||||||
.protocol_name = "nfs",
|
.protocol_name = "nfs",
|
||||||
|
|
||||||
.instance_size = sizeof(NFSClient),
|
.instance_size = sizeof(NFSClient),
|
||||||
.bdrv_parse_filename = nfs_parse_filename,
|
.bdrv_needs_filename = true,
|
||||||
.create_opts = &nfs_create_opts,
|
.create_opts = &nfs_create_opts,
|
||||||
|
|
||||||
.bdrv_has_zero_init = nfs_has_zero_init,
|
.bdrv_has_zero_init = nfs_has_zero_init,
|
||||||
@@ -887,19 +499,13 @@ static BlockDriver bdrv_nfs = {
|
|||||||
.bdrv_file_open = nfs_file_open,
|
.bdrv_file_open = nfs_file_open,
|
||||||
.bdrv_close = nfs_file_close,
|
.bdrv_close = nfs_file_close,
|
||||||
.bdrv_create = nfs_file_create,
|
.bdrv_create = nfs_file_create,
|
||||||
.bdrv_reopen_prepare = nfs_reopen_prepare,
|
|
||||||
|
|
||||||
.bdrv_co_preadv = nfs_co_preadv,
|
.bdrv_co_readv = nfs_co_readv,
|
||||||
.bdrv_co_pwritev = nfs_co_pwritev,
|
.bdrv_co_writev = nfs_co_writev,
|
||||||
.bdrv_co_flush_to_disk = nfs_co_flush,
|
.bdrv_co_flush_to_disk = nfs_co_flush,
|
||||||
|
|
||||||
.bdrv_detach_aio_context = nfs_detach_aio_context,
|
.bdrv_detach_aio_context = nfs_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = nfs_attach_aio_context,
|
.bdrv_attach_aio_context = nfs_attach_aio_context,
|
||||||
.bdrv_refresh_filename = nfs_refresh_filename,
|
|
||||||
|
|
||||||
#ifdef LIBNFS_FEATURE_PAGECACHE
|
|
||||||
.bdrv_invalidate_cache = nfs_invalidate_cache,
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void nfs_block_init(void)
|
static void nfs_block_init(void)
|
||||||
|
|||||||
131
block/null.c
131
block/null.c
@@ -10,19 +10,10 @@
|
|||||||
* See the COPYING file in the top-level directory.
|
* See the COPYING file in the top-level directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
|
||||||
#include "qapi/error.h"
|
|
||||||
#include "qapi/qmp/qdict.h"
|
|
||||||
#include "qapi/qmp/qstring.h"
|
|
||||||
#include "block/block_int.h"
|
#include "block/block_int.h"
|
||||||
|
|
||||||
#define NULL_OPT_LATENCY "latency-ns"
|
|
||||||
#define NULL_OPT_ZEROES "read-zeroes"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int64_t length;
|
int64_t length;
|
||||||
int64_t latency_ns;
|
|
||||||
bool read_zeroes;
|
|
||||||
} BDRVNullState;
|
} BDRVNullState;
|
||||||
|
|
||||||
static QemuOptsList runtime_opts = {
|
static QemuOptsList runtime_opts = {
|
||||||
@@ -39,17 +30,6 @@ static QemuOptsList runtime_opts = {
|
|||||||
.type = QEMU_OPT_SIZE,
|
.type = QEMU_OPT_SIZE,
|
||||||
.help = "size of the null block",
|
.help = "size of the null block",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.name = NULL_OPT_LATENCY,
|
|
||||||
.type = QEMU_OPT_NUMBER,
|
|
||||||
.help = "nanoseconds (approximated) to wait "
|
|
||||||
"before completing request",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = NULL_OPT_ZEROES,
|
|
||||||
.type = QEMU_OPT_BOOL,
|
|
||||||
.help = "return zeroes when read",
|
|
||||||
},
|
|
||||||
{ /* end of list */ }
|
{ /* end of list */ }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -59,21 +39,13 @@ static int null_file_open(BlockDriverState *bs, QDict *options, int flags,
|
|||||||
{
|
{
|
||||||
QemuOpts *opts;
|
QemuOpts *opts;
|
||||||
BDRVNullState *s = bs->opaque;
|
BDRVNullState *s = bs->opaque;
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
|
||||||
qemu_opts_absorb_qdict(opts, options, &error_abort);
|
qemu_opts_absorb_qdict(opts, options, &error_abort);
|
||||||
s->length =
|
s->length =
|
||||||
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
|
qemu_opt_get_size(opts, BLOCK_OPT_SIZE, 1 << 30);
|
||||||
s->latency_ns =
|
|
||||||
qemu_opt_get_number(opts, NULL_OPT_LATENCY, 0);
|
|
||||||
if (s->latency_ns < 0) {
|
|
||||||
error_setg(errp, "latency-ns is invalid");
|
|
||||||
ret = -EINVAL;
|
|
||||||
}
|
|
||||||
s->read_zeroes = qemu_opt_get_bool(opts, NULL_OPT_ZEROES, false);
|
|
||||||
qemu_opts_del(opts);
|
qemu_opts_del(opts);
|
||||||
return ret;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void null_close(BlockDriverState *bs)
|
static void null_close(BlockDriverState *bs)
|
||||||
@@ -86,45 +58,28 @@ static int64_t null_getlength(BlockDriverState *bs)
|
|||||||
return s->length;
|
return s->length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_common(BlockDriverState *bs)
|
|
||||||
{
|
|
||||||
BDRVNullState *s = bs->opaque;
|
|
||||||
|
|
||||||
if (s->latency_ns) {
|
|
||||||
co_aio_sleep_ns(bdrv_get_aio_context(bs), QEMU_CLOCK_REALTIME,
|
|
||||||
s->latency_ns);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
static coroutine_fn int null_co_readv(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
BDRVNullState *s = bs->opaque;
|
return 0;
|
||||||
|
|
||||||
if (s->read_zeroes) {
|
|
||||||
qemu_iovec_memset(qiov, 0, 0, nb_sectors * BDRV_SECTOR_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null_co_common(bs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
static coroutine_fn int null_co_writev(BlockDriverState *bs,
|
||||||
int64_t sector_num, int nb_sectors,
|
int64_t sector_num, int nb_sectors,
|
||||||
QEMUIOVector *qiov)
|
QEMUIOVector *qiov)
|
||||||
{
|
{
|
||||||
return null_co_common(bs);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
static coroutine_fn int null_co_flush(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
return null_co_common(bs);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BlockAIOCB common;
|
BlockAIOCB common;
|
||||||
QEMUTimer timer;
|
QEMUBH *bh;
|
||||||
} NullAIOCB;
|
} NullAIOCB;
|
||||||
|
|
||||||
static const AIOCBInfo null_aiocb_info = {
|
static const AIOCBInfo null_aiocb_info = {
|
||||||
@@ -135,14 +90,7 @@ static void null_bh_cb(void *opaque)
|
|||||||
{
|
{
|
||||||
NullAIOCB *acb = opaque;
|
NullAIOCB *acb = opaque;
|
||||||
acb->common.cb(acb->common.opaque, 0);
|
acb->common.cb(acb->common.opaque, 0);
|
||||||
qemu_aio_unref(acb);
|
qemu_bh_delete(acb->bh);
|
||||||
}
|
|
||||||
|
|
||||||
static void null_timer_cb(void *opaque)
|
|
||||||
{
|
|
||||||
NullAIOCB *acb = opaque;
|
|
||||||
acb->common.cb(acb->common.opaque, 0);
|
|
||||||
timer_deinit(&acb->timer);
|
|
||||||
qemu_aio_unref(acb);
|
qemu_aio_unref(acb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,19 +99,10 @@ static inline BlockAIOCB *null_aio_common(BlockDriverState *bs,
|
|||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
NullAIOCB *acb;
|
NullAIOCB *acb;
|
||||||
BDRVNullState *s = bs->opaque;
|
|
||||||
|
|
||||||
acb = qemu_aio_get(&null_aiocb_info, bs, cb, opaque);
|
acb = qemu_aio_get(&null_aiocb_info, bs, cb, opaque);
|
||||||
/* Only emulate latency after vcpu is running. */
|
acb->bh = aio_bh_new(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
||||||
if (s->latency_ns) {
|
qemu_bh_schedule(acb->bh);
|
||||||
aio_timer_init(bdrv_get_aio_context(bs), &acb->timer,
|
|
||||||
QEMU_CLOCK_REALTIME, SCALE_NS,
|
|
||||||
null_timer_cb, acb);
|
|
||||||
timer_mod_ns(&acb->timer,
|
|
||||||
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + s->latency_ns);
|
|
||||||
} else {
|
|
||||||
aio_bh_schedule_oneshot(bdrv_get_aio_context(bs), null_bh_cb, acb);
|
|
||||||
}
|
|
||||||
return &acb->common;
|
return &acb->common;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,12 +112,6 @@ static BlockAIOCB *null_aio_readv(BlockDriverState *bs,
|
|||||||
BlockCompletionFunc *cb,
|
BlockCompletionFunc *cb,
|
||||||
void *opaque)
|
void *opaque)
|
||||||
{
|
{
|
||||||
BDRVNullState *s = bs->opaque;
|
|
||||||
|
|
||||||
if (s->read_zeroes) {
|
|
||||||
qemu_iovec_memset(qiov, 0, 0, nb_sectors * BDRV_SECTOR_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null_aio_common(bs, cb, opaque);
|
return null_aio_common(bs, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,44 +131,6 @@ static BlockAIOCB *null_aio_flush(BlockDriverState *bs,
|
|||||||
return null_aio_common(bs, cb, opaque);
|
return null_aio_common(bs, cb, opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int null_reopen_prepare(BDRVReopenState *reopen_state,
|
|
||||||
BlockReopenQueue *queue, Error **errp)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t coroutine_fn null_co_get_block_status(BlockDriverState *bs,
|
|
||||||
int64_t sector_num,
|
|
||||||
int nb_sectors, int *pnum,
|
|
||||||
BlockDriverState **file)
|
|
||||||
{
|
|
||||||
BDRVNullState *s = bs->opaque;
|
|
||||||
off_t start = sector_num * BDRV_SECTOR_SIZE;
|
|
||||||
|
|
||||||
*pnum = nb_sectors;
|
|
||||||
*file = bs;
|
|
||||||
|
|
||||||
if (s->read_zeroes) {
|
|
||||||
return BDRV_BLOCK_OFFSET_VALID | start | BDRV_BLOCK_ZERO;
|
|
||||||
} else {
|
|
||||||
return BDRV_BLOCK_OFFSET_VALID | start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void null_refresh_filename(BlockDriverState *bs, QDict *opts)
|
|
||||||
{
|
|
||||||
QINCREF(opts);
|
|
||||||
qdict_del(opts, "filename");
|
|
||||||
|
|
||||||
if (!qdict_size(opts)) {
|
|
||||||
snprintf(bs->exact_filename, sizeof(bs->exact_filename), "%s://",
|
|
||||||
bs->drv->format_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
qdict_put_str(opts, "driver", bs->drv->format_name);
|
|
||||||
bs->full_open_options = opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BlockDriver bdrv_null_co = {
|
static BlockDriver bdrv_null_co = {
|
||||||
.format_name = "null-co",
|
.format_name = "null-co",
|
||||||
.protocol_name = "null-co",
|
.protocol_name = "null-co",
|
||||||
@@ -248,11 +143,6 @@ static BlockDriver bdrv_null_co = {
|
|||||||
.bdrv_co_readv = null_co_readv,
|
.bdrv_co_readv = null_co_readv,
|
||||||
.bdrv_co_writev = null_co_writev,
|
.bdrv_co_writev = null_co_writev,
|
||||||
.bdrv_co_flush_to_disk = null_co_flush,
|
.bdrv_co_flush_to_disk = null_co_flush,
|
||||||
.bdrv_reopen_prepare = null_reopen_prepare,
|
|
||||||
|
|
||||||
.bdrv_co_get_block_status = null_co_get_block_status,
|
|
||||||
|
|
||||||
.bdrv_refresh_filename = null_refresh_filename,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static BlockDriver bdrv_null_aio = {
|
static BlockDriver bdrv_null_aio = {
|
||||||
@@ -267,11 +157,6 @@ static BlockDriver bdrv_null_aio = {
|
|||||||
.bdrv_aio_readv = null_aio_readv,
|
.bdrv_aio_readv = null_aio_readv,
|
||||||
.bdrv_aio_writev = null_aio_writev,
|
.bdrv_aio_writev = null_aio_writev,
|
||||||
.bdrv_aio_flush = null_aio_flush,
|
.bdrv_aio_flush = null_aio_flush,
|
||||||
.bdrv_reopen_prepare = null_reopen_prepare,
|
|
||||||
|
|
||||||
.bdrv_co_get_block_status = null_co_get_block_status,
|
|
||||||
|
|
||||||
.bdrv_refresh_filename = null_refresh_filename,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_null_init(void)
|
static void bdrv_null_init(void)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user