QEMU 启动配置和spice新增通道
spice新增继承port的通道
spice新增通道关联的相关模块有4部分
- spice portcal 协议
在enmus.h文件增加通道的枚举
enum {
SPICE_CHANNEL_MAIN = 1,
SPICE_CHANNEL_DISPLAY,
SPICE_CHANNEL_INPUTS,
SPICE_CHANNEL_CURSOR,
SPICE_CHANNEL_PLAYBACK,
SPICE_CHANNEL_RECORD,
SPICE_CHANNEL_TUNNEL,
SPICE_CHANNEL_SMARTCARD,
SPICE_CHANNEL_USBREDIR,
SPICE_CHANNEL_PORT,
SPICE_CHANNEL_WEBDAV,
// 这里新增
SPICE_END_CHANNEL
};
- spice-common 基础
spice-common有自动代码 生成器生成对应通道的解析函数,在spice.proto文件新增通道定义:
channel WebDAVChannel : PortChannel {
};
// 这里新增
//channel ??Channel : PortChannel {
//};
protocol Spice {
MainChannel main = 1;
DisplayChannel display;
InputsChannel inputs;
CursorChannel cursor;
PlaybackChannel playback;
RecordChannel record;
TunnelChannel tunnel;
SmartcardChannel smartcard;
UsbredirChannel usbredir;
PortChannel port;
WebDAVChannel webdav;
// 这里新增
};
- spice-server端
spice-server需要新增对于qemu启动时的设备名字对比,在向客户端启动main_handle_channel_list()的的通道type和id。
static red::shared_ptr<RedVmcChannel> red_vmc_channel_new(RedsState *reds, uint8_t channel_type)
{
switch (channel_type) {
case SPICE_CHANNEL_USBREDIR:
case SPICE_CHANNEL_WEBDAV:
case SPICE_CHANNEL_PORT:
// 这里新增对应的新通道枚举
break;
default:
g_error("Unsupported channel_type for red_vmc_channel_new(): %u", channel_type);
return red::shared_ptr<RedVmcChannel>();
}
int id = reds_get_free_channel_id(reds, channel_type);
if (id < 0) {
g_warning("Free ID not found creating new VMC channel");
return red::shared_ptr<RedVmcChannel>();
}
return red::make_shared<RedVmcChannel>(reds, channel_type, id);
}
static const char *const channel_names[] = {
[ SPICE_CHANNEL_MAIN ] = "main",
[ SPICE_CHANNEL_DISPLAY ] = "display",
[ SPICE_CHANNEL_INPUTS ] = "inputs",
[ SPICE_CHANNEL_CURSOR ] = "cursor",
[ SPICE_CHANNEL_PLAYBACK ] = "playback",
[ SPICE_CHANNEL_RECORD ] = "record",
[ SPICE_CHANNEL_TUNNEL ] = "tunnel",
[ SPICE_CHANNEL_SMARTCARD] = "smartcard",
[ SPICE_CHANNEL_USBREDIR ] = "usbredir",
[ SPICE_CHANNEL_PORT ] = "port",
[ SPICE_CHANNEL_WEBDAV ] = "webdav",
// 这里新增对应的新通道枚举和名字
};
static int
spice_server_char_device_add_interface(SpiceServer *reds, SpiceBaseInstance *sin)
{
//............................
else if (strcmp(char_device->subtype, SUBTYPE_PORT) == 0) {
if (strcmp(char_device->portname, "org.spice-space.webdav.0") == 0) {
dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_WEBDAV);
} else if (strcmp(char_device->portname, "org.spice-space.stream.0") == 0) {
dev_state = stream_device_connect(reds, char_device);
} // 这里新增对应的新通的注册设备名字对比,进行对应的连接
else {
dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_PORT);
}
}
//............................
}
- spice-gtk端
// 首先类似webdavchannel继承portchannel
// 在通道创建新增对应的
SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
{
SpiceChannel *channel;
GType gtype = 0;
g_return_val_if_fail(s != NULL, NULL);
switch (type) {
//.................................
// 省略
//.................................
case SPICE_CHANNEL_PORT:
gtype = SPICE_TYPE_PORT_CHANNEL;
break;
// 对新增通道枚举判断
case ??:
gtype = ??;
break;
default:
SPICE_DEBUG("unsupported channel kind: %s: %d",
spice_channel_type_to_string(type), type);
return NULL;
}
channel = SPICE_CHANNEL(g_object_new(gtype,
"spice-session", s,
"channel-type", type,
"channel-id", id,
NULL));
// 该通道没有manager,手动对新增通道枚举判断进行连接
if(?? == type){
spice_channel_connect(channel);
}
return channel;
}
Qmue 启动配置
在编译好的qemu build路径下执行:
sudo ./qemu-system-x86_64 -machine pc,accel=kvm -cpu host,vme=on,ss=on,vmx=on,pdcm=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc-adjust=on,umip=on,stibp=on,arch-capabilities=on,xsaveopt=on,pdpe1gb=on,abm=on,ibpb=on,ibrs=on,amd-stibp=on,skip-l1dfl-vmentry=on,pschange-mc-no=on,hv-time=on,hv-relaxed=on,hv-vapic=on,hv-spinlocks=0x1fff,hv-vpindex=on -smp 4,sockets=1,cores=2,threads=2 -m 4G -device qemu-xhci -device virtio-serial-pci -netdev user,id=net0 -device virtio-net-pci,netdev=net0 -drive file=/home/w/share/win10.qcow2,if=virtio,format=qcow2 -boot c -display gtk -spice port=5901,disable-ticketing=on -device virtio-serial-pci,id=virtio-serial0,bus=pci.0 -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=fs_channnel,id=channel1,name=org.spice-space.fs.0 -chardev spiceport,name=org.spice-space.fs.0,id=fs_channnel -device ich9-usb-ehci1,id=usb -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,multifunction=on -chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1
其中这段比较重要,是开启一些hyper加速的,未开启会导致大量kvm_mmio操作导致运行异常卡顿
-cpu host,vme=on,ss=on,vmx=on,pdcm=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc-adjust=on,umip=on,stibp=on,arch-capabilities=on,xsaveopt=on,pdpe1gb=on,abm=on,ibpb=on,ibrs=on,amd-stibp=on,skip-l1dfl-vmentry=on,pschange-mc-no=on,hv-time=on,hv-relaxed=on,hv-vapic=on,hv-spinlocks=0x1fff,hv-vpindex=on
上面的Qemu虚拟的网络是一个虚拟内网地址,只可以向外访问无法访问反向访问,下面是开启桥接的方法:
nmcli con add type bridge con-name br0 ifname br0
nmcli con add type bridge-slave ifname enp1s0 master br0
nmcli con modify br0 ipv4.addresses 172.19.*.*/26
nmcli con modify br0 ipv4.gateway 172.19.0.254
nmcli con modify br0 ipv4.method manual
# 之后手动禁用之前使用enp1s0进行的连接,之后shh这台机器直接ssh到br0分配的ip
上面的命令在linux上建立桥接和转发。
sudo qemu-system-x86_64 -machine pc,accel=kvm -cpu host,vme=on,ss=on,vmx=on,pdcm=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc-adjust=on,umip=on,stibp=on,arch-capabilities=on,xsaveopt=on,pdpe1gb=on,abm=on,ibpb=on,ibrs=on,amd-stibp=on,skip-l1dfl-vmentry=on,pschange-mc-no=on,hv-time=on,hv-relaxed=on,hv-vapic=on,hv-spinlocks=0x1fff,hv-vpindex=on -smp 4,sockets=1,cores=2,threads=2 -m 4G -device qemu-xhci -device virtio-serial-pci -device virtio-net-pci,netdev=user0,mac=fa:16:3e:82:49:95 -netdev bridge,id=user0,br=br0 -drive file=/home/w/code/qemu-src/win10.qcow2,if=virtio,format=qcow2 -boot c -display gtk -spice port=5901,disable-ticketing=on -device virtio-serial-pci,id=virtio-serial0,bus=pci.0 -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=fs_channnel,id=channel1,name=org.spice-space.fs.0 -chardev spiceport,name=org.spice-space.fs.0,id=fs_channnel
桥接启动(需要sudo)
Win7启动
使用win7的iso安装的系统因该缺少virtio的相关驱动,使用其他启动
sudo qemu-system-x86_64 -machine pc,accel=kvm -cpu host,vme=on,ss=on,vmx=on,pdcm=on,f16c=on,rdrand=on,hypervisor=on,arat=on,tsc-adjust=on,umip=on,stibp=on,arch-capabilities=on,xsaveopt=on,pdpe1gb=on,abm=on,ibpb=on,ibrs=on,amd-stibp=on,skip-l1dfl-vmentry=on,pschange-mc-no=on,hv-time=on,hv-relaxed=on,hv-vapic=on,hv-spinlocks=0x1fff,hv-vpindex=on -smp 4,sockets=1,cores=2,threads=2 -m 4G -device qemu-xhci -device virtio-serial-pci -netdev user,id=net0 -device virtio-net-pci,netdev=net0 -drive file=/home/w/code/qemu-src/win7.qcow2,format=qcow2 -spice port=5901,disable-ticketing=on -device virtio-serial-pci,id=virtio-serial0,bus=pci.0 -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=fs_channnel,id=channel1,name=org.spice-space.fs.0 -chardev spiceport,name=org.spice-space.fs.0,id=fs_channnel -cameradev proxy,id=camera0 -device usb-video,cameradev=camera0,terminal=camera -chardev spicevmc,name=usbredir,id=usbredirchardev1 -device usb-redir,chardev=usbredirchardev1,cameradev=camera0,id=usbredirdev1
Client SPICE CHANNEL DISCONNECT PROCESS
First call spice_session_disconnect disconnect SpiceSession.
void spice_session_disconnect(SpiceSession *session) {
SpiceSessionPrivate *s;
g_return_if_fail(SPICE_IS_SESSION(session));
s = session->priv;
SPICE_DEBUG("session: disconnecting %u", s->disconnecting);
if (s->disconnecting != 0)
return;
g_object_ref(session);
s->disconnecting = spice_g_idle_add((GSourceFunc)session_disconnect_idle, session);
}
then call real disconnect in another thread:
static gboolean session_disconnect_idle(SpiceSession *self) {
SpiceSessionPrivate *s = self->priv;
session_disconnect(self, FALSE);
s->disconnecting = 0;
g_object_unref(self);
return FALSE;
}
then disconnect all channel in this session, if main channel will call spice_channel_disconnect otherwise will call spice_session_channel_destroy:
static void session_disconnect(SpiceSession *self, gboolean keep_main) {
SpiceSessionPrivate *s;
s = self->priv;
for (GList *l = s->channels; l != NULL; ) {
SpiceChannel *channel = l->data;
l = l->next;
if (keep_main && channel == s->cmain) {
spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
} else {
spice_session_channel_destroy(self, channel);
}
}
s->connection_id = 0;
g_clear_pointer(&s->name, g_free);
memset(s->uuid, 0, sizeof(s->uuid));
spice_session_abort_migration(self);
}
in function spice_session_channel_destroy will first emit signals[SPICE_SESSION_CHANNEL_DESTROY] notice Qt then call spice_channel_disconnect
static void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel)
{
g_return_if_fail(SPICE_IS_SESSION(session));
g_return_if_fail(SPICE_IS_CHANNEL(channel));
SpiceSessionPrivate *s = session->priv;
GList *l;
if (s->migration_left)
s->migration_left = g_list_remove(s->migration_left, channel);
for (l = s->channels; l != NULL; l = l->next) {
if (l->data == channel)
break;
}
g_return_if_fail(l != NULL);
if (channel == s->cmain) {
CHANNEL_DEBUG(channel, "the session lost the main channel");
s->cmain = NULL;
}
s->channels = g_list_delete_link(s->channels, l);
g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_DESTROY], 0, channel);
g_clear_object(&channel->priv->session);
spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
/* Wait until the channel is properly freed so that we can emit a
* 'disconnected' signal */
s->channels_destroying++;
g_object_weak_ref(G_OBJECT(channel), channel_finally_destroyed, g_object_ref(session));
g_object_unref(channel);
}
in function spice_channel_disconnect will set c->has_error to true, spice_channel_coroutine loop will exit:
void spice_channel_disconnect(SpiceChannel *channel, SpiceChannelEvent reason)
{
SpiceChannelPrivate *c;
CHANNEL_DEBUG(channel, "channel disconnect %u", reason);
g_return_if_fail(SPICE_IS_CHANNEL(channel));
g_return_if_fail(channel->priv != NULL);
c = channel->priv;
if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED)
return;
if (reason == SPICE_CHANNEL_SWITCHING)
c->state = SPICE_CHANNEL_STATE_SWITCHING;
c->has_error = TRUE; /* break the loop */
if (c->state == SPICE_CHANNEL_STATE_MIGRATING) {
c->state = SPICE_CHANNEL_STATE_READY;
} else {
SPICE_CHANNEL_GET_CLASS(channel)->channel_ready_close(channel);
spice_channel_wakeup(channel, TRUE);
}
if (reason != SPICE_CHANNEL_NONE)
g_signal_emit(G_OBJECT(channel), signals[SPICE_CHANNEL_EVENT], 0, reason);
}
in function spice_channel_coroutine will quit while loop spice_channel_iterate. then call spice_channel_reset reset channel and call spice_channel_delayed_unref free channel.
static void *spice_channel_coroutine(void *data)
{
// .....
while (spice_channel_iterate(channel))
;
cleanup:
CHANNEL_DEBUG(channel, "Coroutine exit %s", c->name);
spice_channel_reset(channel, FALSE);
if (c->state == SPICE_CHANNEL_STATE_RECONNECTING ||
c->state == SPICE_CHANNEL_STATE_SWITCHING) {
g_warn_if_fail(c->event == SPICE_CHANNEL_NONE);
if (channel_connect(channel, c->tls)) {
g_object_unref(channel);
return NULL;
}
c->event = SPICE_CHANNEL_ERROR_CONNECT;
}
spice_g_idle_add(spice_channel_delayed_unref, channel);
/* Co-routine exits now - the SpiceChannel object may no longer exist,
so don't do anything else now unless you like SEGVs */
return NULL;
}
Then automatically dispose of unreferenced channels.
static gboolean spice_channel_delayed_unref(gpointer data)
{
SpiceChannel *channel = SPICE_CHANNEL(data);
SpiceChannelPrivate *c = channel->priv;
gboolean was_ready = c->state == SPICE_CHANNEL_STATE_READY;
CHANNEL_DEBUG(channel, "Delayed unref channel %p", channel);
g_return_val_if_fail(c->coroutine.coroutine.exited == TRUE, FALSE);
c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
if (c->event != SPICE_CHANNEL_NONE) {
g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, c->event);
c->event = SPICE_CHANNEL_NONE;
g_clear_error(&c->error);
}
if (was_ready)
g_coroutine_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, SPICE_CHANNEL_CLOSED);
g_object_unref(channel);
return FALSE;
}
USB Video device descriptor in QEMU
// top
static const USBDesc desc_video = {
.id = {
.idVendor = USBVIDEO_VENDOR_NUM,
.idProduct = USBVIDEO_PRODUCT_NUM,
.bcdDevice = 0x0019,//0,
.iManufacturer = 0x00, //STRING_MANUFACTURER,
.iProduct = 0x02, //STRING_PRODUCT,
.iSerialNumber = 0x01, //STRING_SERIALNUMBER,
},
.full = &desc_device_full,
.high = &desc_device_high,
.str = usb_video_stringtable,
};
struct USBDescDevice {
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint8_t bNumConfigurations;
const USBDescConfig *confs;
};
static const USBDescDevice desc_device_full = {
.bcdUSB = 0x0100,
.bDeviceClass = USB_CLASS_MISCELLANEOUS,
.bDeviceSubClass = 2,
.bDeviceProtocol = 1, /* Interface Association */
.bMaxPacketSize0 = 8,
.bNumConfigurations = 1,
.confs = (USBDescConfig[]) {
{
.bNumInterfaces = 2,
.bConfigurationValue = 1,
.iConfiguration = 0, //STRING_CONFIG,
.bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
.bMaxPower = 0x32,
.nif_groups = ARRAY_SIZE(desc_if_groups),
.if_groups = desc_if_groups,
.nif = ARRAY_SIZE(desc_ifaces),
.ifs = desc_ifaces,
},
},
};
typedef struct vcam_dev_descriptor
{
SF_PACK_DEF_VAR(uint8_t, bLength);
SF_PACK_DEF_VAR(uint8_t, bDescriptorType);
SF_PACK_DEF_VAR(uint16_t, bcdUSB);
SF_PACK_DEF_VAR(uint8_t, bDeviceClass);
SF_PACK_DEF_VAR(uint8_t, bDeviceSubClass);
SF_PACK_DEF_VAR(uint8_t, bDeviceProtocol);
SF_PACK_DEF_VAR(uint8_t, bMaxPacketSize0);
SF_PACK_DEF_VAR(uint16_t, idVendor);
SF_PACK_DEF_VAR(uint16_t, idProduct);
SF_PACK_DEF_VAR(uint16_t, bcdDevice);
SF_PACK_DEF_VAR(uint8_t, iManufacturer);
SF_PACK_DEF_VAR(uint8_t, iProduct);
SF_PACK_DEF_VAR(uint8_t, iSerialNumber);
SF_PACK_DEF_VAR(uint8_t, bNumConfigurations);
} vcam_dev_descriptor;
// level 1
static const USBDescDevice desc_device_full = {
.bcdUSB = 0x0100,
.bDeviceClass = USB_CLASS_MISCELLANEOUS,
.bDeviceSubClass = 2,
.bDeviceProtocol = 1, /* Interface Association */
.bMaxPacketSize0 = 8,
.bNumConfigurations = 1,
.confs = (USBDescConfig[]) {
{
.bNumInterfaces = 2,
.bConfigurationValue = 1,
.iConfiguration = 0, //STRING_CONFIG,
.bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
.bMaxPower = 0x32,
.nif_groups = ARRAY_SIZE(desc_if_groups),
.if_groups = desc_if_groups,
.nif = ARRAY_SIZE(desc_ifaces),
.ifs = desc_ifaces,
},
},
};
// USBDescConfig
struct USBDescConfig {
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
/* grouped interfaces */
uint8_t nif_groups;
const USBDescIfaceAssoc *if_groups;
/* "normal" interfaces */
uint8_t nif;
const USBDescIface *ifs;
};
typedef struct vcam_config_descriptor
{
SF_PACK_DEF_VAR(uint8_t, bLength);
SF_PACK_DEF_VAR(uint8_t, bDescriptorType);
SF_PACK_DEF_VAR(uint16_t, wTotalLength);
SF_PACK_DEF_VAR(uint8_t, bConfigurationValue);
SF_PACK_DEF_VAR(uint8_t, iConfiguration);
SF_PACK_DEF_VAR(uint8_t, bmAttributes);
SF_PACK_DEF_VAR(uint8_t, MaxPower);
SF_PACK_DEF_OBJS(vcam_other_descriptor, arrOtherDescs);
SF_PACK_DEF_OBJS(vcam_interface, arrIFDescs);
} vcam_config_descriptor;
