From patchwork Sun Nov 28 19:08:15 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Fran=C3=A7ois_Revol?= X-Patchwork-Id: 73348 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id EEECCB70B8 for ; Mon, 29 Nov 2010 06:09:00 +1100 (EST) Received: from localhost ([127.0.0.1]:41144 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PMmcK-00021k-V5 for incoming@patchwork.ozlabs.org; Sun, 28 Nov 2010 14:08:56 -0500 Received: from [140.186.70.92] (port=46219 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PMmbp-00021a-CD for qemu-devel@nongnu.org; Sun, 28 Nov 2010 14:08:27 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PMmbn-0003vu-4V for qemu-devel@nongnu.org; Sun, 28 Nov 2010 14:08:25 -0500 Received: from smtp6-g21.free.fr ([212.27.42.6]:38310) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PMmbm-0003vi-CG for qemu-devel@nongnu.org; Sun, 28 Nov 2010 14:08:23 -0500 Received: from [192.168.0.1] (unknown [82.244.111.82]) by smtp6-g21.free.fr (Postfix) with ESMTP id 884768226A for ; Sun, 28 Nov 2010 20:08:16 +0100 (CET) From: =?iso-8859-1?Q?Fran=E7ois_Revol?= Date: Sun, 28 Nov 2010 20:08:15 +0100 Message-Id: To: qemu-devel@nongnu.org Mime-Version: 1.0 (Apple Message framework v1081) X-Mailer: Apple Mail (2.1081) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) Subject: [Qemu-devel] [PATCH] Add basic read, write and create support for AMD SimNow HDD images. X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org $subj. Someone asked about this format, wanting to try Haiku in SimNow, so I wrote this. I got a report of successfully booting a converted image in SimNow. It doesn't yet support automatically growing the file, so we just preallocate on create. François. From b0602bc2b02dcd7b15f0f9a143f850defd767509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= Date: Sun, 28 Nov 2010 20:01:03 +0100 Subject: [PATCH] Add basic read, write and create support for AMD SimNow HDD images. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: François Revol --- Makefile.objs | 2 +- block/hdd.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletions(-) create mode 100644 block/hdd.c diff --git a/Makefile.objs b/Makefile.objs index 23b17ce..20e346d 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -20,7 +20,7 @@ block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o block-nested-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o block-nested-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o -block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o +block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o hdd.o block-nested-$(CONFIG_WIN32) += raw-win32.o block-nested-$(CONFIG_POSIX) += raw-posix.o block-nested-$(CONFIG_CURL) += curl.o diff --git a/block/hdd.c b/block/hdd.c new file mode 100644 index 0000000..aed609e --- /dev/null +++ b/block/hdd.c @@ -0,0 +1,354 @@ +/* + * Block driver for the AMD SimNow HDD disk image + * + * Copyright (c) 2010 François Revol + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Reference: + * http://developer.amd.com/Assets/SimNowUsersManual4.6.2.pdf page 181 + * + * "the hard-disk image file contains a 512-byte header before the raw data. + * This 512-byte header is identical to the information provided by the drive + * in response to the ATA command "IDENTIFY". Following the 512-byte header + * is the data for each sector from the device." + */ + +#include "qemu-common.h" +#include "block_int.h" +#include "module.h" + +/* Command line option for static images. */ +#define BLOCK_OPT_STATIC "static" + +#define SECTOR_SIZE 512 +#define DATA_START 1 +#define MAX_MULT_SECTORS 1 + +typedef struct BDRVHddState { + uint8_t identify_data[SECTOR_SIZE]; +} BDRVHddState; + + +static void padstr(char *str, const char *src, int len) +{ + int i, v; + for(i = 0; i < len; i++) { + if (*src) + v = *src++; + else + v = ' '; + str[i^1] = v; + } +} + +static void put_le16(uint16_t *p, unsigned int v) +{ + *p = cpu_to_le16(v); +} + +static int isvalid_ide_chr(char c) +{ + /* XXX: other chars also maybe? */ + return (isalnum(c) || c == ' ' || c == '.'); +} + +static int hdd_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + int name_len; + uint16_t *p = (uint16_t *)buf; + int64_t nb_sectors; + uint32_t nb_sectors_clipped; + int result = 0; + int i; + + if (buf_size < SECTOR_SIZE) { + /* Header too small, no VDI. */ + return 0; + } + + /* best effort sanity check */ + /* TODO: check more (CHS size...) */ + + /* serial number */ + for (i = 10 * 2; i < 10 * 2 + 20; i++) { + if (!isvalid_ide_chr(buf[i])) { + return 0; + } + } + result += 20; + + /* firmware version */ + for (i = 23 * 2; i < 23 * 2 + 8; i++) { + if (!isvalid_ide_chr(buf[i])) { + return 0; + } + } + result += 8; + + /* model */ + for (i = 27 * 2; i < 27 * 2 + 40; i++) { + if (!isvalid_ide_chr(buf[i])) { + return 0; + } + } + result += 40; + + nb_sectors = le16_to_cpu(p[100]); + nb_sectors |= (uint64_t)le16_to_cpu(p[101]) << 16; + nb_sectors |= (uint64_t)le16_to_cpu(p[102]) << 32; + nb_sectors |= (uint64_t)le16_to_cpu(p[103]) << 48; + + nb_sectors_clipped = le16_to_cpu(p[60]) | (le16_to_cpu(p[61]) << 16); + + if (nb_sectors < 1 || ((uint32_t)nb_sectors) != nb_sectors_clipped) { + return 0; + } + result += 10; + + if (filename != NULL) { + name_len = strlen(filename); + if (name_len > 4 && !strcmp(filename + name_len - 4, ".hdd")) + result += 20; + } + + return result; +} + +static int hdd_open(BlockDriverState *bs, int flags) +{ + BDRVHddState *s = bs->opaque; + uint16_t *p = (uint16_t *)s->identify_data; + int64_t nb_sectors; + int sectors; + int cylinders; + int heads; + + if (bdrv_read(bs->file, 0, s->identify_data, 1) < 0) { + goto fail; + } + + if (hdd_probe(s->identify_data, SECTOR_SIZE, NULL) == 0) { + goto fail; + } + + nb_sectors = le16_to_cpu(p[100]); + nb_sectors |= (uint64_t)le16_to_cpu(p[101]) << 16; + nb_sectors |= (uint64_t)le16_to_cpu(p[102]) << 32; + nb_sectors |= (uint64_t)le16_to_cpu(p[103]) << 48; + + bs->total_sectors = nb_sectors; + + /* hints */ + /* + bs->cyls = le16_to_cpu(p[54]); + bs->heads = le16_to_cpu(p[55]); + bs->secs = le16_to_cpu(p[56]); + */ + + return 0; + + fail: + return -1; +} + +static int hdd_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + int ret; + if (bdrv_read(bs->file, sector_num + DATA_START, buf, nb_sectors) < 0) { + return -1; + } + return 0; +} + +static int hdd_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + int ret; + + if (sector_num > bs->total_sectors) { + fprintf(stderr, + "(HDD) Wrong offset: sector_num=0x%" PRIx64 + " total_sectors=0x%" PRIx64 "\n", + sector_num, bs->total_sectors); + return -1; + } + /* TODO: check if already allocated, else truncate() */ + if (bdrv_write(bs->file, sector_num + DATA_START, buf, nb_sectors) < 0) { + return -1; + } + return 0; +} + +static int hdd_create(const char *filename, QEMUOptionParameter *options) +{ + int fd; + int result = 0; + int static_image = 1; /* make it default for now */ + uint64_t bytes = 0; + uint32_t blocks; + uint8_t header[SECTOR_SIZE]; + size_t i; + uint16_t *p; + unsigned int oldsize; + char drive_serial_str[20]; + int mult_sectors = 0; + int64_t nb_sectors; + int sectors; + int cylinders; + int heads; + + /* Read out options. */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + bytes = options->value.n; + } else if (!strcmp(options->name, BLOCK_OPT_STATIC)) { + if (options->value.n) { + static_image = 1; + } + } + options++; + } + + nb_sectors = bytes / SECTOR_SIZE; + /* we can't use bdrv_guess_geometry, use a standard physical disk geometry */ + cylinders = nb_sectors / (16 * 63); + if (cylinders > 16383) + cylinders = 16383; + else if (cylinders < 2) + cylinders = 2; + heads = 16; + sectors = 63; + + /* XXX: generate one ? */ + snprintf(drive_serial_str, sizeof(drive_serial_str), "QM%05d", 0); + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, + 0644); + if (fd < 0) { + return -errno; + } + + /* We need enough blocks to store the given disk size, + so always round up. */ + blocks = (bytes + SECTOR_SIZE - 1) / SECTOR_SIZE; + + /* Stolen from hw/ide/core.c */ + /* XXX: are the hw flags ok there ? */ + memset(header, 0, sizeof(header)); + p = (uint16_t *)header; + put_le16(p + 0, 0x0040); + put_le16(p + 1, cylinders); + put_le16(p + 3, heads); + put_le16(p + 4, 512 * sectors); /* XXX: retired, remove ? */ + put_le16(p + 5, 512); /* XXX: retired, remove ? */ + put_le16(p + 6, sectors); + padstr((char *)(p + 10), drive_serial_str, 20); /* serial number */ + put_le16(p + 20, 3); /* XXX: retired, remove ? */ + put_le16(p + 21, 512); /* cache size in sectors */ + put_le16(p + 22, 4); /* ecc bytes */ + padstr((char *)(p + 23), QEMU_VERSION/*s->version*/, 8); /* firmware version */ + padstr((char *)(p + 27), "QEMU HARDDISK", 40); /* model */ +#if MAX_MULT_SECTORS > 1 + put_le16(p + 47, 0x8000 /*| MAX_MULT_SECTORS*/); +#endif + put_le16(p + 48, 1); /* dword I/O */ + put_le16(p + 49, (1 << 11) | (1 << 9) | (1 << 8)); /* DMA and LBA supported */ + put_le16(p + 51, 0x200); /* PIO transfer cycle */ + put_le16(p + 52, 0x200); /* DMA transfer cycle */ + put_le16(p + 53, 1 | (1 << 1) | (1 << 2)); /* words 54-58,64-70,88 are valid */ + put_le16(p + 54, cylinders); + put_le16(p + 55, heads); + put_le16(p + 56, sectors); + oldsize = cylinders * heads * sectors; + put_le16(p + 57, oldsize); + put_le16(p + 58, oldsize >> 16); + if (mult_sectors) + put_le16(p + 59, 0x100 | mult_sectors); + put_le16(p + 60, nb_sectors); + put_le16(p + 61, nb_sectors >> 16); + put_le16(p + 62, 0x07); /* single word dma0-2 supported */ + put_le16(p + 63, 0x07); /* mdma0-2 supported */ + put_le16(p + 64, 0x03); /* pio3-4 supported */ + put_le16(p + 65, 120); + put_le16(p + 66, 120); + put_le16(p + 67, 120); + put_le16(p + 68, 120); + put_le16(p + 80, 0xf0); /* ata3 -> ata6 supported */ + put_le16(p + 81, 0x16); /* conforms to ata5 */ + /* 14=NOP supported, 5=WCACHE supported, 0=SMART supported */ + put_le16(p + 82, (1 << 14) | (1 << 5) | 1); + /* 13=flush_cache_ext,12=flush_cache,10=lba48 */ + put_le16(p + 83, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10)); + /* 14=set to 1, 1=SMART self test, 0=SMART error logging */ + put_le16(p + 84, (1 << 14) | 0); + /* 14 = NOP supported, 5=WCACHE enabled, 0=SMART feature set enabled */ + put_le16(p + 85, (1 << 14) | 1); + /* 13=flush_cache_ext,12=flush_cache,10=lba48 */ + put_le16(p + 86, (1 << 14) | (1 << 13) | (1 <<12) | (1 << 10)); + /* 14=set to 1, 1=smart self test, 0=smart error logging */ + put_le16(p + 87, (1 << 14) | 0); + put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ + put_le16(p + 93, 1 | (1 << 14) | 0x2000); + put_le16(p + 100, nb_sectors); + put_le16(p + 101, nb_sectors >> 16); + put_le16(p + 102, nb_sectors >> 32); + put_le16(p + 103, nb_sectors >> 48); + + if (write(fd, &header, sizeof(header)) < 0) { + result = -errno; + } + + /* TODO: specs says it can grow, so no need to always do this */ + if (static_image) { + if (ftruncate(fd, sizeof(header) + blocks * SECTOR_SIZE)) { + result = -errno; + } + } + + if (close(fd) < 0) { + result = -errno; + } + + return result; +} + +static void hdd_close(BlockDriverState *bs) +{ + BDRVHddState *s = bs->opaque; +} + +static BlockDriver bdrv_hdd = { + .format_name = "hdd", + .instance_size = sizeof(BDRVHddState), + .bdrv_probe = hdd_probe, + .bdrv_open = hdd_open, + .bdrv_read = hdd_read, + .bdrv_write = hdd_write, + .bdrv_close = hdd_close, + .bdrv_create = hdd_create, +}; + +static void bdrv_hdd_init(void) +{ + bdrv_register(&bdrv_hdd); +} + +block_init(bdrv_hdd_init);