From patchwork Thu Dec 17 18:50:09 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Corey Minyard X-Patchwork-Id: 58625 Delivered-To: patch@linaro.org Received: by 10.112.89.199 with SMTP id bq7csp570820lbb; Thu, 17 Dec 2015 11:29:56 -0800 (PST) X-Received: by 10.140.102.244 with SMTP id w107mr72685006qge.90.1450380596245; Thu, 17 Dec 2015 11:29:56 -0800 (PST) Return-Path: Received: from lists.gnu.org (lists.gnu.org. [2001:4830:134:3::11]) by mx.google.com with ESMTPS id d9si12622031qgf.101.2015.12.17.11.29.56 for (version=TLS1 cipher=AES128-SHA bits=128/128); Thu, 17 Dec 2015 11:29:56 -0800 (PST) Received-SPF: pass (google.com: domain of qemu-devel-bounces+patch=linaro.org@nongnu.org designates 2001:4830:134:3::11 as permitted sender) client-ip=2001:4830:134:3::11; Authentication-Results: mx.google.com; spf=pass (google.com: domain of qemu-devel-bounces+patch=linaro.org@nongnu.org designates 2001:4830:134:3::11 as permitted sender) smtp.mailfrom=qemu-devel-bounces+patch=linaro.org@nongnu.org Received: from localhost ([::1]:56545 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a9eFP-0002qq-Op for patch@linaro.org; Thu, 17 Dec 2015 14:29:55 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:44261) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a9ddo-0007ZS-5D for qemu-devel@nongnu.org; Thu, 17 Dec 2015 13:51:06 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1a9ddk-0007Oc-S0 for qemu-devel@nongnu.org; Thu, 17 Dec 2015 13:51:04 -0500 Received: from vms173021pub.verizon.net ([206.46.173.21]:43291) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1a9ddk-0007OE-Jt for qemu-devel@nongnu.org; Thu, 17 Dec 2015 13:51:00 -0500 Received: from serve.minyard.net ([173.57.176.17]) by vms173021.mailsrvcs.net (Oracle Communications Messaging Server 7.0.5.32.0 64bit (built Jul 16 2014)) with ESMTPA id <0NZI002GUN05RCA0@vms173021.mailsrvcs.net> for qemu-devel@nongnu.org; Thu, 17 Dec 2015 12:50:33 -0600 (CST) X-CMAE-Score: 0 X-CMAE-Analysis: v=2.1 cv=btqxfxui c=1 sm=1 tr=0 a=bXmWQgKa9n63w7XTPFb8JQ==:117 a=N54-gffFAAAA:8 a=HL3alpDKAAAA:8 a=oR5dmqMzAAAA:8 a=wUQvQvOEmiQA:10 a=fk1lIlRQAAAA:8 a=M6UPdg8J3lmlXw17gUsA:9 a=p17M6jkibKDfapnA:21 a=QDhoZqb1dPrC22sF:21 Received: from t430.minyard.net (t430m.minyard.net [192.168.27.3]) by serve.minyard.net (Postfix) with ESMTPA id 4DDE9326A; Thu, 17 Dec 2015 12:50:29 -0600 (CST) Received: by t430.minyard.net (Postfix, from userid 1000) id B7DC7300FE7; Thu, 17 Dec 2015 12:50:26 -0600 (CST) From: minyard@acm.org To: qemu-devel@nongnu.org, Paolo Bonzini , "Michael S. Tsirkin" , Igor Mammedov Date: Thu, 17 Dec 2015 12:50:09 -0600 Message-id: <1450378219-25799-7-git-send-email-minyard@acm.org> X-Mailer: git-send-email 2.5.0 In-reply-to: <1450378219-25799-1-git-send-email-minyard@acm.org> References: <1450378219-25799-1-git-send-email-minyard@acm.org> X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 206.46.173.21 X-Mailman-Approved-At: Thu, 17 Dec 2015 14:22:15 -0500 Cc: Corey Minyard Subject: [Qemu-devel] [PATCH v5 06/16] ipmi: Add tests X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patch=linaro.org@nongnu.org Sender: qemu-devel-bounces+patch=linaro.org@nongnu.org From: Corey Minyard Test the KCS interface with a local BMC and a BT interface with an external BMC. Signed-off-by: Corey Minyard --- tests/Makefile | 4 + tests/ipmi-bt-test.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/ipmi-kcs-test.c | 295 ++++++++++++++++++++++++++++++++++ 3 files changed, 735 insertions(+) create mode 100644 tests/ipmi-bt-test.c create mode 100644 tests/ipmi-kcs-test.c -- 2.5.0 diff --git a/tests/Makefile b/tests/Makefile index 053c1ae..f41070b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -166,6 +166,8 @@ gcov-files-i386-y += hw/block/hd-geometry.c check-qtest-i386-y += tests/boot-order-test$(EXESUF) check-qtest-i386-y += tests/bios-tables-test$(EXESUF) check-qtest-i386-y += tests/rtc-test$(EXESUF) +check-qtest-i386-y += tests/ipmi-kcs-test$(EXESUF) +check-qtest-i386-y += tests/ipmi-bt-test$(EXESUF) check-qtest-i386-y += tests/i440fx-test$(EXESUF) check-qtest-i386-y += tests/fw_cfg-test$(EXESUF) check-qtest-i386-y += tests/drive_del-test$(EXESUF) @@ -488,6 +490,8 @@ tests/spapr-phb-test$(EXESUF): tests/spapr-phb-test.o $(libqos-obj-y) tests/fdc-test$(EXESUF): tests/fdc-test.o tests/ide-test$(EXESUF): tests/ide-test.o $(libqos-pc-obj-y) tests/ahci-test$(EXESUF): tests/ahci-test.o $(libqos-pc-obj-y) +tests/ipmi-kcs-test$(EXESUF): tests/ipmi-kcs-test.o +tests/ipmi-bt-test$(EXESUF): tests/ipmi-bt-test.o tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o tests/boot-order-test$(EXESUF): tests/boot-order-test.o $(libqos-obj-y) tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o $(libqos-obj-y) diff --git a/tests/ipmi-bt-test.c b/tests/ipmi-bt-test.c new file mode 100644 index 0000000..5b7eb73 --- /dev/null +++ b/tests/ipmi-bt-test.c @@ -0,0 +1,436 @@ +/* + * IPMI BT test cases, using the external interface for checking + * + * Copyright (c) 2012 Corey Minyard + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "libqtest.h" +#include "qemu-common.h" + +#define IPMI_IRQ 5 + +#define IPMI_BT_BASE 0xe4 + +#define IPMI_BT_CTLREG_CLR_WR_PTR 0 +#define IPMI_BT_CTLREG_CLR_RD_PTR 1 +#define IPMI_BT_CTLREG_H2B_ATN 2 +#define IPMI_BT_CTLREG_B2H_ATN 3 +#define IPMI_BT_CTLREG_SMS_ATN 4 +#define IPMI_BT_CTLREG_H_BUSY 6 +#define IPMI_BT_CTLREG_B_BUSY 7 + +#define IPMI_BT_CTLREG_GET(b) ((bt_get_ctrlreg() >> (b)) & 1) +#define IPMI_BT_CTLREG_GET_H2B_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_H2B_ATN) +#define IPMI_BT_CTLREG_GET_B2H_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_B2H_ATN) +#define IPMI_BT_CTLREG_GET_SMS_ATN() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_SMS_ATN) +#define IPMI_BT_CTLREG_GET_H_BUSY() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_H_BUSY) +#define IPMI_BT_CTLREG_GET_B_BUSY() IPMI_BT_CTLREG_GET(IPMI_BT_CTLREG_B_BUSY) + +#define IPMI_BT_CTLREG_SET(b) bt_write_ctrlreg(1 << (b)) +#define IPMI_BT_CTLREG_SET_CLR_WR_PTR() IPMI_BT_CTLREG_SET( \ + IPMI_BT_CTLREG_CLR_WR_PTR) +#define IPMI_BT_CTLREG_SET_CLR_RD_PTR() IPMI_BT_CTLREG_SET( \ + IPMI_BT_CTLREG_CLR_RD_PTR) +#define IPMI_BT_CTLREG_SET_H2B_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_H2B_ATN) +#define IPMI_BT_CTLREG_SET_B2H_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_B2H_ATN) +#define IPMI_BT_CTLREG_SET_SMS_ATN() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_SMS_ATN) +#define IPMI_BT_CTLREG_SET_H_BUSY() IPMI_BT_CTLREG_SET(IPMI_BT_CTLREG_H_BUSY) + +static int bt_ints_enabled; + +static uint8_t bt_get_ctrlreg(void) +{ + return inb(IPMI_BT_BASE); +} + +static void bt_write_ctrlreg(uint8_t val) +{ + outb(IPMI_BT_BASE, val); +} + +static uint8_t bt_get_buf(void) +{ + return inb(IPMI_BT_BASE + 1); +} + +static void bt_write_buf(uint8_t val) +{ + outb(IPMI_BT_BASE + 1, val); +} + +static uint8_t bt_get_irqreg(void) +{ + return inb(IPMI_BT_BASE + 2); +} + +static void bt_write_irqreg(uint8_t val) +{ + outb(IPMI_BT_BASE + 2, val); +} + +static void bt_wait_b_busy(void) +{ + unsigned int count = 1000; + while (IPMI_BT_CTLREG_GET_B_BUSY() != 0) { + g_assert(--count != 0); + } +} + +static void bt_wait_b2h_atn(void) +{ + unsigned int count = 1000; + while (IPMI_BT_CTLREG_GET_B2H_ATN() == 0) { + g_assert(--count != 0); + } +} + + +static int emu_lfd; +static int emu_fd; +static in_port_t emu_port; +static uint8_t inbuf[100]; +static unsigned int inbuf_len; +static unsigned int inbuf_pos; +static int last_was_aa; + +static void read_emu_data(void) +{ + fd_set readfds; + int rv; + struct timeval tv; + + FD_ZERO(&readfds); + FD_SET(emu_fd, &readfds); + tv.tv_sec = 10; + tv.tv_usec = 0; + rv = select(emu_fd + 1, &readfds, NULL, NULL, &tv); + if (rv == -1) { + perror("select"); + } + g_assert(rv == 1); + rv = read(emu_fd, inbuf, sizeof(inbuf)); + if (rv == -1) { + perror("read"); + } + g_assert(rv > 0); + inbuf_len = rv; + inbuf_pos = 0; +} + +static void write_emu_msg(uint8_t *msg, unsigned int len) +{ + int rv; + +#ifdef DEBUG_TEST + { + unsigned int i; + printf("sending:"); + for (i = 0; i < len; i++) { + printf(" %2.2x", msg[i]); + } + printf("\n"); + } +#endif + rv = write(emu_fd, msg, len); + g_assert(rv == len); +} + +static void get_emu_msg(uint8_t *msg, unsigned int *len) +{ + unsigned int outpos = 0; + + for (;;) { + while (inbuf_pos < inbuf_len) { + uint8_t ch = inbuf[inbuf_pos++]; + + g_assert(outpos < *len); + if (last_was_aa) { + assert(ch & 0x10); + msg[outpos++] = ch & ~0x10; + last_was_aa = 0; + } else if (ch == 0xaa) { + last_was_aa = 1; + } else { + msg[outpos++] = ch; + if ((ch == 0xa0) || (ch == 0xa1)) { + /* Message complete */ + *len = outpos; + goto done; + } + } + } + read_emu_data(); + } + done: +#ifdef DEBUG_TEST + { + unsigned int i; + printf("Msg:"); + for (i = 0; i < outpos; i++) { + printf(" %2.2x", msg[i]); + } + printf("\n"); + } +#endif + return; +} + +static uint8_t +ipmb_checksum(const unsigned char *data, int size, unsigned char start) +{ + unsigned char csum = start; + + for (; size > 0; size--, data++) { + csum += *data; + } + return csum; +} + +static uint8_t get_dev_id_cmd[] = { 0x18, 0x01 }; +static uint8_t get_dev_id_rsp[] = { 0x1c, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x02, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static uint8_t set_bmc_globals_cmd[] = { 0x18, 0x2e, 0x0f }; +static uint8_t set_bmc_globals_rsp[] = { 0x1c, 0x2e, 0x00 }; +static uint8_t enable_irq_cmd[] = { 0x05, 0xa1 }; + +static void emu_msg_handler(void) +{ + uint8_t msg[100]; + unsigned int msg_len = sizeof(msg); + + get_emu_msg(msg, &msg_len); + g_assert(msg_len >= 5); + g_assert(msg[msg_len - 1] == 0xa0); + msg_len--; + g_assert(ipmb_checksum(msg, msg_len, 0) == 0); + msg_len--; + if ((msg[1] == get_dev_id_cmd[0]) && (msg[2] == get_dev_id_cmd[1])) { + memcpy(msg + 1, get_dev_id_rsp, sizeof(get_dev_id_rsp)); + msg_len = sizeof(get_dev_id_rsp) + 1; + msg[msg_len] = -ipmb_checksum(msg, msg_len, 0); + msg_len++; + msg[msg_len++] = 0xa0; + write_emu_msg(msg, msg_len); + } else if ((msg[1] == set_bmc_globals_cmd[0]) && + (msg[2] == set_bmc_globals_cmd[1])) { + memcpy(msg + 1, set_bmc_globals_rsp, sizeof(set_bmc_globals_rsp)); + msg_len = sizeof(set_bmc_globals_rsp) + 1; + msg[msg_len] = -ipmb_checksum(msg, msg_len, 0); + msg_len++; + msg[msg_len++] = 0xa0; + write_emu_msg(msg, msg_len); + write_emu_msg(enable_irq_cmd, sizeof(enable_irq_cmd)); + } else { + g_assert(0); + } +} + +static void bt_cmd(uint8_t *cmd, unsigned int cmd_len, + uint8_t *rsp, unsigned int *rsp_len) +{ + unsigned int i, len, j = 0; + uint8_t seq = 5; + + /* Should be idle */ + g_assert(bt_get_ctrlreg() == 0); + + bt_wait_b_busy(); + IPMI_BT_CTLREG_SET_CLR_WR_PTR(); + bt_write_buf(cmd_len + 1); + bt_write_buf(cmd[0]); + bt_write_buf(seq); + for (i = 1; i < cmd_len; i++) { + bt_write_buf(cmd[i]); + } + IPMI_BT_CTLREG_SET_H2B_ATN(); + + emu_msg_handler(); /* We should get a message on the socket here. */ + + bt_wait_b2h_atn(); + if (bt_ints_enabled) { + g_assert((bt_get_irqreg() & 0x02) == 0x02); + g_assert(get_irq(IPMI_IRQ)); + bt_write_irqreg(0x03); + } else { + g_assert(!get_irq(IPMI_IRQ)); + } + IPMI_BT_CTLREG_SET_H_BUSY(); + IPMI_BT_CTLREG_SET_B2H_ATN(); + IPMI_BT_CTLREG_SET_CLR_RD_PTR(); + len = bt_get_buf(); + g_assert(len >= 4); + rsp[0] = bt_get_buf(); + assert(bt_get_buf() == seq); + len--; + for (j = 1; j < len; j++) { + rsp[j] = bt_get_buf(); + } + IPMI_BT_CTLREG_SET_H_BUSY(); + *rsp_len = j; +} + + +/* + * We should get a connect request and a short message with capabilities. + */ +static void test_connect(void) +{ + fd_set readfds; + int rv; + int val; + struct timeval tv; + uint8_t msg[100]; + unsigned int msglen; + static uint8_t exp1[] = { 0xff, 0x01, 0xa1 }; /* A protocol version */ + static uint8_t exp2[] = { 0x08, 0x1f, 0xa1 }; /* A capabilities cmd */ + + FD_ZERO(&readfds); + FD_SET(emu_lfd, &readfds); + tv.tv_sec = 10; + tv.tv_usec = 0; + rv = select(emu_lfd + 1, &readfds, NULL, NULL, &tv); + g_assert(rv == 1); + emu_fd = accept(emu_lfd, NULL, 0); + if (emu_fd < 0) { + perror("accept"); + } + g_assert(emu_fd >= 0); + + val = 1; + rv = setsockopt(emu_fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + g_assert(rv != -1); + + /* Report our version */ + write_emu_msg(exp1, sizeof(exp1)); + + /* Validate that we get the info we expect. */ + msglen = sizeof(msg); + get_emu_msg(msg, &msglen); + g_assert(msglen == sizeof(exp1)); + g_assert(memcmp(msg, exp1, msglen) == 0); + msglen = sizeof(msg); + get_emu_msg(msg, &msglen); + g_assert(msglen == sizeof(exp2)); + g_assert(memcmp(msg, exp2, msglen) == 0); +} + +/* + * Send a get_device_id to do a basic test. + */ +static void test_bt_base(void) +{ + uint8_t rsp[20]; + unsigned int rsplen = sizeof(rsp); + + bt_cmd(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen); + g_assert(rsplen == sizeof(get_dev_id_rsp)); + g_assert(memcmp(get_dev_id_rsp, rsp, rsplen) == 0); +} + +/* + * Enable IRQs for the interface. + */ +static void test_enable_irq(void) +{ + uint8_t rsp[20]; + unsigned int rsplen = sizeof(rsp); + + bt_cmd(set_bmc_globals_cmd, sizeof(set_bmc_globals_cmd), rsp, &rsplen); + g_assert(rsplen == sizeof(set_bmc_globals_rsp)); + g_assert(memcmp(set_bmc_globals_rsp, rsp, rsplen) == 0); + bt_write_irqreg(0x01); + bt_ints_enabled = 1; +} + +/* + * Create a local TCP socket with any port, then save off the port we got. + */ +static void open_socket(void) +{ + struct sockaddr_in myaddr; + socklen_t addrlen; + + myaddr.sin_family = AF_INET; + myaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + myaddr.sin_port = 0; + emu_lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (emu_lfd == -1) { + perror("socket"); + exit(1); + } + if (bind(emu_lfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + perror("bind"); + exit(1); + } + addrlen = sizeof(myaddr); + if (getsockname(emu_lfd, (struct sockaddr *) &myaddr , &addrlen) == -1) { + perror("getsockname"); + exit(1); + } + emu_port = ntohs(myaddr.sin_port); + assert(listen(emu_lfd, 1) != -1); +} + +int main(int argc, char **argv) +{ + const char *arch = qtest_get_arch(); + char *cmdline; + int ret; + + /* Check architecture */ + if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { + g_test_message("Skipping test for non-x86\n"); + return 0; + } + + open_socket(); + + /* Run the tests */ + g_test_init(&argc, &argv, NULL); + + cmdline = g_strdup_printf("-vnc none" + " -chardev socket,id=ipmi0,host=localhost,port=%d,reconnect=10" + " -device ipmi-bmc-extern,chardev=ipmi0,id=bmc0" + " -device isa-ipmi-bt,bmc=bmc0", emu_port); + qtest_start(cmdline); + qtest_irq_intercept_in(global_qtest, "ioapic"); + qtest_add_func("/ipmi/extern/connect", test_connect); + qtest_add_func("/ipmi/extern/bt_base", test_bt_base); + qtest_add_func("/ipmi/extern/bt_enable_irq", test_enable_irq); + qtest_add_func("/ipmi/extern/bt_base_irq", test_bt_base); + ret = g_test_run(); + qtest_quit(global_qtest); + + return ret; +} diff --git a/tests/ipmi-kcs-test.c b/tests/ipmi-kcs-test.c new file mode 100644 index 0000000..564c470 --- /dev/null +++ b/tests/ipmi-kcs-test.c @@ -0,0 +1,295 @@ +/* + * IPMI KCS test cases, using the local interface. + * + * Copyright (c) 2012 Corey Minyard + * + * 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. + */ + +#include +#include +#include + +#include + +#include "libqtest.h" + +#define IPMI_IRQ 5 + +#define IPMI_KCS_BASE 0xca2 + +#define IPMI_KCS_STATUS_ABORT 0x60 +#define IPMI_KCS_CMD_WRITE_START 0x61 +#define IPMI_KCS_CMD_WRITE_END 0x62 +#define IPMI_KCS_CMD_READ 0x68 + +#define IPMI_KCS_ABORTED_BY_CMD 0x01 + +#define IPMI_KCS_CMDREG_GET_STATE() ((kcs_get_cmdreg() >> 6) & 3) +#define IPMI_KCS_STATE_IDLE 0 +#define IPMI_KCS_STATE_READ 1 +#define IPMI_KCS_STATE_WRITE 2 +#define IPMI_KCS_STATE_ERROR 3 +#define IPMI_KCS_CMDREG_GET_CD() ((kcs_get_cmdreg() >> 3) & 1) +#define IPMI_KCS_CMDREG_GET_ATN() ((kcs_get_cmdreg() >> 2) & 1) +#define IPMI_KCS_CMDREG_GET_IBF() ((kcs_get_cmdreg() >> 1) & 1) +#define IPMI_KCS_CMDREG_GET_OBF() ((kcs_get_cmdreg() >> 0) & 1) + +static int kcs_ints_enabled; + +static uint8_t kcs_get_cmdreg(void) +{ + return inb(IPMI_KCS_BASE + 1); +} + +static void kcs_write_cmdreg(uint8_t val) +{ + outb(IPMI_KCS_BASE + 1, val); +} + +static uint8_t kcs_get_datareg(void) +{ + return inb(IPMI_KCS_BASE); +} + +static void kcs_write_datareg(uint8_t val) +{ + outb(IPMI_KCS_BASE, val); +} + +static void kcs_wait_ibf(void) +{ + unsigned int count = 1000; + while (IPMI_KCS_CMDREG_GET_IBF() != 0) { + g_assert(--count != 0); + } +} + +static void kcs_wait_obf(void) +{ + unsigned int count = 1000; + while (IPMI_KCS_CMDREG_GET_OBF() == 0) { + g_assert(--count != 0); + } +} + +static void kcs_clear_obf(void) +{ + if (kcs_ints_enabled) { + g_assert(get_irq(IPMI_IRQ)); + } else { + g_assert(!get_irq(IPMI_IRQ)); + } + g_assert(IPMI_KCS_CMDREG_GET_OBF() == 1); + kcs_get_datareg(); + g_assert(IPMI_KCS_CMDREG_GET_OBF() == 0); + g_assert(!get_irq(IPMI_IRQ)); +} + +static void kcs_check_state(uint8_t state) +{ + g_assert(IPMI_KCS_CMDREG_GET_STATE() == state); +} + +static void kcs_cmd(uint8_t *cmd, unsigned int cmd_len, + uint8_t *rsp, unsigned int *rsp_len) +{ + unsigned int i, j = 0; + + /* Should be idle */ + g_assert(kcs_get_cmdreg() == 0); + + kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_START); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + for (i = 0; i < cmd_len; i++) { + kcs_write_datareg(cmd[i]); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + } + kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_END); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + kcs_write_datareg(0); + next_read_byte: + kcs_wait_ibf(); + switch (IPMI_KCS_CMDREG_GET_STATE()) { + case IPMI_KCS_STATE_READ: + kcs_wait_obf(); + g_assert(j < *rsp_len); + rsp[j++] = kcs_get_datareg(); + kcs_write_datareg(IPMI_KCS_CMD_READ); + goto next_read_byte; + break; + + case IPMI_KCS_STATE_IDLE: + kcs_wait_obf(); + kcs_get_datareg(); + break; + + default: + g_assert(0); + } + *rsp_len = j; +} + +static void kcs_abort(uint8_t *cmd, unsigned int cmd_len, + uint8_t *rsp, unsigned int *rsp_len) +{ + unsigned int i, j = 0; + unsigned int retries = 4; + + /* Should be idle */ + g_assert(kcs_get_cmdreg() == 0); + + kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_START); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + for (i = 0; i < cmd_len; i++) { + kcs_write_datareg(cmd[i]); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + } + kcs_write_cmdreg(IPMI_KCS_CMD_WRITE_END); + kcs_wait_ibf(); + kcs_check_state(IPMI_KCS_STATE_WRITE); + kcs_clear_obf(); + kcs_write_datareg(0); + kcs_wait_ibf(); + switch (IPMI_KCS_CMDREG_GET_STATE()) { + case IPMI_KCS_STATE_READ: + kcs_wait_obf(); + g_assert(j < *rsp_len); + rsp[j++] = kcs_get_datareg(); + kcs_write_datareg(IPMI_KCS_CMD_READ); + break; + + default: + g_assert(0); + } + + /* Start the abort here */ + retry_abort: + g_assert(retries > 0); + + kcs_wait_ibf(); + kcs_write_cmdreg(IPMI_KCS_STATUS_ABORT); + kcs_wait_ibf(); + kcs_clear_obf(); + kcs_write_datareg(0); + kcs_wait_ibf(); + if (IPMI_KCS_CMDREG_GET_STATE() != IPMI_KCS_STATE_READ) { + retries--; + goto retry_abort; + } + kcs_wait_obf(); + rsp[0] = kcs_get_datareg(); + kcs_write_datareg(IPMI_KCS_CMD_READ); + kcs_wait_ibf(); + if (IPMI_KCS_CMDREG_GET_STATE() != IPMI_KCS_STATE_IDLE) { + retries--; + goto retry_abort; + } + kcs_wait_obf(); + kcs_clear_obf(); + + *rsp_len = j; +} + + +static uint8_t get_dev_id_cmd[] = { 0x18, 0x01 }; +static uint8_t get_dev_id_rsp[] = { 0x1c, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* + * Send a get_device_id to do a basic test. + */ +static void test_kcs_base(void) +{ + uint8_t rsp[20]; + unsigned int rsplen = sizeof(rsp); + + kcs_cmd(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen); + g_assert(rsplen == sizeof(get_dev_id_rsp)); + g_assert(memcmp(get_dev_id_rsp, rsp, rsplen) == 0); +} + +/* + * Abort a kcs operation while reading + */ +static void test_kcs_abort(void) +{ + uint8_t rsp[20]; + unsigned int rsplen = sizeof(rsp); + + kcs_abort(get_dev_id_cmd, sizeof(get_dev_id_cmd), rsp, &rsplen); + g_assert(rsp[0] == IPMI_KCS_ABORTED_BY_CMD); +} + +static uint8_t set_bmc_globals_cmd[] = { 0x18, 0x2e, 0x0f }; +static uint8_t set_bmc_globals_rsp[] = { 0x1c, 0x2e, 0x00 }; + +/* + * Enable interrupts + */ +static void test_enable_irq(void) +{ + uint8_t rsp[20]; + unsigned int rsplen = sizeof(rsp); + + kcs_cmd(set_bmc_globals_cmd, sizeof(set_bmc_globals_cmd), rsp, &rsplen); + g_assert(rsplen == sizeof(set_bmc_globals_rsp)); + g_assert(memcmp(set_bmc_globals_rsp, rsp, rsplen) == 0); + kcs_ints_enabled = 1; +} + +int main(int argc, char **argv) +{ + const char *arch = qtest_get_arch(); + char *cmdline; + int ret; + + /* Check architecture */ + if (strcmp(arch, "i386") && strcmp(arch, "x86_64")) { + g_test_message("Skipping test for non-x86\n"); + return 0; + } + + /* Run the tests */ + g_test_init(&argc, &argv, NULL); + + cmdline = g_strdup_printf("-vnc none -device ipmi-bmc-sim,id=bmc0" + " -device isa-ipmi-kcs,bmc=bmc0"); + qtest_start(cmdline); + qtest_irq_intercept_in(global_qtest, "ioapic"); + qtest_add_func("/ipmi/local/kcs_base", test_kcs_base); + qtest_add_func("/ipmi/local/kcs_abort", test_kcs_abort); + qtest_add_func("/ipmi/local/kcs_enable_irq", test_enable_irq); + qtest_add_func("/ipmi/local/kcs_base_irq", test_kcs_base); + qtest_add_func("/ipmi/local/kcs_abort_irq", test_kcs_abort); + ret = g_test_run(); + qtest_quit(global_qtest); + + return ret; +}