如何使得Qemu支持光驱刻录
Qemu中实现光驱刻录的先决条件是:
1 系统中的物理光驱支持刻录功能。
2 宿主系统(比如我们要使用的Linux系统)本身支持光驱刻录。
实现原理
有关实现I/O截获,以及光驱控制器的状态模拟架构(如PIO/DMA模式,命令解析方式等)这些复杂的工作,Qemu都早已经完成。我们这里需要做的仅仅是锦上添花的工作。下面我们就实现原理进行介绍。
在Linux宿主机上实现Qemu的光驱刻录首先需要了解光驱控制器的操作方法。目前的光驱控制器的操作指令规范有《Multimedia Device Command Set》(简称MMC),以及《SCSI Device Primary Command Set》(简称SPC)。特别是MMC规范是专门针对DVD/CD操作的规范,而SPC规范是对所有SCSI设备操作的通用规范,属于通用操作的光驱操作可以在这个规范中找到。
Qemu中的光驱控制器模拟代码(ide.c)对MMC和SPC规范的模拟并不完备,因此不能实现真实光驱设备的所有功能。目前仅仅能实现光驱的读功能,也就是说在guest中只能看到一个只读光驱,而无法执行任何真实写操作。因此需要做的第一步是在qemu的光驱控制器模拟代码中补充光驱刻录所需要的所有Atapi指令(具体指令集见下文)。
另外由于目前Guest OS中所见的光驱是模拟光驱,设备名是QEMU CD-ROM(见函数ide_atapi_identify),其所有属性都是通过模拟代码软件设置(各种Atapi命令除了读数据的GPCMD_READ_*外,所有GPCMD_*命令都是模拟实现,见函数ide_atapi_cmd),只有读数据时才会和真实设备交互——具体是通过Linux系统中的/dev/cdrom设备文件提供的lseek和read等接口实现间接访问真实光驱)。那么如果我们想支持光驱刻录,则需要让GUEST看到真实的可刻录光驱设备,否则就在Guest中无法执行刻录操作(因为刻录操作前都需要检测设备属性是否支持刻录)。而要在GUEST中看到真实光驱设备,则需要支持从真实设备获取设备属性和设置设备属性,也就是说要将所有ATAPI命令直接转发到物理设备,而不是模拟执行,返回模拟结果。这种转发依然是通过Linux下的/dev/cdrom设备文件提供的ioctl接口中的CDROM_SEND_PACKET处理分支。所以第二步需要做的是:将ATAPI命令通过ioctl接口转发给真实设备进行处理,并将真实结果返回。
所需要做的理论上就这么多了!下面看看具体实现。
实现步骤
1 先来说说需要补充的命令集合。我主要测试的Guest是vista系统,就vista而言需要实现如下ATAPI命令才能支持刻录:
GPCMD_TEST_UNIT_READY:
GPCMD_MODE_SENSE_10:
GPCMD_REQUEST_SENSE:
GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
GPCMD_READ_10:
GPCMD_READ_12:
GPCMD_READ_CD:
GPCMD_WRITE_10:
GPCMD_WRITE_12:
GPCMD_SEEK:
GPCMD_START_STOP_UNIT:
GPCMD_MECHANISM_STATUS:
GPCMD_READ_TOC_PMA_ATIP:
GPCMD_READ_CDVD_CAPACITY:
GPCMD_READ_DISC_INFO:
GPCMD_GET_CONFIGURATION:
GPCMD_SET_SPEED:
GPCMD_INQUIRY:
GPCMD_GET_PERFORMANCE:
GPCMD_MODE_SELECT_10:
GPCMD_REPORT_KEY:
GPCMD_READ_DVD_STRUCTURE:
GPCMD_GET_EVENT_STATUS_NOTIFICATION:
GPCMD_BLANK:
GPCMD_CLOSE_TRACK:
GPCMD_READ_FORMAT_CAPACITIES:
GPCMD_READ_TRACK_RZONE_INFO:
GPMODE_READ_BUFFER_CAPACITY:
GPCMD_FLUSH_CACHE:
GPCMD_VERIFY_10:
GPCMD_SEND_OPC:
GPCMD_FORMAT_UNIT:
GPCMD_SET_STREAMING:
2 对ioctl进行了封装,让其包含在raw_ioctl中,以符合qemu的设备访问架构。
在操作表中填加raw_ioctl。
BlockDriver bdrv_raw = {
"raw",
sizeof(BDRVRawState),
raw_probe,
raw_open,
raw_read,
raw_write,
raw_close,
raw_create,
raw_flush,
raw_ioctl,
};
对/dev/cdrom的ioctl进行封装,共给qemu解析ATAPI packet命令时调用。
int raw_ioctl(BlockDriverState *bs)
{
BDRVRawState *s = bs->opaque;
struct cdrom_generic_command *cgc=bs->opaque2;
struct cdrom_generic_command _cgc;
struct request_sense sense;
int ret=0;
ret=ioctl(s->fd,CDROM_SEND_PACKET,cgc);
if ( ret<0)
{
printf("raw_ioctl: error fd=%d\n",s->fd);
}
return ret;
return 0;
}
3 状态机状态补充。
目前的Qemu所接收的命令都是读取命令,其输入只需要一个命令,而无需附加参数,这种情况属于不带数据的ATAPI packet命令,如Mode Sense command等。Qemu中目前的模拟代码处理没有问题。
但是对于传入命令以外,还需要附加参数的ATAPI pactet命令,如Mode Select Command(除了命令本身要发给设备外,还需要传入Mode Parameter List等参数)处理上缺失状态迁移,也就是说在接收到命令后,设备状态未能迁移到接受参数状态,因此造成了驱动程序无法将参数传入。
解决办法是按照设备规范补充状态迁移代码即可。具体做法是在收到12字节的命令后,设置DRQ寄存器,并发送中断INTRQ。
4 I/O缓存问题。
由于Linux系统的I/O缓存层的存在,而当我们使用ioctl向光盘写入数据,并不经过I/O缓存层,因此Linux内核并未刷新I/O缓存层(按照道理来说,内核应该刷新或invalidate原缓存,这里可能是Linux内核一个bug),所以不能在使用原来的/dev/cdrom的read接口读光盘内容,因为read读取是读的I/O缓存中的数据,所以我们需要让读数的时候绕过I/O缓存。方法有1 直接用ioctl读取光盘 ;方法2 使用裸设备方法访问光驱。
阅读(1898) | 评论(0) | 转发(0) |