Add support for the SMBus protocol on the PIIX4 chipset. Add a test for
EEPROMs using this interface.

Signed-off-by: Marc Marí <marc.mari.barc...@gmail.com>
---
 tests/Makefile             |    5 +
 tests/eeprom-test.c        |   58 ++++++++++++
 tests/libqos/i2c.h         |    5 +-
 tests/libqos/smbus-piix4.c |  220 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 287 insertions(+), 1 deletion(-)
 create mode 100644 tests/eeprom-test.c
 create mode 100644 tests/libqos/smbus-piix4.c

diff --git a/tests/Makefile b/tests/Makefile
index 361bb7b..e49c779 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -152,6 +152,8 @@ gcov-files-i386-y += hw/pci-bridge/i82801b11.c
 check-qtest-i386-y += tests/ioh3420-test$(EXESUF)
 gcov-files-i386-y += hw/pci-bridge/ioh3420.c
 check-qtest-i386-y += tests/usb-hcd-ehci-test$(EXESUF)
+gcov-files-i386-y += hw/i2c/smbus_eeprom.c
+check-qtest-i386-y += tests/eeprom-test$(EXESUF)
 gcov-files-i386-y += hw/usb/hcd-ehci.c
 gcov-files-i386-y += hw/usb/hcd-uhci.c
 gcov-files-i386-y += hw/usb/dev-hid.c
@@ -281,6 +283,7 @@ libqos-obj-y += tests/libqos/i2c.o
 libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o
 libqos-pc-obj-y += tests/libqos/malloc-pc.o
 libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o
+libqos-piix4-obj-y = $(libqos-obj-y) tests/libqos/smbus-piix4.o
 
 tests/rtc-test$(EXESUF): tests/rtc-test.o
 tests/m48t59-test$(EXESUF): tests/m48t59-test.o
@@ -322,9 +325,11 @@ tests/es1370-test$(EXESUF): tests/es1370-test.o
 tests/intel-hda-test$(EXESUF): tests/intel-hda-test.o
 tests/ioh3420-test$(EXESUF): tests/ioh3420-test.o
 tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-pc-obj-y)
+tests/eeprom-test$(EXESUF): tests/eeprom-test.o $(libqos-piix4-obj-y)
 tests/qemu-iotests/socket_scm_helper$(EXESUF): 
tests/qemu-iotests/socket_scm_helper.o
 tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o libqemuutil.a 
libqemustub.a
 
+
 # QTest rules
 
 TARGETS=$(patsubst %-softmmu,%, $(filter %-softmmu,$(TARGET_DIRS)))
diff --git a/tests/eeprom-test.c b/tests/eeprom-test.c
new file mode 100644
index 0000000..6f01dd2
--- /dev/null
+++ b/tests/eeprom-test.c
@@ -0,0 +1,58 @@
+/*
+ * QTest testcase for the SMBus EEPROM device
+ *
+ * Copyright (c) 2014 Marc Marí
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <stdio.h>
+
+#include "libqtest.h"
+#include "libqos/i2c.h"
+
+#define PIIX4_SMBUS_BASE 0x0000b100
+
+#define EEPROM_TEST_ID   "eeprom-test"
+#define EEPROM_TEST_ADDR 0x50
+
+static I2CAdapter *i2c;
+
+static void send_and_receive(void)
+{
+    uint8_t i;
+    uint8_t buf = 0;
+    uint64_t big_buf = 0;
+
+    for (i = EEPROM_TEST_ADDR; i < EEPROM_TEST_ADDR+8; ++i) {
+        i2c_recv(i2c, i, &buf, 1);
+        g_assert_cmphex(buf, ==, 0);
+
+        i2c_recv(i2c, i, (uint8_t *)&big_buf, 4);
+        g_assert_cmphex(big_buf, ==, 0);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    QTestState *s = NULL;
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+
+    s = qtest_start(NULL);
+    i2c = piix4_smbus_create(PIIX4_SMBUS_BASE);
+
+    qtest_add_func("/eeprom/tx-rx", send_and_receive);
+
+    ret = g_test_run();
+
+    if (s) {
+        qtest_quit(s);
+    }
+    g_free(i2c);
+
+    return ret;
+}
diff --git a/tests/libqos/i2c.h b/tests/libqos/i2c.h
index 1ce9af4..9fd3a1c 100644
--- a/tests/libqos/i2c.h
+++ b/tests/libqos/i2c.h
@@ -24,7 +24,10 @@ void i2c_send(I2CAdapter *i2c, uint8_t addr,
 void i2c_recv(I2CAdapter *i2c, uint8_t addr,
               uint8_t *buf, uint16_t len);
 
-/* libi2c-omap.c */
+/* i2c-omap.c */
 I2CAdapter *omap_i2c_create(uint64_t addr);
 
