| /* |
| * Copyright © 2011 Intel Corporation |
| * |
| * 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 (including the next |
| * paragraph) 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. |
| * |
| * Authors: |
| * Ben Widawsky <ben@bwidawsk.net> |
| * |
| * Notes: |
| * |
| */ |
| |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <assert.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/mman.h> |
| #include <sys/un.h> |
| #include <sys/socket.h> |
| #include "drm.h" |
| #include "i915_drm.h" |
| #include "drmtest.h" |
| #include "intel_chipset.h" |
| #include "intel_bufmgr.h" |
| #include "intel_io.h" |
| #include "intel_batchbuffer.h" |
| #include "intel_debug.h" |
| #include "debug.h" |
| |
| #define EU_ATT 0x7810 |
| #define EU_ATT_CLR 0x7830 |
| |
| #define RSVD_EU -1 |
| #define RSVD_THREAD -1 |
| #define RSVD_ID EUID(-1, -1, -1) |
| |
| enum { |
| EBAD_SHMEM, |
| EBAD_PROTOCOL, |
| EBAD_MAGIC, |
| EBAD_WRITE |
| }; |
| |
| struct debuggee { |
| int euid; |
| int tid; |
| int fd; |
| int clr; |
| uint32_t reg; |
| }; |
| |
| struct debugger { |
| struct debuggee *debuggees; |
| int num_threads; |
| int real_num_threads; |
| int threads_per_eu; |
| } *eu_info; |
| |
| drm_intel_bufmgr *bufmgr; |
| struct intel_batchbuffer *batch; |
| drm_intel_bo *scratch_bo; |
| |
| int handle; |
| int drm_fd; |
| int debug_fd = 0; |
| const char *debug_file = "dump_debug.bin"; |
| int debug; |
| int clear_waits; |
| int shutting_down = 0; |
| struct intel_debug_handshake dh; |
| int force_clear = 0; |
| uint32_t old_td_ctl; |
| |
| /* |
| * The docs are wrong about the attention clear bits. The clear bits are |
| * provided as part of the structure in case they change in future generations. |
| */ |
| #define EUID(eu, td, clear) \ |
| { .euid = eu, .tid = td, .reg = EU_ATT, .fd = -1, .clr = clear } |
| #define EUID2(eu, td, clear) \ |
| { .euid = eu, .tid = td, .reg = EU_ATT + 4, .fd = -1, .clr = clear } |
| struct debuggee gt1_debug_ids[] = { |
| RSVD_ID, RSVD_ID, |
| RSVD_ID, EUID(6, 3, 28), EUID(6, 2, 27), EUID(6, 1, 26), EUID(6, 0, 25), |
| RSVD_ID, EUID(5, 3, 23), EUID(5, 2, 22), EUID(5, 1, 21), EUID(5, 0, 20), |
| RSVD_ID, EUID(4, 3, 18), EUID(4, 2, 17), EUID(4, 1, 16), EUID(4, 0, 15), |
| RSVD_ID, EUID(2, 3, 13), EUID(2, 2, 12), EUID(2, 1, 11), EUID(2, 0, 10), |
| RSVD_ID, EUID(1, 3, 8), EUID(1, 2, 7), EUID(1, 1, 6), EUID(1, 0, 5), |
| RSVD_ID, EUID(0, 3, 3), EUID(0, 2, 2), EUID(0, 1, 1), EUID(0, 0, 0) |
| }; |
| |
| struct debuggee gt2_debug_ids[] = { |
| EUID(8, 1, 31), EUID(8, 0, 30), |
| EUID(6, 4, 29), EUID(6, 3, 28), EUID(6, 2, 27), EUID(6, 1, 26), EUID(6, 0, 25), |
| EUID(5, 4, 24), EUID(5, 3, 23), EUID(5, 2, 22), EUID(5, 1, 21), EUID(5, 0, 20), |
| EUID(4, 4, 19), EUID(4, 3, 18), EUID(4, 2, 17), EUID(4, 1, 16), EUID(4, 0, 15), |
| EUID(2, 4, 14), EUID(2, 3, 13), EUID(2, 2, 12), EUID(2, 1, 11), EUID(2, 0, 10), |
| EUID(1, 4, 9), EUID(1, 3, 8), EUID(1, 2, 7), EUID(1, 1, 6), EUID(1, 0, 5), |
| EUID(0, 4, 4), EUID(0, 3, 3), EUID(0, 2, 2), EUID(0, 1, 1), EUID(0, 0, 0), |
| RSVD_ID, RSVD_ID, RSVD_ID, RSVD_ID, |
| EUID2(14, 4, 27), EUID2(14, 3, 26), EUID2(14, 2, 25), EUID2(14, 1, 24), EUID2(14, 0, 23), |
| EUID2(13, 4, 22), EUID2(13, 3, 21), EUID2(13, 2, 20), EUID2(13, 1, 19), EUID2(13, 0, 18), |
| EUID2(12, 4, 17), EUID2(12, 3, 16), EUID2(12, 2, 15), EUID2(12, 1, 14), EUID2(12, 0, 13), |
| EUID2(10, 4, 12), EUID2(10, 3, 11), EUID2(10, 2, 10), EUID2(10, 1, 9), EUID2(10, 0, 8), |
| EUID2(9, 4, 7), EUID2(9, 3, 6), EUID2(9, 2, 5), EUID2(9, 1, 4), EUID2(9, 0, 3), |
| EUID2(8, 4, 2), EUID2(8, 3, 1), EUID2(8, 2, 0) |
| }; |
| |
| struct debugger gt1 = { |
| .debuggees = gt1_debug_ids, |
| .num_threads = 32, |
| .real_num_threads = 24, |
| .threads_per_eu = 4 |
| }; |
| |
| struct debugger gt2 = { |
| .debuggees = gt2_debug_ids, |
| .num_threads = 64, |
| .real_num_threads = 60, |
| .threads_per_eu = 5 |
| }; |
| |
| static void |
| dump_debug(void *buf, size_t count) { |
| if (!debug_fd) |
| debug_fd = open(debug_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRWXO); |
| |
| if (write(debug_fd, buf, count) == -1) |
| fprintf(stderr, "Error writing to debug file: %s\n", |
| strerror(errno)); |
| } |
| |
| static volatile void * |
| map_debug_buffer(void) { |
| int ret; |
| |
| ret = drm_intel_bo_map(scratch_bo, 0); |
| assert(ret == 0); |
| return scratch_bo->virtual; |
| } |
| |
| static void |
| unmap_debug_buffer(void) { |
| drm_intel_bo_unmap(scratch_bo); |
| } |
| |
| static int |
| wait_for_attn(int timeout, int *out_bits) { |
| int step = 1; |
| int eus_waiting = 0; |
| int i,j; |
| |
| if (timeout <= 0) { |
| timeout = 1; |
| step = 0; |
| } |
| |
| for (i = 0; i < timeout; i += step) { |
| for (j = 0; j < 8; j += 4) { |
| uint32_t attn = intel_register_read(EU_ATT + j); |
| if (attn) { |
| int bit = 0; |
| while( (bit = ffs(attn)) != 0) { |
| bit--; // ffs is 1 based |
| assert(bit >= 0); |
| out_bits[eus_waiting] = bit + (j * 8); |
| attn &= ~(1 << bit); |
| eus_waiting++; |
| } |
| } |
| } |
| |
| if (intel_register_read(EU_ATT + 8) || |
| intel_register_read(EU_ATT + 0xc)) { |
| fprintf(stderr, "Unknown attention bits\n"); |
| } |
| |
| if (eus_waiting || shutting_down) |
| break; |
| } |
| |
| return eus_waiting; |
| } |
| |
| #define eu_fd(bit) eu_info->debuggees[bit].fd |
| #define eu_id(bit) eu_info->debuggees[bit].euid |
| #define eu_tid(bit) eu_info->debuggees[bit].tid |
| static struct eu_state * |
| find_eu_shmem(int bit, volatile uint8_t *buf) { |
| struct eu_state *eu; |
| int mem_tid, mem_euid, i; |
| |
| for(i = 0; i < eu_info->num_threads; i++) { |
| eu = (struct eu_state *)(buf + i * dh.per_thread_scratch); |
| mem_tid = eu->sr0 & 0x7; |
| mem_euid = (eu->sr0 >> 8) & 0xf; |
| if (mem_tid == eu_tid(bit) && mem_euid == eu_id(bit)) |
| break; |
| eu = NULL; |
| } |
| |
| return eu; |
| } |
| |
| #define GRF_CMP(a, b) memcmp(a, b, sizeof(grf)) |
| #define GRF_CPY(a, b) memcpy(a, b, sizeof(grf)) |
| static int |
| verify(struct eu_state *eu) { |
| if (GRF_CMP(eu->version, protocol_version)) { |
| if (debug) { |
| printf("Bad EU protocol version %x %x\n", |
| ((uint32_t *)&eu->version)[0], |
| DEBUG_PROTOCOL_VERSION); |
| dump_debug((void *)eu, sizeof(*eu)); |
| } |
| return -EBAD_PROTOCOL; |
| } |
| |
| if (GRF_CMP(eu->state_magic, eu_msg)) { |
| if (debug) { |
| printf("Bad EU state magic %x %x\n", |
| ((uint32_t *)&eu->state_magic)[0], |
| ((uint32_t *)&eu->state_magic)[1]); |
| dump_debug((void *)eu, sizeof(*eu)); |
| } |
| return -EBAD_MAGIC; |
| } else { |
| GRF_CPY(eu->state_magic, cpu_ack); |
| } |
| |
| eu->sr0 = RSVD_EU << 8 | RSVD_THREAD; |
| return 0; |
| } |
| |
| static int |
| collect_data(int bit, volatile uint8_t *buf) { |
| struct eu_state *eu; |
| ssize_t num; |
| int ret; |
| |
| assert(eu_id(bit) != RSVD_EU); |
| |
| if (eu_fd(bit) == -1) { |
| char name[128]; |
| sprintf(name, "dump_eu_%02d_%d.bin", eu_id(bit), eu_tid(bit)); |
| eu_fd(bit) = open(name, O_CREAT | O_WRONLY | O_TRUNC, S_IRWXO); |
| if (eu_fd(bit) == -1) |
| return -1; |
| } |
| |
| eu = find_eu_shmem(bit, buf); |
| |
| if (eu == NULL) { |
| if (debug) |
| printf("Bad offset %d %d\n", eu_id(bit), eu_tid(bit)); |
| return -EBAD_SHMEM; |
| } |
| |
| ret = verify(eu); |
| if (ret) |
| return ret; |
| |
| num = write(eu_fd(bit), (void *)eu, sizeof(*eu)); |
| if (num != sizeof(*eu)) { |
| perror("unhandled write failure"); |
| return EBAD_WRITE; |
| } |
| |
| |
| return 0; |
| } |
| |
| static void |
| clear_attn(int bit) { |
| #if 0 |
| /* |
| * This works but doesn't allow for easily changed clearing bits |
| */ |
| static void |
| clear_attn_old(int bit) { |
| int bit_to_clear = bit % 32; |
| bit_to_clear = 31 - bit_to_clear; |
| intel_register_write(0x7830 + (bit/32) * 4, 0); |
| intel_register_write(0x7830 + (bit/32) * 4, 1 << bit_to_clear); |
| } |
| #else |
| if (!force_clear) { |
| int bit_to_clear; |
| bit_to_clear = eu_info->debuggees[bit].clr; |
| intel_register_write(EU_ATT_CLR + (bit/32) * 4, 0); |
| intel_register_write(EU_ATT_CLR + (bit/32) * 4, 1 << bit_to_clear); |
| } else { |
| intel_register_write(EU_ATT_CLR + 0, 0); |
| intel_register_write(EU_ATT_CLR + 4, 0); |
| intel_register_write(EU_ATT_CLR + 0, 0xffffffff); |
| intel_register_write(EU_ATT_CLR + 4, 0xffffffff); |
| } |
| #endif |
| } |
| |
| static void |
| db_shutdown(int sig) { |
| shutting_down = 1; |
| printf("Shutting down...\n"); |
| } |
| |
| static void __attribute__((noreturn)) |
| die(int reason) { |
| int i = 0; |
| |
| intel_register_write(EU_ATT_CLR, 0); |
| intel_register_write(EU_ATT_CLR + 4, 0); |
| |
| if (debug_fd) |
| close(debug_fd); |
| |
| for (i = 0; i < eu_info->num_threads; i++) { |
| if (eu_info->debuggees[i].fd != -1) |
| close(eu_info->debuggees[i].fd); |
| } |
| |
| unmap_debug_buffer(); |
| |
| if (old_td_ctl) |
| intel_register_write(TD_CTL, old_td_ctl); |
| intel_register_access_fini(); |
| exit(reason); |
| } |
| |
| static int |
| identify_device(int devid) { |
| if (!IS_SANDYBRIDGE(devid)) |
| return -ENODEV; |
| |
| switch (intel_gt(devid)) { |
| case 0: |
| eu_info = >1; |
| break; |
| case 1: |
| eu_info = >2; |
| break; |
| default: |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| parse_data(const char *file_name) { |
| struct eu_state *eu_state = NULL; |
| struct stat st; |
| int fd = -1; |
| int ret, i, elements; |
| |
| fd = open(file_name, O_RDONLY); |
| if (fd == -1) { |
| perror("open"); |
| goto out; |
| } |
| |
| ret = fstat(fd, &st); |
| if (ret == -1) { |
| perror("fstat"); |
| goto out; |
| } |
| |
| elements = st.st_size / sizeof(struct eu_state); |
| if (elements == 0) { |
| fprintf(stderr, "File not big enough for 1 entry\n"); |
| goto out; |
| } |
| |
| eu_state = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); |
| if (eu_state == MAP_FAILED) { |
| perror("mmap"); |
| goto out; |
| } |
| |
| for(i = 0; i < elements; i++) { |
| printf("AIP: "); |
| printf("%x\n", ((uint32_t *)eu_state[i].cr0)[2]); |
| } |
| out: |
| if (eu_state) |
| munmap(eu_state, st.st_size); |
| if (fd != -1) |
| close(fd); |
| } |
| |
| static int |
| wait_for_scratch_bo(void) { |
| struct sockaddr_un addr; |
| uint32_t version; |
| int fd, ret, dh_handle = -1; |
| |
| assert(sizeof(version) == sizeof(dh.version)); |
| |
| fd = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (fd == -1) |
| return -1; |
| |
| /* Clean up previous runs */ |
| remove(SHADER_DEBUG_SOCKET); |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| strncpy(addr.sun_path, SHADER_DEBUG_SOCKET, sizeof(addr.sun_path) - 1); |
| |
| ret = bind(fd, (const struct sockaddr *)&addr, sizeof(addr)); |
| if (ret == -1) { |
| perror("listen"); |
| return -1; |
| } |
| |
| ret = listen(fd, 1); |
| if (ret == -1) { |
| perror("listen"); |
| goto done; |
| } |
| |
| while(1) { |
| int client_fd; |
| size_t count; |
| char ack[] = DEBUG_HANDSHAKE_ACK; |
| |
| client_fd = accept(fd, NULL, NULL); |
| if (client_fd == -1) { |
| perror("accept"); |
| goto done; |
| } |
| |
| count = read(client_fd, &version, sizeof(version)); |
| if (count != sizeof(version)) { |
| perror("read version"); |
| goto loop_out; |
| } |
| |
| if (version != DEBUG_HANDSHAKE_VERSION) { |
| fprintf(stderr, "Bad debug handshake\n"); |
| goto loop_out; |
| } |
| |
| count = read(client_fd, ((char *)&dh) + 1, sizeof(dh) - 1); |
| if (count != sizeof(dh) - 1) { |
| perror("read handshake"); |
| goto loop_out; |
| } |
| |
| count = write(client_fd, ack, sizeof(ack)); |
| if (count != sizeof(ack)) { |
| perror("write ack"); |
| goto loop_out; |
| } |
| dh_handle = dh.flink_handle; |
| if (debug > 0) { |
| printf("Handshake completed successfully\n" |
| "\tprotocol version = %d\n" |
| "\tflink handle = %d\n" |
| "\tper thread scratch = %x\n", version, |
| dh.flink_handle, dh.per_thread_scratch); |
| } |
| |
| loop_out: |
| close(client_fd); |
| break; |
| } |
| |
| done: |
| close(fd); |
| return dh_handle; |
| } |
| |
| static void |
| setup_hw_bits(void) |
| { |
| intel_register_write(INST_PM, GEN6_GLOBAL_DEBUG_ENABLE | |
| GEN6_GLOBAL_DEBUG_ENABLE << 16); |
| old_td_ctl = intel_register_read(GEN6_TD_CTL); |
| intel_register_write(GEN6_TD_CTL, GEN6_TD_CTL_FORCE_TD_BKPT); |
| } |
| |
| int main(int argc, char* argv[]) { |
| struct pci_device *pci_dev; |
| volatile uint8_t *scratch = NULL; |
| int bits[64]; |
| int devid = -1, opt; |
| |
| while ((opt = getopt(argc, argv, "cdr:pf?h")) != -1) { |
| switch (opt) { |
| case 'c': |
| clear_waits = 1; |
| break; |
| case 'd': |
| debug = 1; |
| break; |
| case 'r': |
| parse_data(optarg); |
| exit(0); |
| break; |
| case 'p': |
| devid = atoi(optarg); |
| break; |
| case 'f': |
| force_clear = 1; |
| break; |
| case '?': |
| case 'h': |
| default: |
| exit(0); |
| } |
| } |
| |
| pci_dev = intel_get_pci_device(); |
| if (devid == -1) |
| devid = pci_dev->device_id; |
| if (identify_device(devid)) { |
| abort(); |
| } |
| |
| assert(intel_register_access_init(pci_dev, 1) == 0); |
| |
| memset(bits, -1, sizeof(bits)); |
| /* |
| * These events have to occur before the SR runs, or we need |
| * non-blocking versions of the functions. |
| */ |
| if (!clear_waits) { |
| int dh_handle; |
| drm_fd = drm_open_driver(DRIVER_INTEL); |
| bufmgr = drm_intel_bufmgr_gem_init(drm_fd, 4096); |
| |
| setup_hw_bits(); |
| |
| /* We are probably root, make files world friendly */ |
| umask(0); |
| dh_handle = wait_for_scratch_bo(); |
| if (dh_handle == -1) { |
| printf("No handle from mesa, please enter manually: "); |
| if (fscanf(stdin, "%1d", &dh_handle) == 0) |
| exit(1); |
| } |
| scratch_bo = intel_bo_gem_create_from_name(bufmgr, "scratch", dh_handle); |
| if (scratch_bo == NULL) { |
| fprintf(stderr, "Couldn't flink buffer\n"); |
| abort(); |
| } |
| signal(SIGINT, db_shutdown); |
| printf("Press Ctrl-C to stop\n"); |
| } else { |
| int time = force_clear ? 0 : 20000; |
| while (wait_for_attn(time, bits)) { |
| clear_attn(bits[0]); |
| memset(bits, -1, sizeof(bits)); |
| } |
| die(0); |
| } |
| |
| scratch = map_debug_buffer(); |
| while (shutting_down == 0) { |
| int num_events, i; |
| |
| memset(bits, -1, sizeof(bits)); |
| num_events = wait_for_attn(-1, bits); |
| if (num_events == 0) |
| break; |
| |
| for (i = 0; i < num_events; i++) { |
| assert(bits[i] < 64 && bits[i] >= 0); |
| if (collect_data(bits[i], scratch)) { |
| bits[i] = -1; |
| continue; |
| } |
| clear_attn(bits[i]); |
| } |
| } |
| |
| die(0); |
| return 0; |
| } |