usb重定向qemu前端处理

发布于:2025-04-22 ⋅ 阅读:(52) ⋅ 点赞:(0)
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);
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到