news 2026/7/8 3:10:25

qxl驱动的内存区域注册到spice笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
qxl驱动的内存区域注册到spice笔记
1、qxl前端驱动
1.1、qxl驱动通过probe函数初始化qxl设备。
static int qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct qxl_device *qdev; int ret; if (pdev->revision < 4) { DRM_ERROR("qxl too old, doesn't support client_monitors_config, use xf86-video-qxl in user mode"); return -EINVAL; /* TODO: ENODEV ? */ } qdev = devm_drm_dev_alloc(&pdev->dev, &qxl_driver, struct qxl_device, ddev); if (IS_ERR(qdev)) { pr_err("Unable to init drm dev"); return -ENOMEM; } ret = pci_enable_device(pdev); if (ret) return ret; ret = drm_fb_helper_remove_conflicting_pci_framebuffers(pdev, "qxl"); if (ret) goto disable_pci; if (is_vga(pdev) && pdev->revision < 5) { ret = vga_get_interruptible(pdev, VGA_RSRC_LEGACY_IO); if (ret) { DRM_ERROR("can't get legacy vga ioports\n"); goto disable_pci; } } ret = qxl_device_init(qdev, pdev); if (ret) goto put_vga; ret = qxl_modeset_init(qdev); if (ret) goto unload; drm_kms_helper_poll_init(&qdev->ddev); /* Complete initialization. */ ret = drm_dev_register(&qdev->ddev, ent->driver_data); if (ret) goto modeset_cleanup; drm_fbdev_generic_setup(&qdev->ddev, 32); return 0; modeset_cleanup: qxl_modeset_fini(qdev); unload: qxl_device_fini(qdev); put_vga: if (is_vga(pdev) && pdev->revision < 5) vga_put(pdev, VGA_RSRC_LEGACY_IO); disable_pci: pci_disable_device(pdev); return ret; }
1.2、初始化qxl设备时通过setup_slot函数注册内存显示区域。
int qxl_device_init(struct qxl_device *qdev, struct pci_dev *pdev) { int r, sb; qdev->ddev.pdev = pdev; pci_set_drvdata(pdev, &qdev->ddev); mutex_init(&qdev->gem.mutex); mutex_init(&qdev->update_area_mutex); mutex_init(&qdev->release_mutex); mutex_init(&qdev->surf_evict_mutex); qxl_gem_init(qdev); qdev->rom_base = pci_resource_start(pdev, 2); qdev->rom_size = pci_resource_len(pdev, 2); qdev->vram_base = pci_resource_start(pdev, 0); qdev->io_base = pci_resource_start(pdev, 3); qdev->vram_mapping = io_mapping_create_wc(qdev->vram_base, pci_resource_len(pdev, 0)); if (!qdev->vram_mapping) { pr_err("Unable to create vram_mapping"); return -ENOMEM; } if (pci_resource_len(pdev, 4) > 0) { /* 64bit surface bar present */ sb = 4; qdev->surfaceram_base = pci_resource_start(pdev, sb); qdev->surfaceram_size = pci_resource_len(pdev, sb); qdev->surface_mapping = io_mapping_create_wc(qdev->surfaceram_base, qdev->surfaceram_size); } if (qdev->surface_mapping == NULL) { /* 64bit surface bar not present (or mapping failed) */ sb = 1; qdev->surfaceram_base = pci_resource_start(pdev, sb); qdev->surfaceram_size = pci_resource_len(pdev, sb); qdev->surface_mapping = io_mapping_create_wc(qdev->surfaceram_base, qdev->surfaceram_size); if (!qdev->surface_mapping) { pr_err("Unable to create surface_mapping"); r = -ENOMEM; goto vram_mapping_free; } } DRM_DEBUG_KMS("qxl: vram %llx-%llx(%dM %dk), surface %llx-%llx(%dM %dk, %s)\n", (unsigned long long)qdev->vram_base, (unsigned long long)pci_resource_end(pdev, 0), (int)pci_resource_len(pdev, 0) / 1024 / 1024, (int)pci_resource_len(pdev, 0) / 1024, (unsigned long long)qdev->surfaceram_base, (unsigned long long)pci_resource_end(pdev, sb), (int)qdev->surfaceram_size / 1024 / 1024, (int)qdev->surfaceram_size / 1024, (sb == 4) ? "64bit" : "32bit"); qdev->rom = ioremap(qdev->rom_base, qdev->rom_size); if (!qdev->rom) { pr_err("Unable to ioremap ROM\n"); r = -ENOMEM; goto surface_mapping_free; } if (!qxl_check_device(qdev)) { r = -ENODEV; goto rom_unmap; } r = qxl_bo_init(qdev); if (r) { DRM_ERROR("bo init failed %d\n", r); goto rom_unmap; } int header_offset = qdev->rom->ram_header_offset; int vram_size = pci_resource_len(pdev, 0); qdev->ram_header = ioremap(qdev->vram_base + header_offset, sizeof(*qdev->ram_header)); if (!qdev->ram_header) { DRM_ERROR("Unable to ioremap RAM header\n"); r = -ENOMEM; goto bo_fini; } qdev->command_ring = qxl_ring_create(&(qdev->ram_header->cmd_ring_hdr), sizeof(struct qxl_command), QXL_COMMAND_RING_SIZE, qdev->io_base + QXL_IO_NOTIFY_CMD, false, &qdev->display_event); if (!qdev->command_ring) { DRM_ERROR("Unable to create command ring\n"); r = -ENOMEM; goto ram_header_unmap; } qdev->cursor_ring = qxl_ring_create( &(qdev->ram_header->cursor_ring_hdr), sizeof(struct qxl_command), QXL_CURSOR_RING_SIZE, qdev->io_base + QXL_IO_NOTIFY_CURSOR, false, &qdev->cursor_event); if (!qdev->cursor_ring) { DRM_ERROR("Unable to create cursor ring\n"); r = -ENOMEM; goto command_ring_free; } qdev->release_ring = qxl_ring_create( &(qdev->ram_header->release_ring_hdr), sizeof(uint64_t), QXL_RELEASE_RING_SIZE, 0, true, NULL); if (!qdev->release_ring) { DRM_ERROR("Unable to create release ring\n"); r = -ENOMEM; goto cursor_ring_free; } idr_init(&qdev->release_idr); spin_lock_init(&qdev->release_idr_lock); spin_lock_init(&qdev->release_lock); idr_init(&qdev->surf_id_idr); spin_lock_init(&qdev->surf_id_idr_lock); mutex_init(&qdev->async_io_mutex); /* reset the device into a known state - no memslots, no primary * created, no surfaces. */ qxl_io_reset(qdev); /* must initialize irq before first async io - slot creation */ r = qxl_irq_init(qdev); if (r) { DRM_ERROR("Unable to init qxl irq\n"); goto release_ring_free; } /* * Note that virtual is surface0. We rely on the single ioremap done * before. */ setup_slot(qdev, &qdev->main_slot, 0, "main", (unsigned long)qdev->vram_base, (unsigned long)qdev->rom->ram_header_offset); setup_slot(qdev, &qdev->surfaces_slot, 1, "surfaces", (unsigned long)qdev->surfaceram_base, (unsigned long)qdev->surfaceram_size); INIT_WORK(&qdev->gc_work, qxl_gc_work); return 0; release_ring_free: qxl_ring_free(qdev->release_ring); cursor_ring_free: qxl_ring_free(qdev->cursor_ring); command_ring_free: qxl_ring_free(qdev->command_ring); ram_header_unmap: iounmap(qdev->ram_header); bo_fini: qxl_bo_fini(qdev); rom_unmap: iounmap(qdev->rom); surface_mapping_free: io_mapping_free(qdev->surface_mapping); vram_mapping_free: io_mapping_free(qdev->vram_mapping); return r; }
1.3、驱动最终调用qxl_io_memslot_add函数将QXL_IO_MEMSLOT_ADD_ASYNC命令给到qxl设备。
void qxl_io_memslot_add(struct qxl_device *qdev, uint8_t id) { DRM_DEBUG_DRIVER("qxl_memslot_add %d\n", id); wait_for_io_cmd(qdev, id, QXL_IO_MEMSLOT_ADD_ASYNC); } static void setup_hw_slot(struct qxl_device *qdev, struct qxl_memslot *slot) { if (!qdev->ram_header || !qdev->rom) { DRM_ERROR("qxl: null ram_header or rom\n"); return; } qdev->ram_header->mem_slot.mem_start = slot->start_phys_addr; qdev->ram_header->mem_slot.mem_end = slot->start_phys_addr + slot->size; qxl_io_memslot_add(qdev, qdev->rom->slots_start + slot->index); } static void setup_slot(struct qxl_device *qdev, struct qxl_memslot *slot, unsigned int slot_index, const char *slot_name, unsigned long start_phys_addr, unsigned long size) { uint64_t high_bits; slot->index = slot_index; slot->name = slot_name; slot->start_phys_addr = start_phys_addr; slot->size = size; setup_hw_slot(qdev, slot); slot->generation = qdev->rom->slot_generation; high_bits = (qdev->rom->slots_start + slot->index) << qdev->rom->slot_gen_bits; high_bits |= slot->generation; high_bits <<= (64 - (qdev->rom->slot_gen_bits + qdev->rom->slot_id_bits)); slot->high_bits = high_bits; DRM_INFO("slot %d (%s): base 0x%08lx, size 0x%08lx\n", slot->index, slot->name, (unsigned long)slot->start_phys_addr, (unsigned long)slot->size); }
2、qxl后端设备
2.1、接收到ioport指令QXL_IO_MEMSLOT_ADD
static void ioport_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) { PCIQXLDevice *d = opaque; uint32_t io_port = addr; qxl_async_io async = QXL_SYNC; uint32_t orig_io_port; if (d->guest_bug && io_port != QXL_IO_RESET) { return; } if (d->revision <= QXL_REVISION_STABLE_V10 && io_port > QXL_IO_FLUSH_RELEASE) { qxl_set_guest_bug(d, "unsupported io %d for revision %d\n", io_port, d->revision); return; } switch (io_port) { case QXL_IO_RESET: case QXL_IO_SET_MODE: case QXL_IO_MEMSLOT_ADD: case QXL_IO_MEMSLOT_DEL: case QXL_IO_CREATE_PRIMARY: case QXL_IO_UPDATE_IRQ: case QXL_IO_LOG: case QXL_IO_MEMSLOT_ADD_ASYNC: case QXL_IO_CREATE_PRIMARY_ASYNC: break; default: if (d->mode != QXL_MODE_VGA) { break; } trace_qxl_io_unexpected_vga_mode(d->id, addr, val, io_port_to_string(io_port)); /* be nice to buggy guest drivers */ if (io_port >= QXL_IO_UPDATE_AREA_ASYNC && io_port < QXL_IO_RANGE_SIZE) { qxl_send_events(d, QXL_INTERRUPT_IO_CMD); } return; } /* we change the io_port to avoid ifdeffery in the main switch */ orig_io_port = io_port; switch (io_port) { case QXL_IO_UPDATE_AREA_ASYNC: io_port = QXL_IO_UPDATE_AREA; goto async_common; case QXL_IO_MEMSLOT_ADD_ASYNC: io_port = QXL_IO_MEMSLOT_ADD; goto async_common; case QXL_IO_CREATE_PRIMARY_ASYNC: io_port = QXL_IO_CREATE_PRIMARY; goto async_common; case QXL_IO_DESTROY_PRIMARY_ASYNC: io_port = QXL_IO_DESTROY_PRIMARY; goto async_common; case QXL_IO_DESTROY_SURFACE_ASYNC: io_port = QXL_IO_DESTROY_SURFACE_WAIT; goto async_common; case QXL_IO_DESTROY_ALL_SURFACES_ASYNC: io_port = QXL_IO_DESTROY_ALL_SURFACES; goto async_common; case QXL_IO_FLUSH_SURFACES_ASYNC: case QXL_IO_MONITORS_CONFIG_ASYNC: async_common: async = QXL_ASYNC; WITH_QEMU_LOCK_GUARD(&d->async_lock) { if (d->current_async != QXL_UNDEFINED_IO) { qxl_set_guest_bug(d, "%d async started before last (%d) complete", io_port, d->current_async); return; } d->current_async = orig_io_port; } break; default: break; } trace_qxl_io_write(d->id, qxl_mode_to_string(d->mode), addr, io_port_to_string(addr), val, size, async); switch (io_port) { case QXL_IO_UPDATE_AREA: { QXLCookie *cookie = NULL; QXLRect update = d->ram->update_area; if (d->ram->update_surface > d->ssd.num_surfaces) { qxl_set_guest_bug(d, "QXL_IO_UPDATE_AREA: invalid surface id %d\n", d->ram->update_surface); break; } if (update.left >= update.right || update.top >= update.bottom || update.left < 0 || update.top < 0) { qxl_set_guest_bug(d, "QXL_IO_UPDATE_AREA: invalid area (%ux%u)x(%ux%u)\n", update.left, update.top, update.right, update.bottom); if (update.left == update.right || update.top == update.bottom) { /* old drivers may provide empty area, keep going */ qxl_clear_guest_bug(d); goto cancel_async; } break; } if (async == QXL_ASYNC) { cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO, QXL_IO_UPDATE_AREA_ASYNC); cookie->u.area = update; } qxl_spice_update_area(d, d->ram->update_surface, cookie ? &cookie->u.area : &update, NULL, 0, 0, async, cookie); break; } case QXL_IO_NOTIFY_CMD: qemu_spice_wakeup(&d->ssd); break; case QXL_IO_NOTIFY_CURSOR: qemu_spice_wakeup(&d->ssd); break; case QXL_IO_UPDATE_IRQ: qxl_update_irq(d); break; case QXL_IO_NOTIFY_OOM: if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { break; } d->oom_running = 1; qxl_spice_oom(d); d->oom_running = 0; break; case QXL_IO_SET_MODE: qxl_set_mode(d, val, 0); break; case QXL_IO_LOG: #ifdef CONFIG_MODULES /* * FIXME * trace_event_get_state_backends() does not work for modules, * it leads to "undefined symbol: qemu_qxl_io_log_semaphore" */ if (true) { #else if (trace_event_get_state_backends(TRACE_QXL_IO_LOG) || d->guestdebug) { #endif /* We cannot trust the guest to NUL terminate d->ram->log_buf */ char *log_buf = g_strndup((const char *)d->ram->log_buf, sizeof(d->ram->log_buf)); trace_qxl_io_log(d->id, log_buf); if (d->guestdebug) { fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), log_buf); } g_free(log_buf); } break; case QXL_IO_RESET: qxl_hard_reset(d, 0); break; case QXL_IO_MEMSLOT_ADD: if (val >= NUM_MEMSLOTS) { qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range"); break; } if (d->guest_slots[val].active) { qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: memory slot already active"); break; } d->guest_slots[val].slot = d->ram->mem_slot; qxl_add_memslot(d, val, 0, async); break; case QXL_IO_MEMSLOT_DEL: if (val >= NUM_MEMSLOTS) { qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range"); break; } qxl_del_memslot(d, val); break; case QXL_IO_CREATE_PRIMARY: if (val != 0) { qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0", async); goto cancel_async; } d->guest_primary.surface = d->ram->create_surface; qxl_create_guest_primary(d, 0, async); break; case QXL_IO_DESTROY_PRIMARY: if (val != 0) { qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0", async); goto cancel_async; } if (!qxl_destroy_primary(d, async)) { trace_qxl_io_destroy_primary_ignored(d->id, qxl_mode_to_string(d->mode)); goto cancel_async; } break; case QXL_IO_DESTROY_SURFACE_WAIT: if (val >= d->ssd.num_surfaces) { qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):" "%" PRIu64 " >= NUM_SURFACES", async, val); goto cancel_async; } qxl_spice_destroy_surface_wait(d, val, async); break; case QXL_IO_FLUSH_RELEASE: { QXLReleaseRing *ring = &d->ram->release_ring; if (ring->prod - ring->cons + 1 == ring->num_items) { fprintf(stderr, "ERROR: no flush, full release ring [p%d,%dc]\n", ring->prod, ring->cons); } qxl_push_free_res(d, 1 /* flush */); break; } case QXL_IO_FLUSH_SURFACES_ASYNC: qxl_spice_flush_surfaces_async(d); break; case QXL_IO_DESTROY_ALL_SURFACES: d->mode = QXL_MODE_UNDEFINED; qxl_spice_destroy_surfaces(d, async); break; case QXL_IO_MONITORS_CONFIG_ASYNC: qxl_spice_monitors_config_async(d, 0); break; default: qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port); } return; cancel_async: if (async) { qxl_send_events(d, QXL_INTERRUPT_IO_CMD); qemu_mutex_lock(&d->async_lock); d->current_async = QXL_UNDEFINED_IO; qemu_mutex_unlock(&d->async_lock); } }
2.2、qxl_add_memslot将guest os物理地址转换成qemu进程的虚拟地址,并通过QemuConsole发送给spice。
static int qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta, qxl_async_io async) { static const int regions[] = { QXL_RAM_RANGE_INDEX, QXL_VRAM_RANGE_INDEX, QXL_VRAM64_RANGE_INDEX, }; uint64_t guest_start; uint64_t guest_end; int pci_region; pcibus_t pci_start; pcibus_t pci_end; MemoryRegion *mr; intptr_t virt_start; QXLDevMemSlot memslot; int i; guest_start = le64_to_cpu(d->guest_slots[slot_id].slot.mem_start); guest_end = le64_to_cpu(d->guest_slots[slot_id].slot.mem_end); trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end); if (slot_id >= NUM_MEMSLOTS) { qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__, slot_id, NUM_MEMSLOTS); return 1; } if (guest_start > guest_end) { qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64 " > 0x%" PRIx64, __func__, guest_start, guest_end); return 1; } for (i = 0; i < ARRAY_SIZE(regions); i++) { pci_region = regions[i]; pci_start = d->pci.io_regions[pci_region].addr; pci_end = pci_start + d->pci.io_regions[pci_region].size; /* mapped? */ if (pci_start == -1) { continue; } /* start address in range ? */ if (guest_start < pci_start || guest_start > pci_end) { continue; } /* end address in range ? */ if (guest_end > pci_end) { continue; } /* passed */ break; } if (i == ARRAY_SIZE(regions)) { qxl_set_guest_bug(d, "%s: finished loop without match", __func__); return 1; } switch (pci_region) { case QXL_RAM_RANGE_INDEX: mr = &d->vga.vram; break; case QXL_VRAM_RANGE_INDEX: case 4 /* vram 64bit */: mr = &d->vram_bar; break; default: /* should not happen */ qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region); return 1; } assert(guest_end - pci_start <= memory_region_size(mr)); virt_start = (intptr_t)memory_region_get_ram_ptr(mr); memslot.slot_id = slot_id; memslot.slot_group_id = MEMSLOT_GROUP_GUEST; /* guest group */ memslot.virt_start = virt_start + (guest_start - pci_start); memslot.virt_end = virt_start + (guest_end - pci_start); memslot.addr_delta = memslot.virt_start - delta; memslot.generation = d->rom->slot_generation = 0; qxl_rom_set_dirty(d); qemu_spice_add_memslot(&d->ssd, &memslot, async); d->guest_slots[slot_id].mr = mr; d->guest_slots[slot_id].offset = memslot.virt_start - virt_start; d->guest_slots[slot_id].size = memslot.virt_end - memslot.virt_start; d->guest_slots[slot_id].delta = delta; d->guest_slots[slot_id].active = 1; return 0; }
3、libspice协议端
3.1、获取qxl设备的调度器
Dispatcher *red_qxl_get_dispatcher(QXLInstance *qxl) { return qxl->st->dispatcher.get(); }
3.2、设置调度器处理回调函数来处理收到的RedWorkerMessageAddMemslot消息
static void register_callbacks(Dispatcher *dispatcher) { /* TODO: register cursor & display specific msg in respective channel files */ register_handler(dispatcher, handle_dev_update, true); register_handler(dispatcher, handle_dev_update_async, false); register_handler(dispatcher, handle_dev_add_memslot, true); register_handler(dispatcher, handle_dev_add_memslot_async, false); register_handler(dispatcher, handle_dev_del_memslot, false); register_handler(dispatcher, handle_dev_destroy_surfaces, true); register_handler(dispatcher, handle_dev_destroy_surfaces_async, false); register_handler(dispatcher, handle_dev_destroy_primary_surface, true); register_handler(dispatcher, handle_dev_destroy_primary_surface_async, false); register_handler(dispatcher, handle_dev_create_primary_surface_async, false); register_handler(dispatcher, handle_dev_create_primary_surface, true); register_handler(dispatcher, handle_dev_reset_image_cache, true); register_handler(dispatcher, handle_dev_reset_cursor, true); register_handler(dispatcher, handle_dev_wakeup, false); register_handler(dispatcher, handle_dev_oom, false); register_handler(dispatcher, handle_dev_start, false); register_handler(dispatcher, handle_dev_flush_surfaces_async, false); register_handler(dispatcher, handle_dev_stop, true); register_handler(dispatcher, handle_dev_loadvm_commands, true); register_handler(dispatcher, handle_dev_set_compression, false); register_handler(dispatcher, handle_dev_set_streaming_video, false); register_handler(dispatcher, handle_dev_set_video_codecs, false); register_handler(dispatcher, handle_dev_set_mouse_mode, false); register_handler(dispatcher, handle_dev_destroy_surface_wait, true); register_handler(dispatcher, handle_dev_destroy_surface_wait_async, false); register_handler(dispatcher, handle_dev_reset_memslots, false); register_handler(dispatcher, handle_dev_monitors_config_async, false); register_handler(dispatcher, handle_dev_driver_unload, false); register_handler(dispatcher, handle_dev_gl_scanout, false); register_handler(dispatcher, handle_dev_gl_draw_async, false); register_handler(dispatcher, handle_dev_close, false); }
3.3、spice新增内存区域
static void handle_dev_add_memslot(RedWorker* worker, RedWorkerMessageAddMemslot* msg) { QXLDevMemSlot mem_slot = msg->mem_slot; memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id, mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end, mem_slot.generation); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 3:08:21

