1、qemu添加spicevmc前端时会创建vmc通道。
-chardev 'spicevmc,id=usbredirchardev0,name=usbredir'
red::shared_ptr<RedCharDevice>
spicevmc_device_connect(RedsState *reds, SpiceCharDeviceInstance *sin, uint8_t channel_type)
{auto channel(red_vmc_channel_new(reds, channel_type)); //创建RedVmcChannel通道if (!channel) {return red::shared_ptr<RedCharDevice>();}auto dev = red::make_shared<RedCharDeviceSpiceVmc>(sin, reds, channel.get()); //设备与通道绑定channel->chardev_sin = sin;return dev;
}
2、RedVmcChannel中的rcc成员变量说明Channel与client是1对1的关系。
struct RedVmcChannel: public RedChannel
{RedVmcChannel(RedsState *reds, uint32_t type, uint32_t id);~RedVmcChannel() override;void on_connect(RedClient *client, RedStream *stream, int migration, RedChannelCapabilities *caps) override;VmcChannelClient *rcc;RedCharDevice *chardev; /* weak */SpiceCharDeviceInstance *chardev_sin;red::shared_ptr<RedVmcPipeItem> pipe_item;RedCharDeviceWriteBuffer *recv_from_client_buf;uint8_t port_opened;uint32_t queued_data;RedStatCounter in_data;RedStatCounter in_compressed;RedStatCounter in_decompressed;RedStatCounter out_data;RedStatCounter out_compressed;RedStatCounter out_uncompressed;
};
3、usbredir客户端连接上,创建 VmcChannelClient。
void RedVmcChannel::on_connect(RedClient *client, RedStream *stream, int migration,RedChannelCapabilities *caps)
{RedVmcChannel *vmc_channel;SpiceCharDeviceInstance *sin;SpiceCharDeviceInterface *sif;vmc_channel = this;sin = vmc_channel->chardev_sin;if (rcc) {red_channel_warning(this, "channel client (%p) already connected, refusing second connection", rcc);// TODO: notify client in advance about the in use channel using// SPICE_MSG_MAIN_CHANNEL_IN_USE (for example)red_stream_free(stream);return;}rcc = vmc_channel_client_create(this, client, stream, caps);if (!rcc) {return;}vmc_channel->queued_data = 0;rcc->ack_zero_messages_window();if (strcmp(sin->subtype, "port") == 0) {spicevmc_port_send_init(rcc);}if (!vmc_channel->chardev->client_add(reinterpret_cast<RedCharDeviceClientOpaque *>(client), FALSE, 0, ~0, ~0, rcc->is_waiting_for_migrate_data())) {spice_warning("failed to add client to spicevmc");rcc->disconnect();return;}sif = spice_char_device_get_interface(sin);if (sif->state) {sif->state(sin, 1);}
}
4、处理VmcChannelClient客户端消息,写入chardev设备。
bool VmcChannelClient::handle_message(uint16_t type, uint32_t size, void *msg)
{/* NOTE: *msg free by g_free() (when cb to VmcChannelClient::release_recv_buf* with the compressed msg type) */RedVmcChannel *channel;SpiceCharDeviceInterface *sif;channel = get_channel();sif = spice_char_device_get_interface(channel->chardev_sin);switch (type) {case SPICE_MSGC_SPICEVMC_DATA:spice_assert(channel->recv_from_client_buf->buf == msg);stat_inc_counter(channel->in_data, size);channel->recv_from_client_buf->buf_used = size;channel->chardev->write_buffer_add(channel->recv_from_client_buf);channel->recv_from_client_buf = nullptr;break;case SPICE_MSGC_SPICEVMC_COMPRESSED_DATA:return handle_compressed_msg(channel, this, static_cast<SpiceMsgCompressedData *>(msg));break;case SPICE_MSGC_PORT_EVENT:if (size != sizeof(uint8_t)) {spice_warning("bad port event message size");return FALSE;}if (sif->base.minor_version >= 2 && sif->event != nullptr)sif->event(channel->chardev_sin, *static_cast<uint8_t *>(msg));break;default:return RedChannelClient::handle_message(type, size, msg);}return TRUE;
}
5、数据添加到队列中。
void RedCharDevice::write_buffer_add(RedCharDeviceWriteBuffer *write_buf)
{/* caller shouldn't add buffers for client that was removed */if (write_buf->priv->origin == WRITE_BUFFER_ORIGIN_CLIENT &&!red_char_device_client_find(this, write_buf->priv->client)) {g_warning("client not found: this %p client %p", this, write_buf->priv->client);red_char_device_write_buffer_unref(write_buf);return;}g_queue_push_head(&priv->write_queue, write_buf);write_to_device();
}
6、最终写入qemu的spicevmc前端。
int RedCharDevice::write_to_device()
{SpiceCharDeviceInterface *sif;int total = 0;int n;if (!priv->running || priv->wait_for_migrate_data || !priv->sin) {return 0;}/* protect against recursion with red_char_device_wakeup */if (priv->during_write_to_device++ > 0) {return 0;}red::shared_ptr<RedCharDevice> hold_dev(this);if (priv->write_to_dev_timer) {red_timer_cancel(priv->write_to_dev_timer);}sif = spice_char_device_get_interface(priv->sin);while (priv->running) {uint32_t write_len;if (!priv->cur_write_buf) {priv->cur_write_buf =static_cast<RedCharDeviceWriteBuffer *>(g_queue_pop_tail(&priv->write_queue));if (!priv->cur_write_buf)break;priv->cur_write_buf_pos = priv->cur_write_buf->buf;}write_len = priv->cur_write_buf->buf + priv->cur_write_buf->buf_used - priv->cur_write_buf_pos;n = sif->write(priv->sin, priv->cur_write_buf_pos, write_len);if (n <= 0) {if (priv->during_write_to_device > 1) {priv->during_write_to_device = 1;continue; /* a wakeup might have been called during the write - make sure it doesn't get lost */}break;}total += n;write_len -= n;if (!write_len) {write_buffer_release(&priv->cur_write_buf);continue;}priv->cur_write_buf_pos += n;}/* retry writing as long as the write queue is not empty */if (priv->running) {if (priv->cur_write_buf) {if (priv->write_to_dev_timer) {red_timer_start(priv->write_to_dev_timer,CHAR_DEVICE_WRITE_TO_TIMEOUT);}} else {spice_assert(g_queue_is_empty(&priv->write_queue));}priv->active = priv->active || total;}priv->during_write_to_device = 0;return total;
}
7、spice vmc前端注册。
static SpiceCharDeviceInterface vmc_interface = {.base.type = SPICE_INTERFACE_CHAR_DEVICE,.base.description = "spice virtual channel char device",.base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,.base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,.state = vmc_state,.write = vmc_write,.read = vmc_read,.event = vmc_event,.flags = SPICE_CHAR_DEVICE_NOTIFY_WRITABLE,
};
8、spice vmc前端写函数实现。
static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
{SpiceChardev *scd = container_of(sin, SpiceChardev, sin);Chardev *chr = CHARDEV(scd);ssize_t out = 0;ssize_t last_out;uint8_t* p = (uint8_t*)buf;while (len > 0) {int can_write = qemu_chr_be_can_write(chr);last_out = MIN(len, can_write);if (last_out <= 0) {break;}qemu_chr_be_write(chr, p, last_out);out += last_out;len -= last_out;p += last_out;}trace_spice_vmc_write(out, len + out);return out;
}
9、调用qemu char前端模块的写。
void qemu_chr_be_write(Chardev *s, uint8_t *buf, int len)
{if (qemu_chr_replay(s)) {if (replay_mode == REPLAY_MODE_PLAY) {return;}replay_chr_be_write(s, buf, len);} else {qemu_chr_be_write_impl(s, buf, len);}
}
10、判断是否有后端设备连接并发送给后端设备
void qemu_chr_be_write_impl(Chardev *s, uint8_t *buf, int len)
{CharBackend *be = s->be;if (be && be->chr_read) {be->chr_read(be->opaque, buf, len);}
}