+/* smbus-piix4.c */
+I2CAdapter *piix4_smbus_create(uint64_t addr);
+
 #endif
diff --git a/tests/libqos/smbus-piix4.c b/tests/libqos/smbus-piix4.c
new file mode 100644
index 0000000..2e04968
--- /dev/null
+++ b/tests/libqos/smbus-piix4.c
@@ -0,0 +1,220 @@
+/*
+ * QTest I2C driver
+ *
+ * Copyright (c) 2014 Marc Marí
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "libqos/i2c.h"
+
+#include <glib.h>
+
+#include "libqtest.h"
+
+#define SMBUS_TIMEOUT 100000
+
+enum PIIX4SMBUSRegisters {
+    PIIX4_SMBUS_BA = 0x90, /* Base address (in [15:4]). 32 bits */
+    PIIX4_SMBUS_STAT = 0x00, /* Status. 8 bits */
+    PIIX4_SMBUS_CNT = 0x02, /* Control. 8 bits */
+    PIIX4_SMBUS_CMD = 0x03, /* Command. 8 bits */
+    PIIX4_SMBUS_ADD = 0x04, /* Address. 8 bits */
+    PIIX4_SMBUS_DAT0 = 0x05, /* Data 0. 8 bits */
+    PIIX4_SMBUS_DAT1 = 0x06, /* Data 1. 8 bits */
+    PIIX4_SMBUS_BLKDAT = 0x07, /* Block data. 8 bits */
+};
+
+enum PIIX4SMBUSSTATBits {
+    PIIX4_SMBUS_CNT_INTEREN = 1 << 0, /* Enable interrupts */
+    PIIX4_SMBUS_CNT_KILL = 1 << 1, /* Stop the current transaction */
+    PIIX4_SMBUS_CNT_PROT = 7 << 2, /* Type of command */
+    PIIX4_SMBUS_CNT_START = 1 << 6, /* Start execution */
+    PIIX4_SMBUS_STAT_BUSY = 1 << 0, /* Host busy */
+};
+
+enum PIIX4SMBUSCommands {
+    PIIX4_SMBUS_QRW = 0x00, /* Quick read or write */
+    PIIX4_SMBUS_BRW = 0x04, /* Byte read or write */
+    PIIX4_SMBUS_BDRW = 0x08, /* Byte data read or write */
+    PIIX4_SMBUS_WDRW = 0x0C, /* Word data read or write */
+    PIIX4_SMBUS_BLRW = 0x14, /* Block read or write */
+    PIIX4_SMBUS_WR = 0x0, /* Write */
+    PIIX4_SMBUS_RD = 0x1, /* Read */
+};
+
+typedef struct {
+    I2CAdapter parent;
+    uint64_t addr;
+} PIIX4I2C;
+
+static int piix4_smbus_wait_ready(uint64_t addr)
+{
+    uint64_t loops;
+    uint8_t status;
+    loops = SMBUS_TIMEOUT;
+    do {
+        clock_step(10);
+        status = inb(addr + PIIX4_SMBUS_STAT);
+        if ((status & 0x1) == 0) {
+            break; /* It has ended */
+        }
+    } while (--loops);
+
+    if (loops != 0) {
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+static int piix4_smbus_wait_done(uint64_t addr)
+{
+    uint64_t loops;
+    uint8_t status;
+    loops = SMBUS_TIMEOUT;
+    do {
+        clock_step(10);
+        status = inb(addr + PIIX4_SMBUS_STAT);
+        if ((status & 0x1) != 0) {
+            continue; /* It has not started yet */
+        }
+        if (status & 0xfe) {
+            break; /* One of the interrupt flags is set */
+        }
+    } while (--loops);
+
+    if (loops != 0) {
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+static void piix4_smbus_recv(I2CAdapter *i2c, uint8_t addr,
+                          uint8_t *buf, uint16_t len)
+{
+    uint8_t stat;
+    uint8_t size;
+    PIIX4I2C *s = (PIIX4I2C *)i2c;
+
+    /* Wait to be ready */
+    g_assert_cmpint(piix4_smbus_wait_ready(s->addr), == , 0);
+
+    /* Clear interrupts */
+    outb(s->addr + PIIX4_SMBUS_STAT, 0x1e);
+
+    /* Write device */
+    outb(s->addr + PIIX4_SMBUS_ADD, (addr << 1) | PIIX4_SMBUS_RD);
+
+    /* Clear data */
+    outb(s->addr + PIIX4_SMBUS_DAT0, 0);
+
+    /* Start */
+    if (len == 1) {
+        outb(s->addr + PIIX4_SMBUS_CNT,
+                PIIX4_SMBUS_BRW | PIIX4_SMBUS_CNT_START);
+    } else {
+        outb(s->addr + PIIX4_SMBUS_CNT,
+                PIIX4_SMBUS_BLRW | PIIX4_SMBUS_CNT_START);
+    }
+
+    /* Wait to be done */
+    piix4_smbus_wait_done(s->addr);
+
+    /* Wait end */
+    stat = inb(s->addr + PIIX4_SMBUS_STAT);
+    g_assert_cmphex((stat & 0x3e), ==, 2); /* Only interrupt enabled
+
+    /* Read */
+    if (len == 1) {
+        buf[0] = inb(s->addr + PIIX4_SMBUS_DAT0);
+    } else {
+        while (len > 0) {
+            size = inb(s->addr + PIIX4_SMBUS_DAT0);
+            if (size == 0) {
+                break;
+            }
+            g_assert_cmpuint((len-size), <, 0);
+            while (size > 0) {
+                buf[0] = readb(s->addr + PIIX4_SMBUS_BLKDAT);
+                ++buf;
+                --size;
+            }
+
+            len -= size;
+        }
+    }
+}
+
+static void piix4_smbus_send(I2CAdapter *i2c, uint8_t addr,
+                          const uint8_t *buf, uint16_t len)
+{
+    uint8_t stat;
+    uint8_t size;
+    PIIX4I2C *s = (PIIX4I2C *)i2c;
+
+    /* Wait to be ready */
+    g_assert_cmpint(piix4_smbus_wait_ready(s->addr), ==, 0);
+
+    /* Clear interrupts */
+    outb(s->addr + PIIX4_SMBUS_STAT, 0x1e);
+
+    /* Write device */
+    outb(s->addr + PIIX4_SMBUS_ADD, (addr << 1) | PIIX4_SMBUS_WR);
+
+    if (len == 1) {
+        /* Write */
+        outb(s->addr + PIIX4_SMBUS_DAT0, buf[0]);
+
+        stat = inb(s->addr + PIIX4_SMBUS_DAT0);
+        g_assert_cmphex(buf[0], ==, stat);
+
+        /* Start */
+        outb(s->addr + PIIX4_SMBUS_CNT,
+                PIIX4_SMBUS_BRW | PIIX4_SMBUS_CNT_START);
+
+        /* Wait to be done */
+        piix4_smbus_wait_done(s->addr);
+
+        /* Wait end */
+        stat = inb(s->addr + PIIX4_SMBUS_STAT);
+        g_assert_cmphex((stat & 0x3e), ==, 2); /* Only interrupt enabled
+    } else {
+        /* Write 32 bytes */
+        while (len > 0) {
+            size = 0;
+            while (len > 0 && size < 32) {
+                outb(s->addr + PIIX4_SMBUS_BLKDAT, buf[0]);
+                ++buf;
+                ++size;
+                --len;
+            }
+            outb(s->addr + PIIX4_SMBUS_DAT0, size);
+
+            /* Start */
+            outb(s->addr + PIIX4_SMBUS_CNT,
+                    PIIX4_SMBUS_BLRW | PIIX4_SMBUS_CNT_START);
+
+            /* Wait to be done */
+            piix4_smbus_wait_done(s->addr);
+
+            /* Wait end */
+            stat = inb(s->addr + PIIX4_SMBUS_STAT);
+            g_assert_cmphex((stat & 0x3e), ==, 2);
+        }
+    }
+}
+
+I2CAdapter *piix4_smbus_create(uint64_t addr)
+{
+    PIIX4I2C *s = g_malloc0(sizeof(*s));
+    I2CAdapter *i2c = (I2CAdapter *)s;
+
+    s->addr = addr;
+
+    i2c->send = piix4_smbus_send;
+    i2c->recv = piix4_smbus_recv;
+
+    return i2c;
+}
-- 
1.7.10.4


Reply via email to