Kotlin Multiplatform day03

// // 第 3 课&#xff1a;集合&#xff08;List / Set / Map&#xff09; 空安全(null) // fun main() {// 一、List&#xff1a;有序的列表&#xff08;最常用&#xff09;// listOf 创建一个“只读”列表&#xff08;不能增删改&#xff09;val fruits listOf("苹果…

作者头像 李华
网站建设 2026/7/8 3:07:47

001 数据通信网络基础

本文档从通信与网络的基本概念出发&#xff0c;系统介绍了数据通信网络的信息传递过程、常见网络设备&#xff08;交换机、路由器、防火墙、无线设备&#xff09;、网络类型&#xff08;LAN/MAN/WAN&#xff09;与拓扑结构&#xff0c;以及网络工程师的职业发展路径和华为数通认…

作者头像 李华
网站建设 2026/7/8 3:07:35

索尼(sony)FX3摄像机断电后RSV文件的修复方法

FX3是索尼(sony)Cinema Line专业电影机系列的入门机型&#xff0c;定位高端专业级&#xff0c;是一款市占率较高的产品。大家都知道长时间的拍摄对电池是一种极大的考验&#xff0c;那么在这种场景下如果遇到摄像机意外断电应该如何修复呢&#xff1f; 故障文件: 74.1GB RSV文…

作者头像 李华
网站建设 2026/7/8 3:07:25

鸿蒙三方库 | harmony-utils之AppUtil灰阶与颜色模式设置详解

前言 无障碍功能是现代应用不可或缺的一部分。HarmonyOS提供了灰阶模式和颜色反转等视觉辅助功能&#xff0c;帮助视觉障碍用户更好地使用应用。pura/harmony-utils 的 AppUtil 封装了这些功能的查询与设置方法&#xff0c;开发者可以轻松实现无障碍相关的功能适配。本文将从AP…

作者头像 李华
网站建设 2026/7/8 3:03:55

厨房秤PCBA方案开发全流程设计与技术要点

在健康饮食、精细烘焙、精准烹饪常态化的当下&#xff0c;家用厨房秤已从传统简易小家电升级为高精度、智能化的居家计量设备。PCBA电路板作为厨房秤的核心控制与信号处理核心&#xff0c;其开发设计的合理性&#xff0c;会影响设备的称重精度、运行稳定性、续航能力与使用寿命…

作者头像 李华