From patchwork Tue Mar 31 15:40:46 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alex_Benn=C3=A9e?= X-Patchwork-Id: 46602 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-wi0-f198.google.com (mail-wi0-f198.google.com [209.85.212.198]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 154802159A for ; Tue, 31 Mar 2015 15:44:09 +0000 (UTC) Received: by wibdy1 with SMTP id dy1sf5357781wib.3 for ; Tue, 31 Mar 2015 08:44:08 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:delivered-to:from:to:date:message-id:in-reply-to :references:mime-version:content-type:content-transfer-encoding:cc :subject:precedence:list-id:list-unsubscribe:list-archive:list-post :list-help:list-subscribe:errors-to:sender:x-original-sender :x-original-authentication-results:mailing-list; bh=6AMJ4u9jYYMhopfjVKIEnbdLEjx7e4TxbBzBythRq+Y=; b=kDmW/CohaUsYbuYvOJA69DmxODzBheun3PPra17YW2nMvgsI35/Unx+P8VBXUqzxxq 4lUsEM5Nt9JOW86K084KndhCRM83Mi2U9LcBU7iBkqVOhwUqFAocbCUr02Gv58lWi/uX Pm8DETsDxaoOODFuu5f4ve2q2CNeTduaok48MllEEAVYHrO3Ay/weTvHPIIvE/c1AUBC axa8/wr2NMyC5CqXuamMpi4UyQAAvG3EqGF093hU2bIME0axzgZxMw/UAJm7KOhZT4TS Bq3D6mEQZEXoLVyw9tEgj8BJlFy0g6YmjqYGfgv5FO0c/OfXyd3zoFdiwfj8/Sit13do 1jMw== X-Gm-Message-State: ALoCoQlEDO4E+eECFf3vsb9eIwM59J473m2o9RciPJLRtXy3HyUCmcv5i8htVkN2ARC+M+kR82EG X-Received: by 10.112.148.10 with SMTP id to10mr8676155lbb.18.1427816648345; Tue, 31 Mar 2015 08:44:08 -0700 (PDT) X-BeenThere: patchwork-forward@linaro.org Received: by 10.152.4.229 with SMTP id n5ls152522lan.100.gmail; Tue, 31 Mar 2015 08:44:08 -0700 (PDT) X-Received: by 10.152.29.68 with SMTP id i4mr31609319lah.19.1427816648035; Tue, 31 Mar 2015 08:44:08 -0700 (PDT) Received: from mail-lb0-f178.google.com (mail-lb0-f178.google.com. [209.85.217.178]) by mx.google.com with ESMTPS id v6si9422370lby.61.2015.03.31.08.44.07 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 31 Mar 2015 08:44:07 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.217.178 as permitted sender) client-ip=209.85.217.178; Received: by lbdc10 with SMTP id c10so15524316lbd.2 for ; Tue, 31 Mar 2015 08:44:07 -0700 (PDT) X-Received: by 10.112.212.106 with SMTP id nj10mr31326965lbc.36.1427816647714; Tue, 31 Mar 2015 08:44:07 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.112.57.201 with SMTP id k9csp66673lbq; Tue, 31 Mar 2015 08:44:06 -0700 (PDT) X-Received: by 10.55.43.14 with SMTP id r14mr79540455qkh.35.1427816646176; Tue, 31 Mar 2015 08:44:06 -0700 (PDT) Received: from lists.gnu.org (lists.gnu.org. [2001:4830:134:3::11]) by mx.google.com with ESMTPS id c60si14035114qga.8.2015.03.31.08.44.05 for (version=TLSv1 cipher=RC4-SHA bits=128/128); Tue, 31 Mar 2015 08:44:06 -0700 (PDT) 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; Received: from localhost ([::1]:39283 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YcyKj-0004fJ-B4 for patch@linaro.org; Tue, 31 Mar 2015 11:44:05 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:42396) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YcyHp-0006Qy-65 for qemu-devel@nongnu.org; Tue, 31 Mar 2015 11:41:07 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YcyHk-0004B1-CF for qemu-devel@nongnu.org; Tue, 31 Mar 2015 11:41:05 -0400 Received: from static.88-198-71-155.clients.your-server.de ([88.198.71.155]:54274 helo=socrates.bennee.com) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YcyHk-0004Am-3G for qemu-devel@nongnu.org; Tue, 31 Mar 2015 11:41:00 -0400 Received: from localhost ([127.0.0.1] helo=zen.linaroharston) by socrates.bennee.com with esmtp (Exim 4.80) (envelope-from ) id 1YczNv-0005y0-50; Tue, 31 Mar 2015 18:51:27 +0200 From: =?UTF-8?q?Alex=20Benn=C3=A9e?= To: qemu-devel@nongnu.org, peter.maydell@linaro.org, christoffer.dall@linaro.org, zhichao.huang@linaro.org Date: Tue, 31 Mar 2015 16:40:46 +0100 Message-Id: <1427816446-31586-5-git-send-email-alex.bennee@linaro.org> X-Mailer: git-send-email 2.3.4 In-Reply-To: <1427816446-31586-1-git-send-email-alex.bennee@linaro.org> References: <1427816446-31586-1-git-send-email-alex.bennee@linaro.org> MIME-Version: 1.0 X-SA-Exim-Connect-IP: 127.0.0.1 X-SA-Exim-Mail-From: alex.bennee@linaro.org X-SA-Exim-Scanned: No (on socrates.bennee.com); SAEximRunCond expanded to false X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 88.198.71.155 Cc: =?UTF-8?q?Alex=20Benn=C3=A9e?= , kvm@vger.kernel.org, marc.zyngier@arm.com, Paolo Bonzini , =?UTF-8?q?Alex=20Benn=C3=A9e?= , kvmarm@lists.cs.columbia.edu, linux-arm-kernel@lists.infradead.org Subject: [Qemu-devel] [PATCH v2 4/4] target-arm: kvm - add support for HW assisted debug 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 X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: alex.bennee@linaro.org X-Original-Authentication-Results: mx.google.com; spf=pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.217.178 as permitted sender) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 From: Alex Bennée This adds basic support for HW assisted debug. The ioctl interface to KVM allows us to pass an implementation defined number of break and watch point registers. When KVM_GUESTDBG_USE_HW_BP is specified these debug registers will be installed in place on the world switch into the guest. The hardware is actually capable of more advanced matching but it is unclear if this expressiveness is available via the gdbstub protocol. Signed-off-by: Alex Bennée --- v2 - correct setting of PMC/BAS/MASK - improved commentary - added helper function to check watchpoint in range - fix find/deletion of watchpoints diff --git a/target-arm/kvm.c b/target-arm/kvm.c index ae0f8b2..d1adf5f 100644 --- a/target-arm/kvm.c +++ b/target-arm/kvm.c @@ -17,6 +17,7 @@ #include "qemu-common.h" #include "qemu/timer.h" +#include "qemu/error-report.h" #include "sysemu/sysemu.h" #include "sysemu/kvm.h" #include "kvm_arm.h" @@ -476,6 +477,8 @@ void kvm_arch_post_run(CPUState *cs, struct kvm_run *run) #define HSR_EC_SHIFT 26 #define HSR_EC_SOFT_STEP 0x32 +#define HSR_EC_HW_BKPT 0x30 +#define HSR_EC_HW_WATCH 0x34 #define HSR_EC_SW_BKPT 0x3c static int kvm_handle_debug(CPUState *cs, struct kvm_run *run) @@ -496,6 +499,16 @@ static int kvm_handle_debug(CPUState *cs, struct kvm_run *run) return true; } break; + case HSR_EC_HW_BKPT: + if (kvm_arm_find_hw_breakpoint(cs, arch_info->pc)) { + return true; + } + break; + case HSR_EC_HW_WATCH: + if (kvm_arm_find_hw_watchpoint(cs, arch_info->far)) { + return true; + } + break; default: error_report("%s: unhandled debug exit (%x, %llx)\n", __func__, arch_info->hsr, arch_info->pc); @@ -556,6 +569,10 @@ void kvm_arch_update_guest_debug(CPUState *cs, struct kvm_guest_debug *dbg) if (kvm_sw_breakpoints_active(cs)) { dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; } + if (kvm_hw_breakpoints_active(cs)) { + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; + kvm_copy_hw_breakpoint_data(&dbg->arch); + } } /* C6.6.29 BRK instruction */ @@ -582,26 +599,6 @@ int kvm_arch_remove_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp) return 0; } -int kvm_arch_insert_hw_breakpoint(target_ulong addr, - target_ulong len, int type) -{ - qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__); - return -EINVAL; -} - -int kvm_arch_remove_hw_breakpoint(target_ulong addr, - target_ulong len, int type) -{ - qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__); - return -EINVAL; -} - - -void kvm_arch_remove_all_hw_breakpoints(void) -{ - qemu_log_mask(LOG_UNIMP, "%s: not implemented\n", __func__); -} - void kvm_arch_init_irq_routing(KVMState *s) { } diff --git a/target-arm/kvm64.c b/target-arm/kvm64.c index 8cf3a62..dbe81a7 100644 --- a/target-arm/kvm64.c +++ b/target-arm/kvm64.c @@ -2,6 +2,7 @@ * ARM implementation of KVM hooks, 64 bit specific code * * Copyright Mian-M. Hamayun 2013, Virtual Open Systems + * Copyright Alex Bennée 2014, Linaro * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. @@ -12,11 +13,17 @@ #include #include #include +#include +#include +#include #include #include "qemu-common.h" #include "qemu/timer.h" +#include "qemu/host-utils.h" +#include "qemu/error-report.h" +#include "exec/gdbstub.h" #include "sysemu/sysemu.h" #include "sysemu/kvm.h" #include "kvm_arm.h" @@ -24,6 +31,312 @@ #include "internals.h" #include "hw/arm/arm.h" +/* Max and current break/watch point counts */ +int max_hw_bp, max_hw_wp; +int cur_hw_bp, cur_hw_wp; +struct kvm_guest_debug_arch guest_debug_registers; + +/** + * kvm_arm_init_debug() + * @cs: CPUState + * + * kvm_check_extension returns 0 if we have no debug registers or the + * number we have. + * + */ +static void kvm_arm_init_debug(CPUState *cs) +{ + max_hw_wp = kvm_check_extension(cs->kvm_state, KVM_CAP_GUEST_DEBUG_HW_WPS); + max_hw_bp = kvm_check_extension(cs->kvm_state, KVM_CAP_GUEST_DEBUG_HW_BPS); + return; +} + +/** + * insert_hw_breakpoint() + * @addr: address of breakpoint + * + * See ARM ARM D2.9.1 for details but here we are only going to create + * simple un-linked breakpoints (i.e. we don't chain breakpoints + * together to match address and context or vmid). The hardware is + * capable of fancier matching but that will require exposing that + * fanciness to GDB's interface + * + * D7.3.2 DBGBCR_EL1, Debug Breakpoint Control Registers + * + * 31 24 23 20 19 16 15 14 13 12 9 8 5 4 3 2 1 0 + * +------+------+-------+-----+----+------+-----+------+-----+---+ + * | RES0 | BT | LBN | SSC | HMC| RES0 | BAS | RES0 | PMC | E | + * +------+------+-------+-----+----+------+-----+------+-----+---+ + * + * BT: Breakpoint type (0 = unlinked address match) + * LBN: Linked BP number (0 = unused) + * SSC/HMC/PMC: Security, Higher and Priv access control (Table D-12) + * BAS: Byte Address Select (RES1 for AArch64) + * E: Enable bit + */ +static int insert_hw_breakpoint(target_ulong addr) +{ + uint32_t bcr = 0x1; /* E=1, enable */ + if (cur_hw_bp >= max_hw_bp) { + return -ENOBUFS; + } + bcr = deposit32(bcr, 1, 2, 0x3); /* PMC = 11 */ + bcr = deposit32(bcr, 5, 4, 0xf); /* BAS = RES1 */ + guest_debug_registers.dbg_bcr[cur_hw_bp] = bcr; + guest_debug_registers.dbg_bvr[cur_hw_bp] = addr; + cur_hw_bp++; + return 0; +} + +/** + * delete_hw_breakpoint() + * @pc: address of breakpoint + * + * Delete a breakpoint and shuffle any above down + */ + +static int delete_hw_breakpoint(target_ulong pc) +{ + int i; + for (i = 0; i < cur_hw_bp; i++) { + if (guest_debug_registers.dbg_bvr[i] == pc) { + while (i < cur_hw_bp) { + if (i == max_hw_bp) { + guest_debug_registers.dbg_bvr[i] = 0; + guest_debug_registers.dbg_bcr[i] = 0; + } else { + guest_debug_registers.dbg_bvr[i] = + guest_debug_registers.dbg_bvr[i + 1]; + guest_debug_registers.dbg_bcr[i] = + guest_debug_registers.dbg_bcr[i + 1]; + } + i++; + } + cur_hw_bp--; + return 0; + } + } + return -ENOENT; +} + +/** + * insert_hw_watchpoint() + * @addr: address of watch point + * @len: size of area + * @type: type of watch point + * + * See ARM ARM D2.10. As with the breakpoints we can do some advanced + * stuff if we want to. The watch points can be linked with the break + * points above to make them context aware. However for simplicity + * currently we only deal with simple read/write watch points. + * + * D7.3.11 DBGWCR_EL1, Debug Watchpoint Control Registers + * + * 31 29 28 24 23 21 20 19 16 15 14 13 12 5 4 3 2 1 0 + * +------+-------+------+----+-----+-----+-----+-----+-----+-----+---+ + * | RES0 | MASK | RES0 | WT | LBN | SSC | HMC | BAS | LSC | PAC | E | + * +------+-------+------+----+-----+-----+-----+-----+-----+-----+---+ + * + * MASK: num bits addr mask (0=none,01/10=res,11=3 bits (8 bytes)) + * WT: 0 - unlinked, 1 - linked (not currently used) + * LBN: Linked BP number (not currently used) + * SSC/HMC/PAC: Security, Higher and Priv access control (Table D-12) + * BAS: Byte Address Select + * LSC: Load/Store control (01: load, 10: store, 11: both) + * E: Enable + * + * The bottom 2 bits of the value register are masked. Therefor to + * break on an sizes smaller than unaligned byte you need to set + * MASK=0, BAS=bit per byte in question. For larger regions (^2) you + * need to ensure you mask the address as required and set BAS=0xff + */ + +static int insert_hw_watchpoint(target_ulong addr, + target_ulong len, int type) +{ + uint32_t dbgwcr = 1; /* E=1, enable */ + uint64_t dbgwvr = addr & (~0x7ULL); + + if (cur_hw_wp >= max_hw_wp) { + return -ENOBUFS; + } + + /* PAC 00 is reserved, assume EL1 exception */ + dbgwcr = deposit32(dbgwcr, 1, 2, 1); + + switch (type) { + case GDB_WATCHPOINT_READ: + dbgwcr = deposit32(dbgwcr, 3, 2, 1); + break; + case GDB_WATCHPOINT_WRITE: + dbgwcr = deposit32(dbgwcr, 3, 2, 2); + break; + case GDB_WATCHPOINT_ACCESS: + dbgwcr = deposit32(dbgwcr, 3, 2, 3); + break; + default: + g_assert_not_reached(); + break; + } + if (len <= 8) { + /* we align the address and set the bits in BAS */ + int off = addr & 0x7; + int bas = (1 << len)-1; + dbgwcr = deposit32(dbgwcr, 5+off, 8-off, bas); + } else { + /* For ranges above 8 bytes we need to be a power of 2 */ + if (ctpop64(len)==1) { + int bits = ctz64(len); + dbgwvr &= ~((1 << bits)-1); + dbgwcr = deposit32(dbgwcr, 24, 4, bits); + dbgwcr = deposit32(dbgwcr, 5, 8, 0xff); + } else { + return -ENOBUFS; + } + } + + guest_debug_registers.dbg_wcr[cur_hw_wp] = dbgwcr; + guest_debug_registers.dbg_wvr[cur_hw_wp] = dbgwvr; + cur_hw_wp++; + return 0; +} + + +static bool check_watchpoint_in_range(int i, target_ulong addr) +{ + uint32_t dbgwcr = guest_debug_registers.dbg_wcr[i]; + uint64_t addr_top, addr_bottom = guest_debug_registers.dbg_wvr[i]; + int bas = extract32(dbgwcr, 5, 8); + int mask = extract32(dbgwcr, 24, 4); + + if (mask) { + addr_top = addr_bottom + (1 << mask); + } else { + /* BAS must be contiguous but can offset against the base + * address in DBGWVR */ + addr_bottom = addr_bottom + ctz32(bas); + addr_top = addr_bottom + clo32(bas); + } + + if (addr >= addr_bottom && addr <= addr_top ) { + return true; + } + + return false; +} + +/** + * delete_hw_watchpoint() + * @addr: address of breakpoint + * + * Delete a breakpoint and shuffle any above down + */ + +static int delete_hw_watchpoint(target_ulong addr, + target_ulong len, int type) +{ + int i; + for (i = 0; i < cur_hw_wp; i++) { + if (check_watchpoint_in_range(i, addr)) { + while (i < cur_hw_wp) { + if (i == max_hw_wp) { + guest_debug_registers.dbg_wvr[i] = 0; + guest_debug_registers.dbg_wcr[i] = 0; + } else { + guest_debug_registers.dbg_wvr[i] = + guest_debug_registers.dbg_wvr[i + 1]; + guest_debug_registers.dbg_wcr[i] = + guest_debug_registers.dbg_wcr[i + 1]; + } + i++; + } + cur_hw_wp--; + return 0; + } + } + return -ENOENT; +} + + +int kvm_arch_insert_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + switch (type) { + case GDB_BREAKPOINT_HW: + return insert_hw_breakpoint(addr); + break; + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_ACCESS: + return insert_hw_watchpoint(addr, len, type); + default: + return -ENOSYS; + } +} + +int kvm_arch_remove_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + switch (type) { + case GDB_BREAKPOINT_HW: + return delete_hw_breakpoint(addr); + break; + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_ACCESS: + return delete_hw_watchpoint(addr, len, type); + default: + return -ENOSYS; + } +} + + +void kvm_arch_remove_all_hw_breakpoints(void) +{ + memset((void *)&guest_debug_registers, 0, sizeof(guest_debug_registers)); + cur_hw_bp = 0; + cur_hw_wp = 0; +} + +void kvm_copy_hw_breakpoint_data(struct kvm_guest_debug_arch *ptr) +{ + /* Compile time assert? */ + g_assert(sizeof(struct kvm_guest_debug_arch) == sizeof(guest_debug_registers)); + memcpy(ptr, &guest_debug_registers, sizeof(guest_debug_registers)); +} + +bool kvm_hw_breakpoints_active(CPUState *cs) +{ + return ( (cur_hw_bp > 0) || (cur_hw_wp >0) ) ? TRUE:FALSE; +} + +bool kvm_arm_find_hw_breakpoint(CPUState *cpu, target_ulong pc) +{ + if (kvm_hw_breakpoints_active(cpu)) { + int i; + for (i=0; i