diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index 447e32a..7832d2c 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -178,7 +178,7 @@ common-obj-$(CONFIG_MAX111X) += max111x.o
 common-obj-$(CONFIG_DS1338) += ds1338.o
 common-obj-y += i2c.o smbus.o smbus_eeprom.o
 common-obj-y += eeprom93xx.o
-common-obj-y += scsi-disk.o cdrom.o hd-geometry.o block-common.o
+common-obj-y += scsi-disk.o cdrom.o block-common.o
 common-obj-y += scsi-generic.o scsi-bus.o
 common-obj-y += hid.o
 common-obj-$(CONFIG_SSI) += ssi.o
@@ -209,6 +209,10 @@ obj-$(CONFIG_VGA) += vga.o
 obj-$(CONFIG_SOFTMMU) += device-hotplug.o
 obj-$(CONFIG_XEN) += xen_domainbuild.o xen_machine_pv.o
 
+# geometry is target/architecture dependent and therefore needs to be built
+# for every target platform
+obj-y += hd-geometry.o
+
 # Inter-VM PCI shared memory & VFIO PCI device assignment
 ifeq ($(CONFIG_PCI), y)
 obj-$(CONFIG_KVM) += ivshmem.o
diff --git a/hw/hd-geometry.c b/hw/hd-geometry.c
index c305143..98253d7 100644
--- a/hw/hd-geometry.c
+++ b/hw/hd-geometry.c
@@ -33,6 +33,10 @@
 #include "block/block.h"
 #include "hw/block-common.h"
 #include "trace.h"
+#ifdef __linux__
+#include <linux/fs.h>
+#include <linux/hdreg.h>
+#endif
 
 struct partition {
         uint8_t boot_ind;           /* 0x80 - active */
@@ -47,6 +51,39 @@ struct partition {
         uint32_t nr_sects;          /* nr of sectors in partition */
 } QEMU_PACKED;
 
+static void guess_chs_for_size(BlockDriverState *bs,
+                uint32_t *pcyls, uint32_t *pheads, uint32_t *psecs);
+
+/* try to get geometry from disk via HDIO_GETGEO ioctl
+   Return 0 if OK, -1 if ioctl does not work (e.g. image file) */
+inline static int guess_disk_pchs(BlockDriverState *bs,
+                    uint32_t *pcylinders, uint32_t *pheads,
+                    uint32_t *psectors, int *ptranslation)
+{
+#ifdef __linux__
+    struct hd_geometry geo;
+
+    if (bdrv_ioctl(bs, HDIO_GETGEO, &geo)) {
+        return -1;
+    }
+
+    /* HDIO_GETGEO may return success even though geo contains zeros
+       (e.g. certain multipath setups) */
+    if (!geo.heads || !geo.sectors || !geo.cylinders) {
+        return -1;
+    }
+
+    *pheads = geo.heads;
+    *psectors = geo.sectors;
+    *pcylinders = geo.cylinders;
+    *ptranslation = BIOS_ATA_TRANSLATION_NONE;
+    return 0;
+#else
+    return -1;
+#endif
+}
+
+
 /* try to guess the disk logical geometry from the MSDOS partition table.
    Return 0 if OK, -1 if could not guess */
 static int guess_disk_lchs(BlockDriverState *bs,
@@ -116,13 +153,43 @@ static void guess_chs_for_size(BlockDriverState *bs,
     *psecs = 63;
 }
 
+/* target specific geometry guessing hooks:
+ * return 0 if guess available, != 0 in any other case */
+#ifdef TARGET_S390X
+static inline int target_geometry_guess(BlockDriverState *bs,
+                          uint32_t *pcyls, uint32_t *pheads,
+                          uint32_t *psecs, int *ptranslation)
+{
+    int cyls, heads, secs;
+    if (!guess_disk_lchs(bs, &cyls, &heads, &secs)) {
+        *pcyls = cyls;
+        *pheads = heads;
+        *psecs = secs;
+        *ptranslation = BIOS_ATA_TRANSLATION_NONE;
+        return 0;
+    } else {
+        return guess_disk_pchs(bs, pcyls, pheads, psecs, ptranslation);
+    }
+}
+#else
+static inline int target_geometry_guess(BlockDriverState *bs,
+                          uint32_t *pcyls, uint32_t *pheads,
+                          uint32_t *psecs, int *ptranslation)
+{
+    return -1;
+}
+#endif
+
 void hd_geometry_guess(BlockDriverState *bs,
                        uint32_t *pcyls, uint32_t *pheads, uint32_t *psecs,
                        int *ptrans)
 {
     int cylinders, heads, secs, translation;
 
-    if (guess_disk_lchs(bs, &cylinders, &heads, &secs) < 0) {
+    if (!target_geometry_guess(bs, pcyls, pheads, psecs, &translation)) {
+        /* a target specific guess has highest priority */
+        goto out;
+    } else if (guess_disk_lchs(bs, &cylinders, &heads, &secs) < 0) {
         /* no LCHS guess: use a standard physical disk geometry  */
         guess_chs_for_size(bs, pcyls, pheads, psecs);
         translation = hd_bios_chs_auto_trans(*pcyls, *pheads, *psecs);
@@ -143,6 +210,7 @@ void hd_geometry_guess(BlockDriverState *bs,
            the logical geometry */
         translation = BIOS_ATA_TRANSLATION_NONE;
     }
+out:
     if (ptrans) {
         *ptrans = translation;
     }
