/* * Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * The driver handles Error's from Control Backbone(CBB) generated due to * illegal accesses. When an error is reported from a NOC within CBB, * the driver checks ErrVld status of all three Error Logger's of that NOC. * It then prints debug information about failed transaction using ErrLog * registers of error logger which has ErrVld set. Currently, SLV, DEC, * TMO, SEC, UNS are the only codes which are supported by CBB. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static LIST_HEAD(cbb_noc_list); static DEFINE_RAW_SPINLOCK(cbb_noc_lock); #define get_mstr_id(userbits) get_noc_errlog_subfield(userbits, 21, 18)-1; static struct tegra_noc_errors noc_errors[] = { {.errcode = "SLV", .src = "Target", .type = "Target error detected by CBB slave" }, {.errcode = "DEC", .src = "Initiator NIU", .type = "Address decode error" }, {.errcode = "UNS", .src = "Target NIU", .type = "Unsupported request. Not a valid transaction" }, {.errcode = "DISC", /* Not Supported by CBB */ .src = "Power Disconnect", .type = "Disconnected target or domain" }, {.errcode = "SEC", .src = "Initiator NIU or Firewall", .type = "Security violation. Firewall error" }, {.errcode = "HIDE", /* Not Supported by CBB */ .src = "Firewall", .type = "Hidden security violation, reported as OK to initiator" }, {.errcode = "TMO", .src = "Target NIU", .type = "Target time-out error" }, {.errcode = "RSV", .src = "None", .type = "Reserved" } }; static char *tegra_noc_opc_trantype[] = { "RD - Read, Incrementing", "RDW - Read, Wrap", /* Not Supported by CBB */ "RDX - Exclusive Read", /* Not Supported by CBB */ "RDL - Linked Read", /* Not Supported by CBB */ "WR - Write, Incrementing", "WRW - Write, Wrap", /* Not Supported by CBB */ "WRC - Exclusive Write", /* Not Supported by CBB */ "PRE - Preamble Sequence for Fixed Accesses" }; static char *tegra_axi2apb_errors[] = { "SFIFONE - Status FIFO Not Empty interrupt", "SFIFOF - Status FIFO Full interrupt", "TIM - Timer(Timeout) interrupt", "SLV - SLVERR interrupt", "NULL", "ERBF - Early response buffer Full interrupt", "NULL", "RDFIFOF - Read Response FIFO Full interrupt", "WRFIFOF - Write Response FIFO Full interrupt", "CH0DFIFOF - Ch0 Data FIFO Full interrupt", "CH1DFIFOF - Ch1 Data FIFO Full interrupt", "CH2DFIFOF - Ch2 Data FIFO Full interrupt", "UAT - Unsupported alignment type error", "UBS - Unsupported burst size error", "UBE - Unsupported Byte Enable error", "UBT - Unsupported burst type error", "BFS - Block Firewall security error", "ARFS - Address Range Firewall security error", "CH0RFIFOF - Ch0 Request FIFO Full interrupt", "CH1RFIFOF - Ch1 Request FIFO Full interrupt", "CH2RFIFOF - Ch2 Request FIFO Full interrupt" }; static void print_cbb_err(struct seq_file *file, const char *fmt, ...) { va_list args; struct va_format vaf; va_start(args, fmt); if (file) { seq_vprintf(file, fmt, args); } else { vaf.fmt = fmt; vaf.va = &args; pr_crit("%pV", &vaf); } va_end(args); } static void cbbcentralnoc_parse_routeid (struct tegra_lookup_noc_aperture *noc_trans_info, u64 routeid) { noc_trans_info->initflow = get_noc_errlog_subfield(routeid, 23, 20); noc_trans_info->targflow = get_noc_errlog_subfield(routeid, 19, 16); noc_trans_info->targ_subrange = get_noc_errlog_subfield(routeid, 15, 9); noc_trans_info->seqid = get_noc_errlog_subfield(routeid, 8, 0); } static void bpmpnoc_parse_routeid (struct tegra_lookup_noc_aperture *noc_trans_info, u64 routeid) { noc_trans_info->initflow = get_noc_errlog_subfield(routeid, 20, 18); noc_trans_info->targflow = get_noc_errlog_subfield(routeid, 17, 13); noc_trans_info->targ_subrange = get_noc_errlog_subfield(routeid, 12, 9); noc_trans_info->seqid = get_noc_errlog_subfield(routeid, 8, 0); } static void aonnoc_parse_routeid (struct tegra_lookup_noc_aperture *noc_trans_info, u64 routeid) { noc_trans_info->initflow = get_noc_errlog_subfield(routeid, 22, 21); noc_trans_info->targflow = get_noc_errlog_subfield(routeid, 20, 15); noc_trans_info->targ_subrange = get_noc_errlog_subfield(routeid, 14, 9); noc_trans_info->seqid = get_noc_errlog_subfield(routeid, 8, 0); } static void scenoc_parse_routeid (struct tegra_lookup_noc_aperture *noc_trans_info, u64 routeid) { noc_trans_info->initflow = get_noc_errlog_subfield(routeid, 21, 19); noc_trans_info->targflow = get_noc_errlog_subfield(routeid, 18, 14); noc_trans_info->targ_subrange = get_noc_errlog_subfield(routeid, 13, 9); noc_trans_info->seqid = get_noc_errlog_subfield(routeid, 8, 0); } static void cvnoc_parse_routeid (struct tegra_lookup_noc_aperture *noc_trans_info, u64 routeid) { noc_trans_info->initflow = get_noc_errlog_subfield(routeid, 18, 16); noc_trans_info->targflow = get_noc_errlog_subfield(routeid, 15, 12); noc_trans_info->targ_subrange = get_noc_errlog_subfield(routeid, 11, 7); noc_trans_info->seqid = get_noc_errlog_subfield(routeid, 6, 0); } static void cbbcentralnoc_parse_userbits (struct tegra_noc_userbits *noc_trans_usrbits, u64 usrbits) { noc_trans_usrbits->axcache = get_noc_errlog_subfield(usrbits, 3, 0); noc_trans_usrbits->non_mod = get_noc_errlog_subfield(usrbits, 4, 4); noc_trans_usrbits->axprot = get_noc_errlog_subfield(usrbits, 7, 5); noc_trans_usrbits->vqc = get_noc_errlog_subfield(usrbits, 9, 8); noc_trans_usrbits->grpsec = get_noc_errlog_subfield(usrbits, 16, 10); noc_trans_usrbits->falconsec = get_noc_errlog_subfield(usrbits, 18, 17); noc_trans_usrbits->mstr_id = get_noc_errlog_subfield(usrbits, 22, 19)-1; noc_trans_usrbits->axi_id = get_noc_errlog_subfield(usrbits, 30, 23); } static void clusternoc_parse_userbits (struct tegra_noc_userbits *noc_trans_usrbits, u64 usrbits) { noc_trans_usrbits->mstr_id = get_noc_errlog_subfield(usrbits, 21, 18)-1; noc_trans_usrbits->vqc = get_noc_errlog_subfield(usrbits, 17, 16); noc_trans_usrbits->grpsec = get_noc_errlog_subfield(usrbits, 15, 9); noc_trans_usrbits->falconsec = get_noc_errlog_subfield(usrbits, 8, 7); noc_trans_usrbits->axprot = get_noc_errlog_subfield(usrbits, 6, 4); noc_trans_usrbits->axcache = get_noc_errlog_subfield(usrbits, 3, 0); } static void cbb_errlogger_faulten(void __iomem *addr) { writel(1, addr+OFF_ERRLOGGER_0_FAULTEN_0); writel(1, addr+OFF_ERRLOGGER_1_FAULTEN_0); writel(1, addr+OFF_ERRLOGGER_2_FAULTEN_0); } static void cbb_errlogger_stallen(void __iomem *addr) { writel(1, addr+OFF_ERRLOGGER_0_STALLEN_0); writel(1, addr+OFF_ERRLOGGER_1_STALLEN_0); writel(1, addr+OFF_ERRLOGGER_2_STALLEN_0); } static void cbb_errlogger_errclr(void __iomem *addr) { writel(1, addr+OFF_ERRLOGGER_0_ERRCLR_0); writel(1, addr+OFF_ERRLOGGER_1_ERRCLR_0); writel(1, addr+OFF_ERRLOGGER_2_ERRCLR_0); } static unsigned int cbb_errlogger_errvld(void __iomem *addr) { unsigned int errvld_status = 0; errvld_status = readl(addr+OFF_ERRLOGGER_0_ERRVLD_0); errvld_status |= (readl(addr+OFF_ERRLOGGER_1_ERRVLD_0) << 1); errvld_status |= (readl(addr+OFF_ERRLOGGER_2_ERRVLD_0) << 2); return errvld_status; } /* * Fetch InitlocalAddress from NOC Aperture lookup table * using Targflow, Targsubrange */ static int get_init_localaddress( struct tegra_lookup_noc_aperture *noc_trans_info, struct tegra_lookup_noc_aperture *lookup_noc_aperture, int max_cnt) { int targ_f = 0, targ_sr = 0; unsigned long long init_localaddress = 0; int targflow = noc_trans_info->targflow; int targ_subrange = noc_trans_info->targ_subrange; for (targ_f = 0; targ_f < max_cnt; targ_f++) { if (lookup_noc_aperture[targ_f].targflow == targflow) { targ_sr = targ_f; do { if (lookup_noc_aperture[targ_sr].targ_subrange == targ_subrange) { init_localaddress = lookup_noc_aperture[targ_sr].init_localaddress; return init_localaddress; } if (targ_sr >= max_cnt) return 0; targ_sr++; } while (lookup_noc_aperture[targ_sr].targflow == lookup_noc_aperture[targ_sr-1].targflow); targ_f = targ_sr; } } return init_localaddress; } static void print_cache(struct seq_file *file, u32 cache) { if ((cache & 0x3) == 0x0) { print_cbb_err(file, "\t Cache\t\t\t: 0x%x -- " "Non-cacheable/Non-Bufferable)\n", cache); return; } if ((cache & 0x3) == 0x1) { print_cbb_err(file, "\t Cache\t\t\t: 0x%x -- Device\n", cache); return; } switch (cache) { case 0x2: print_cbb_err(file, "\t Cache\t\t\t: 0x%x -- Cacheable/Non-Bufferable\n", cache); break; case 0x3: print_cbb_err(file, "\t Cache\t\t\t: 0x%x -- Cacheable/Bufferable\n", cache); break; default: print_cbb_err(file, "\t Cache\t\t\t: 0x%x -- Cacheable\n", cache); } } static void print_prot(struct seq_file *file, u32 prot) { char *data_str; char *secure_str; char *priv_str; data_str = (prot & 0x4) ? "Instruction" : "Data"; secure_str = (prot & 0x2) ? "Non-Secure" : "Secure"; priv_str = (prot & 0x1) ? "Privileged" : "Unprivileged"; print_cbb_err(file, "\t Protection\t\t: 0x%x -- %s, %s, %s Access\n", prot, priv_str, secure_str, data_str); } static unsigned int tegra_axi2apb_errstatus(void __iomem *addr) { unsigned int error_status; error_status = readl(addr+DMAAPB_X_RAW_INTERRUPT_STATUS); writel(0xFFFFFFFF, addr+DMAAPB_X_RAW_INTERRUPT_STATUS); return error_status; } static void print_errlog5(struct seq_file *file, struct tegra_cbb_errlog_record *errlog) { struct tegra_noc_userbits userbits; u32 errlog5 = errlog->errlog5; errlog->tegra_noc_parse_userbits(&userbits, errlog5); if (!strcmp(errlog->name, "CBB-NOC")) { print_cbb_err(file, "\t Non-Modify\t\t: 0x%x\n", userbits.non_mod); print_cbb_err(file, "\t AXI ID\t\t: 0x%x\n", userbits.axi_id); } print_cbb_err(file, "\t Master ID\t\t: %s\n", errlog->tegra_cbb_master_id[userbits.mstr_id]); print_cbb_err(file, "\t Security Group(GRPSEC): 0x%x\n", userbits.grpsec); print_cache(file, userbits.axcache); print_prot(file, userbits.axprot); print_cbb_err(file, "\t FALCONSEC\t\t: 0x%x\n", userbits.falconsec); print_cbb_err(file, "\t Virtual Queuing Channel(VQC): 0x%x\n", userbits.vqc); } /* * Fetch Base Address/InitlocalAddress from NOC aperture lookup table * using TargFlow & Targ_subRange extracted from RouteId. * Perform address reconstruction as below: * Address = Base Address + (ErrLog3+ErrLog4) */ static void print_errlog3_4(struct seq_file *file, u32 errlog3, u32 errlog4, struct tegra_lookup_noc_aperture *noc_trans_info, struct tegra_lookup_noc_aperture *noc_aperture, int max_noc_aperture) { struct resource *res = NULL; u64 addr = 0; addr = errlog4; addr = (addr << 32) | errlog3; /* * if errlog4[7]="1", then it's a joker entry. * joker entry is a rare phenomenon and address is not reliable. * debug should be done using the routeid information alone. */ if (errlog4 & 0x80) print_cbb_err(file, "\t debug using routeid alone as below" " address is a joker entry and not-reliable."); addr += get_init_localaddress(noc_trans_info, noc_aperture, max_noc_aperture); res = locate_resource(&iomem_resource, addr); if (res == NULL) print_cbb_err(file, "\t Address\t\t: 0x%llx" " (unknown device)\n", addr); else print_cbb_err(file, "\t Address\t\t: " "0x%llx -- %s + 0x%llx\n", addr, res->name, addr - res->start); } /* * Get RouteId from ErrLog1+ErrLog2 registers and fetch values of * InitFlow, TargFlow, Targ_subRange and SeqId values from RouteId */ static void print_errlog1_2(struct seq_file *file, struct tegra_cbb_errlog_record *errlog, struct tegra_lookup_noc_aperture *noc_trans_info) { u64 routeid = 0; u32 seqid = 0; routeid = errlog->errlog2; routeid = (routeid<<32)|errlog->errlog1; print_cbb_err(file, "\t RouteId\t\t: 0x%lx\n", routeid); errlog->tegra_noc_parse_routeid(noc_trans_info, routeid); print_cbb_err(file, "\t InitFlow\t\t: %s\n", errlog->tegra_noc_routeid_initflow[noc_trans_info->initflow]); print_cbb_err(file, "\t Targflow\t\t: %s\n", errlog->tegra_noc_routeid_targflow[noc_trans_info->targflow]); print_cbb_err(file, "\t TargSubRange\t\t: %d\n", noc_trans_info->targ_subrange); print_cbb_err(file, "\t SeqId\t\t\t: %d\n", seqid); } /* * Print transcation type, error code and description from ErrLog0 for all * errors. For NOC slave errors, all relevant error info is printed using * ErrLog0 only. But additional information is printed for errors from * APB slaves because for them: * - All errors are logged as SLV(slave) errors in errlog0 due to APB having * only single bit pslverr to report all errors. * - Exact cause is printed by reading DMAAPB_X_RAW_INTERRUPT_STATUS register. * - The driver prints information showing AXI2APB bridge and exact error * only if there is error in any AXI2APB slave. * - There is still no way to disambiguate a DEC error from SLV error type. */ static bool print_errlog0(struct seq_file *file, struct tegra_cbb_errlog_record *errlog) { struct tegra_noc_packet_header hdr; bool is_fatal = true; hdr.lock = errlog->errlog0 & 0x1; hdr.opc = get_noc_errlog_subfield(errlog->errlog0, 4, 1); hdr.errcode = get_noc_errlog_subfield(errlog->errlog0, 10, 8); hdr.len1 = get_noc_errlog_subfield(errlog->errlog0, 27, 16); hdr.format = (errlog->errlog0>>31); print_cbb_err(file, "\t Transaction Type\t: %s\n", tegra_noc_opc_trantype[hdr.opc]); print_cbb_err(file, "\t Error Code\t\t: %s\n", noc_errors[hdr.errcode].errcode); print_cbb_err(file, "\t Error Source\t\t: %s\n", noc_errors[hdr.errcode].src); print_cbb_err(file, "\t Error Description\t: %s\n", noc_errors[hdr.errcode].type); if (!strcmp(noc_errors[hdr.errcode].errcode, "SEC") || !strcmp(noc_errors[hdr.errcode].errcode, "DEC") || !strcmp(noc_errors[hdr.errcode].errcode, "UNS") || !strcmp(noc_errors[hdr.errcode].errcode, "DISC") ) is_fatal = false; else if (!strcmp(noc_errors[hdr.errcode].errcode, "SLV") && (errlog->is_ax2apb_bridge_connected)) { int i = 0, j = 0; int max_axi2apb_err = ARRAY_SIZE(tegra_axi2apb_errors); u32 bus_status = 0; /* For all SLV errors, read DMAAPB_X_RAW_INTERRUPT_STATUS * register to get error status for all AXI2APB bridges and * print only if a bit set for any bridge due to error in * a APB slave. For other NOC slaves, no bit will be set. * So, below line won't get printed. */ for (i = 0; i < errlog->apb_bridge_cnt; i++) { bus_status = tegra_axi2apb_errstatus( errlog->axi2abp_bases[i]); if (bus_status) { for(j = 0; j < max_axi2apb_err; j++) { if (bus_status & (1 << j)) { print_cbb_err(file, "\t " "AXI2APB_%d bridge error: %s" , i, tegra_axi2apb_errors[j]); if (strstr (tegra_axi2apb_errors[j], "Firewall")) is_fatal = false; } } } } } print_cbb_err(file, "\t Packet header Lock\t: %d\n", hdr.lock); print_cbb_err(file, "\t Packet header Len1\t: %d\n", hdr.len1); if (hdr.format) print_cbb_err(file, "\t NOC protocol version\t: %s\n", "version >= 2.7"); else print_cbb_err(file, "\t NOC protocol version\t: %s\n", "version < 2.7"); return is_fatal; } /* * Print debug information about failed transaction using * ErrLog registers of error loggger having ErrVld set */ static bool print_errloggerX_info( struct seq_file *file, struct tegra_cbb_errlog_record *errlog, int errloggerX) { struct tegra_lookup_noc_aperture noc_trans_info = {0,}; bool is_fatal = true; print_cbb_err(file, "\tError Logger\t\t: %d\n", errloggerX); if (errloggerX == 0) { errlog->errlog0 = readl(errlog->vaddr+OFF_ERRLOGGER_0_ERRLOG0_0); errlog->errlog1 = readl(errlog->vaddr+OFF_ERRLOGGER_0_ERRLOG1_0); errlog->errlog2 = readl(errlog->vaddr+OFF_ERRLOGGER_0_RESERVED_00_0); errlog->errlog3 = readl(errlog->vaddr+OFF_ERRLOGGER_0_ERRLOG3_0); errlog->errlog4 = readl(errlog->vaddr+OFF_ERRLOGGER_0_ERRLOG4_0); errlog->errlog5 = readl(errlog->vaddr+OFF_ERRLOGGER_0_ERRLOG5_0); } else if (errloggerX == 1) { errlog->errlog0 = readl(errlog->vaddr+OFF_ERRLOGGER_1_ERRLOG0_0); errlog->errlog1 = readl(errlog->vaddr+OFF_ERRLOGGER_1_ERRLOG1_0); errlog->errlog2 = readl(errlog->vaddr+OFF_ERRLOGGER_1_RESERVED_00_0); errlog->errlog3 = readl(errlog->vaddr+OFF_ERRLOGGER_1_ERRLOG3_0); errlog->errlog4 = readl(errlog->vaddr+OFF_ERRLOGGER_1_ERRLOG4_0); errlog->errlog5 = readl(errlog->vaddr+OFF_ERRLOGGER_1_ERRLOG5_0); } else if (errloggerX == 2) { errlog->errlog0 = readl(errlog->vaddr+OFF_ERRLOGGER_2_ERRLOG0_0); errlog->errlog1 = readl(errlog->vaddr+OFF_ERRLOGGER_2_ERRLOG1_0); errlog->errlog2 = readl(errlog->vaddr+OFF_ERRLOGGER_2_RESERVED_00_0); errlog->errlog3 = readl(errlog->vaddr+OFF_ERRLOGGER_2_ERRLOG3_0); errlog->errlog4 = readl(errlog->vaddr+OFF_ERRLOGGER_2_ERRLOG4_0); errlog->errlog5 = readl(errlog->vaddr+OFF_ERRLOGGER_2_ERRLOG5_0); } print_cbb_err(file, "\tErrLog0\t\t\t: 0x%x\n", errlog->errlog0); is_fatal = print_errlog0(file, errlog); print_cbb_err(file, "\tErrLog1\t\t\t: 0x%x\n", errlog->errlog1); print_cbb_err(file, "\tErrLog2\t\t\t: 0x%x\n", errlog->errlog2); print_errlog1_2(file, errlog, &noc_trans_info); print_cbb_err(file, "\tErrLog3\t\t\t: 0x%x\n", errlog->errlog3); print_cbb_err(file, "\tErrLog4\t\t\t: 0x%x\n", errlog->errlog4); print_errlog3_4(file, errlog->errlog3, errlog->errlog4, &noc_trans_info, errlog->noc_aperture, errlog->max_noc_aperture); print_cbb_err(file, "\tErrLog5\t\t\t: 0x%x\n", errlog->errlog5); if(errlog->errlog5) print_errlog5(file, errlog); return is_fatal; } static bool print_errlog(struct seq_file *file, struct tegra_cbb_errlog_record *errlog, int errvld_status) { bool is_fatal = true; pr_crit("**************************************\n"); pr_crit("* For more Internal Decode Help\n"); pr_crit("* http://nv/cbberr\n"); pr_crit("* NVIDIA userID is required to access\n"); pr_crit("**************************************\n"); pr_crit("CPU:%d, Error:%s\n", smp_processor_id(), errlog->name); if (errvld_status & 0x1) is_fatal = print_errloggerX_info(file, errlog, 0); else if (errvld_status & 0x2) is_fatal = print_errloggerX_info(file, errlog, 1); else if (errvld_status & 0x4) is_fatal = print_errloggerX_info(file, errlog, 2); errlog->errclr(errlog->vaddr); print_cbb_err(file, "\t**************************************\n"); return is_fatal; } static int cbb_serr_callback(struct pt_regs *regs, int reason, unsigned int esr, void *priv) { unsigned int errvld_status = 0; struct tegra_cbb_errlog_record *errlog = priv; int retval = 1; if ((!errlog->is_clk_rst) || (errlog->is_clk_rst && errlog->is_clk_enabled())) { errvld_status = errlog->errvld(errlog->vaddr); if (errvld_status) { print_errlog(NULL, errlog, errvld_status); retval = 0; } } return retval; } static struct tegra_cbb_noc_data tegra194_cbb_central_noc_data = { .name = "CBB-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_cbbcentralnoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_cbbcentralnoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_cbbcentralnoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_cbbcentralnoc_routeid_targflow, .tegra_noc_parse_routeid = cbbcentralnoc_parse_routeid, .tegra_noc_parse_userbits = cbbcentralnoc_parse_userbits, .is_ax2apb_bridge_connected = 1, .erd_mask_inband_err = true, .off_erd_err_config = 0x120c }; static struct tegra_cbb_noc_data tegra194_aon_noc_data = { .name = "AON-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_aonnoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_aonnoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_aonnoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_aonnoc_routeid_targflow, .tegra_noc_parse_routeid = aonnoc_parse_routeid, .tegra_noc_parse_userbits = clusternoc_parse_userbits, .is_ax2apb_bridge_connected = 0, .erd_mask_inband_err = false }; static struct tegra_cbb_noc_data tegra194_bpmp_noc_data = { .name = "BPMP-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_bpmpnoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_bpmpnoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_bpmpnoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_bpmpnoc_routeid_targflow, .tegra_noc_parse_routeid = bpmpnoc_parse_routeid, .tegra_noc_parse_userbits = clusternoc_parse_userbits, .is_ax2apb_bridge_connected = 1, .erd_mask_inband_err = false }; static struct tegra_cbb_noc_data tegra194_rce_noc_data = { .name = "RCE-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_scenoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_scenoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_scenoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_scenoc_routeid_targflow, .tegra_noc_parse_routeid = scenoc_parse_routeid, .tegra_noc_parse_userbits = clusternoc_parse_userbits, .is_ax2apb_bridge_connected = 1, .erd_mask_inband_err = false }; static struct tegra_cbb_noc_data tegra194_sce_noc_data = { .name = "SCE-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_scenoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_scenoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_scenoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_scenoc_routeid_targflow, .tegra_noc_parse_routeid = scenoc_parse_routeid, .tegra_noc_parse_userbits = clusternoc_parse_userbits, .is_ax2apb_bridge_connected = 1, .erd_mask_inband_err = false }; static struct tegra_cbb_noc_data tegra194_cv_noc_data = { .name = "CV-NOC", .errvld = cbb_errlogger_errvld, .faulten = cbb_errlogger_faulten, .stallen = cbb_errlogger_stallen, .errclr = cbb_errlogger_errclr, .tegra_cbb_master_id = t194_master_id, .noc_aperture = t194_cvnoc_aperture_lookup, .max_noc_aperture = ARRAY_SIZE(t194_cvnoc_aperture_lookup), .tegra_noc_routeid_initflow = t194_cvnoc_routeid_initflow, .tegra_noc_routeid_targflow = t194_cvnoc_routeid_targflow, .tegra_noc_parse_routeid = cvnoc_parse_routeid, .tegra_noc_parse_userbits = clusternoc_parse_userbits, .is_ax2apb_bridge_connected = 1, .is_clk_rst = true, .erd_mask_inband_err = false, .is_cluster_probed = is_nvcvnas_probed, .is_clk_enabled = is_nvcvnas_clk_enabled, .tegra_noc_en_clk_rpm = nvcvnas_busy, .tegra_noc_dis_clk_rpm = nvcvnas_idle, .tegra_noc_en_clk_no_rpm = nvcvnas_busy_no_rpm, .tegra_noc_dis_clk_no_rpm = nvcvnas_idle_no_rpm }; static const struct of_device_id axi2apb_match[] = { { .compatible = "nvidia,tegra194-AXI2APB-bridge", }, {}, }; static struct of_device_id tegra_cbb_match[] = { {.compatible = "nvidia,tegra194-CBB-NOC", .data = &tegra194_cbb_central_noc_data}, {.compatible = "nvidia,tegra194-AON-NOC", .data = &tegra194_aon_noc_data}, {.compatible = "nvidia,tegra194-BPMP-NOC", .data = &tegra194_bpmp_noc_data}, {.compatible = "nvidia,tegra194-RCE-NOC", .data = &tegra194_rce_noc_data}, {.compatible = "nvidia,tegra194-SCE-NOC", .data = &tegra194_sce_noc_data}, {.compatible = "nvidia,tegra194-CV-NOC", .data = &tegra194_cv_noc_data}, {}, }; MODULE_DEVICE_TABLE(of, tegra_cbb_match); #ifdef CONFIG_DEBUG_FS static DEFINE_MUTEX(cbb_err_mutex); static int created_root; static int cbb_err_show(struct seq_file *file, void *data) { struct tegra_cbb_errlog_record *errlog; unsigned int errvld_status = 0; mutex_lock(&cbb_err_mutex); list_for_each_entry(errlog, &cbb_noc_list, node) { if ((!errlog->is_clk_rst) || (errlog->is_clk_rst && errlog->is_clk_enabled())) { errvld_status = errlog->errvld(errlog->vaddr); if (errvld_status) { print_errlog(file, errlog, errvld_status); } } } mutex_unlock(&cbb_err_mutex); return 0; } static int cbb_err_open(struct inode *inode, struct file *file) { return single_open(file, cbb_err_show, inode->i_private); } static const struct file_operations cbb_err_fops = { .open = cbb_err_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int cbb_noc_dbgfs_init(void) { struct dentry *d; if (!created_root) { d = debugfs_create_file("tegra_cbb_err", S_IRUGO, NULL, NULL, &cbb_err_fops); if (IS_ERR_OR_NULL(d)) { pr_err("%s: could not create 'tegra_cbb_err' node\n", __func__); return PTR_ERR(d); } created_root = true; } return 0; } #else static int cbb_noc_dbgfs_init(void) { return 0; } #endif /* * Handler for CBB errors from masters other than CCPLEX */ static irqreturn_t tegra_cbb_error_isr(int irq, void *dev_id) { struct tegra_cbb_errlog_record *errlog; bool is_inband_err = false, is_fatal = false; unsigned int errvld_status = 0; unsigned long flags; u8 mstr_id = 0; raw_spin_lock_irqsave(&cbb_noc_lock, flags); list_for_each_entry(errlog, &cbb_noc_list, node) { if ((!errlog->is_clk_rst) || (errlog->is_clk_rst && errlog->is_clk_enabled())) { errvld_status = errlog->errvld(errlog->vaddr); if (errvld_status && ((irq == errlog->noc_secure_irq) || (irq == errlog->noc_nonsecure_irq))) { print_cbb_err(NULL, "CPU:%d, Error:%s@0x%llx," "irq=%d\n", smp_processor_id(), errlog->name, errlog->start, irq); is_fatal = print_errlog(NULL, errlog, errvld_status); mstr_id = get_mstr_id(errlog->errlog5); /* If illegal request is from CCPLEX(id:0x1) * master then call BUG() to crash system. */ if(mstr_id == 0x1) is_inband_err = 1; } } } raw_spin_unlock_irqrestore(&cbb_noc_lock, flags); if (is_inband_err) { if (is_fatal) BUG(); else WARN(true, "Warning due to CBB Error\n"); } return IRQ_HANDLED; } /* * Register handler for CBB_NONSECURE & CBB_SECURE interrupts due to * CBB errors from masters other than CCPLEX */ static int cbb_register_isr(struct platform_device *pdev, struct tegra_cbb_errlog_record *errlog) { int err = 0; errlog->noc_nonsecure_irq = platform_get_irq(pdev, 0); if (errlog->noc_nonsecure_irq <= 0) { dev_err(&pdev->dev, "can't get irq (%d)\n", errlog->noc_nonsecure_irq); err = -ENOENT; goto isr_err; } errlog->noc_secure_irq = platform_get_irq(pdev, 1); if (errlog->noc_secure_irq <= 0) { dev_err(&pdev->dev, "can't get irq (%d)\n", errlog->noc_secure_irq); err = -ENOENT; goto isr_err; } dev_info(&pdev->dev, "noc_secure_irq = %d, noc_nonsecure_irq = %d>\n", errlog->noc_secure_irq, errlog->noc_nonsecure_irq); if (request_irq(errlog->noc_nonsecure_irq, tegra_cbb_error_isr, 0, "noc_nonsecure_irq", pdev)) { dev_err(&pdev->dev, "%s: Unable to register (%d) interrupt\n", __func__, errlog->noc_nonsecure_irq); goto isr_err; } if (request_irq(errlog->noc_secure_irq, tegra_cbb_error_isr, 0, "noc_secure_irq", pdev)) { dev_err(&pdev->dev, "%s: Unable to register (%d) interrupt\n", __func__, errlog->noc_secure_irq); goto isr_err_free_irq; } return 0; isr_err_free_irq: free_irq(errlog->noc_nonsecure_irq, pdev); isr_err: return err; } static void tegra_cbb_error_enable(struct tegra_cbb_errlog_record *errlog) { /* set “StallEn=1” to enable queuing of error packets till * first is served & cleared */ errlog->stallen(errlog->vaddr); /* set “FaultEn=1” to enable error reporting signal “Fault” */ errlog->faulten(errlog->vaddr); } static int tegra_cbb_probe(struct platform_device *pdev) { struct resource *res_base; struct tegra_cbb_errlog_record *errlog; const struct tegra_cbb_noc_data *bdata; struct serr_hook *callback; unsigned long flags; struct device_node *np; int i = 0; int err = 0; /* * CBB don't exist on the simulator */ if (tegra_cpu_is_asim()) return 0; bdata = of_device_get_match_data(&pdev->dev); if (!bdata) { dev_err(&pdev->dev, "No device match found\n"); return -EINVAL; } if (bdata->is_clk_rst) { if (bdata->is_cluster_probed() && !bdata->is_clk_enabled()) { bdata->tegra_noc_en_clk_rpm(); } else { dev_info(&pdev->dev, "defer probe as %s not probed yet", bdata->name); return -EPROBE_DEFER; } } res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) { dev_err(&pdev->dev, "Could not find base address"); return -ENOENT; } err = cbb_noc_dbgfs_init(); if (err) return err; errlog = devm_kzalloc(&pdev->dev, sizeof(*errlog), GFP_KERNEL); if (!errlog) return -ENOMEM; errlog->start = res_base->start; errlog->vaddr = devm_ioremap_resource(&pdev->dev, res_base); if (IS_ERR(errlog->vaddr)) return -EPERM; if (bdata->is_ax2apb_bridge_connected) { np = of_find_matching_node(NULL, axi2apb_match); if (!np) { dev_info(&pdev->dev, "No match found for axi2apb\n"); return -ENOENT; } errlog->apb_bridge_cnt = (of_property_count_elems_of_size(np, "reg", sizeof(u32)))/4; errlog->axi2abp_bases = devm_kzalloc(&pdev->dev, sizeof(u64)*errlog->apb_bridge_cnt, GFP_KERNEL); if (errlog->axi2abp_bases == NULL) return -ENOMEM; for (i = 0; i < errlog->apb_bridge_cnt; i++) { void __iomem *base = of_iomap(np, i); if (!base) { dev_err(&pdev->dev, "failed to map axi2apb range\n"); return -ENOENT; } errlog->axi2abp_bases[i] = base; } } errlog->name = bdata->name; errlog->errvld = bdata->errvld; errlog->errclr = bdata->errclr; errlog->faulten = bdata->faulten; errlog->stallen = bdata->stallen; errlog->noc_aperture = bdata->noc_aperture; errlog->max_noc_aperture = bdata->max_noc_aperture; errlog->tegra_noc_routeid_initflow = bdata->tegra_noc_routeid_initflow; errlog->tegra_noc_routeid_targflow = bdata->tegra_noc_routeid_targflow; errlog->tegra_noc_parse_routeid = bdata->tegra_noc_parse_routeid; errlog->tegra_noc_parse_userbits = bdata->tegra_noc_parse_userbits; errlog->tegra_cbb_master_id = bdata->tegra_cbb_master_id; errlog->is_ax2apb_bridge_connected = bdata->is_ax2apb_bridge_connected; errlog->is_clk_rst = bdata->is_clk_rst; errlog->is_cluster_probed = bdata->is_cluster_probed; errlog->is_clk_enabled = bdata->is_clk_enabled; errlog->tegra_noc_en_clk_rpm = bdata->tegra_noc_en_clk_rpm; errlog->tegra_noc_dis_clk_rpm = bdata->tegra_noc_dis_clk_rpm; errlog->tegra_noc_en_clk_no_rpm = bdata->tegra_noc_en_clk_no_rpm; errlog->tegra_noc_dis_clk_no_rpm = bdata->tegra_noc_dis_clk_no_rpm; platform_set_drvdata(pdev, errlog); callback = devm_kzalloc(&pdev->dev, sizeof(*callback), GFP_KERNEL); callback->fn = cbb_serr_callback; callback->priv = errlog; errlog->callback = callback; raw_spin_lock_irqsave(&cbb_noc_lock, flags); list_add(&errlog->node, &cbb_noc_list); raw_spin_unlock_irqrestore(&cbb_noc_lock, flags); if (bdata->erd_mask_inband_err) { /* set Error Response Disable to mask SError/inband errors */ err = tegra_miscreg_set_erd(bdata->off_erd_err_config); if (err) { dev_err(&pdev->dev, "couldn't mask inband errors\n"); return err; } } /* register handler for CBB errors due to CCPLEX master*/ register_serr_hook(callback); /* register handler for CBB errors due to masters other than CCPLEX*/ err = cbb_register_isr(pdev, errlog); if (err < 0) { dev_err(&pdev->dev, "Failed to register CBB Interrupt ISR"); return err; } tegra_cbb_error_enable(errlog); if ((errlog->is_clk_rst) && (errlog->is_cluster_probed()) && errlog->is_clk_enabled()) errlog->tegra_noc_dis_clk_rpm(); return 0; } static int tegra_cbb_remove(struct platform_device *pdev) { struct resource *res_base; struct tegra_cbb_errlog_record *errlog; unsigned long flags; res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res_base) return 0; raw_spin_lock_irqsave(&cbb_noc_lock, flags); list_for_each_entry(errlog, &cbb_noc_list, node) { if (errlog->start == res_base->start) { unregister_serr_hook(errlog->callback); list_del(&errlog->node); break; } } raw_spin_unlock_irqrestore(&cbb_noc_lock, flags); return 0; } #ifdef CONFIG_PM_SLEEP static int tegra_cbb_suspend_noirq(struct device *dev) { return 0; } static int tegra_cbb_resume_noirq(struct device *dev) { struct tegra_cbb_errlog_record *errlog = dev_get_drvdata(dev); int ret = 0; if (errlog->is_clk_rst) { if (errlog->is_cluster_probed() && !errlog->is_clk_enabled()) errlog->tegra_noc_en_clk_no_rpm(); else { dev_info(dev, "%s not resumed", errlog->name); return 0; } } tegra_cbb_error_enable(errlog); dsb(sy); if ((errlog->is_clk_rst) && (errlog->is_cluster_probed()) && errlog->is_clk_enabled()) errlog->tegra_noc_dis_clk_no_rpm(); dev_info(dev, "%s resumed\n", errlog->name); return ret; } static const struct dev_pm_ops tegra_cbb_pm = { SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(tegra_cbb_suspend_noirq, tegra_cbb_resume_noirq) }; #endif static struct platform_driver tegra_cbb_driver = { .probe = tegra_cbb_probe, .remove = tegra_cbb_remove, .driver = { .owner = THIS_MODULE, .name = "tegra-cbb", .of_match_table = of_match_ptr(tegra_cbb_match), #ifdef CONFIG_PM_SLEEP .pm = &tegra_cbb_pm, #endif }, }; static int __init tegra_cbb_init(void) { return platform_driver_register(&tegra_cbb_driver); } static void __exit tegra_cbb_exit(void) { platform_driver_unregister(&tegra_cbb_driver); } arch_initcall(tegra_cbb_init); module_exit(tegra_cbb_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("SError handler for NOC errors within Control Backbone");