// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2018 Arm Limited. All rights reserved. * * Coresight Address Translation Unit support * * Author: Suzuki K Poulose <suzuki.poulose@arm.com> */ #include <linux/amba/bus.h> #include <linux/device.h> #include <linux/dma-mapping.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/slab.h> #include "coresight-catu.h" #include "coresight-priv.h" #include "coresight-tmc.h" #define csdev_to_catu_drvdata(csdev) \ dev_get_drvdata(csdev->dev.parent) /* Verbose output for CATU table contents */ #ifdef CATU_DEBUG #define catu_dbg(x, ...) dev_dbg(x, __VA_ARGS__) #else #define catu_dbg(x, ...) do {} while (0) #endif DEFINE_CORESIGHT_DEVLIST(catu_devs, "catu"); struct catu_etr_buf { struct tmc_sg_table *catu_table; dma_addr_t sladdr; }; /* * CATU uses a page size of 4KB for page tables as well as data pages. * Each 64bit entry in the table has the following format. * * 63 12 1 0 * ------------------------------------ * | Address [63-12] | SBZ | V| * ------------------------------------ * * Where bit[0] V indicates if the address is valid or not. * Each 4K table pages have upto 256 data page pointers, taking upto 2K * size. There are two Link pointers, pointing to the previous and next * table pages respectively at the end of the 4K page. (i.e, entry 510 * and 511). * E.g, a table of two pages could look like : * * Table Page 0 Table Page 1 * SLADDR ===> x------------------x x--> x-----------------x * INADDR ->| Page 0 | V | | | Page 256 | V | <- INADDR+1M * |------------------| | |-----------------| * INADDR+4K ->| Page 1 | V | | | | * |------------------| | |-----------------| * | Page 2 | V | | | | * |------------------| | |-----------------| * | ... | V | | | ... | * |------------------| | |-----------------| * INADDR+1020K| Page 255 | V | | | Page 511 | V | * SLADDR+2K==>|------------------| | |-----------------| * | UNUSED | | | | | * |------------------| | | | * | UNUSED | | | | | * |------------------| | | | * | ... | | | | | * |------------------| | |-----------------| * | IGNORED | 0 | | | Table Page 0| 1 | * |------------------| | |-----------------| * | Table Page 1| 1 |--x | IGNORED | 0 | * x------------------x x-----------------x * SLADDR+4K==> * * The base input address (used by the ETR, programmed in INADDR_{LO,HI}) * must be aligned to 1MB (the size addressable by a single page table). * The CATU maps INADDR{LO:HI} to the first page in the table pointed * to by SLADDR{LO:HI} and so on. * */ typedef u64 cate_t; #define CATU_PAGE_SHIFT 12 #define CATU_PAGE_SIZE (1UL << CATU_PAGE_SHIFT) #define CATU_PAGES_PER_SYSPAGE (PAGE_SIZE / CATU_PAGE_SIZE) /* Page pointers are only allocated in the first 2K half */ #define CATU_PTRS_PER_PAGE ((CATU_PAGE_SIZE >> 1) / sizeof(cate_t)) #define CATU_PTRS_PER_SYSPAGE (CATU_PAGES_PER_SYSPAGE * CATU_PTRS_PER_PAGE) #define CATU_LINK_PREV ((CATU_PAGE_SIZE / sizeof(cate_t)) - 2) #define CATU_LINK_NEXT ((CATU_PAGE_SIZE / sizeof(cate_t)) - 1) #define CATU_ADDR_SHIFT 12 #define CATU_ADDR_MASK ~(((cate_t)1 << CATU_ADDR_SHIFT) - 1) #define CATU_ENTRY_VALID ((cate_t)0x1) #define CATU_VALID_ENTRY(addr) \ (((cate_t)(addr) & CATU_ADDR_MASK) | CATU_ENTRY_VALID) #define CATU_ENTRY_ADDR(entry) ((cate_t)(entry) & ~((cate_t)CATU_ENTRY_VALID)) /* CATU expects the INADDR to be aligned to 1M. */ #define CATU_DEFAULT_INADDR (1ULL << 20) /* * catu_get_table : Retrieve the table pointers for the given @offset * within the buffer. The buffer is wrapped around to a valid offset. * * Returns : The CPU virtual address for the beginning of the table * containing the data page pointer for @offset. If @daddrp is not NULL, * @daddrp points the DMA address of the beginning of the table. */ static inline cate_t *catu_get_table(struct tmc_sg_table *catu_table, unsigned long offset, dma_addr_t *daddrp) { unsigned long buf_size = tmc_sg_table_buf_size(catu_table); unsigned int table_nr, pg_idx, pg_offset; struct tmc_pages *table_pages = &catu_table->table_pages; void *ptr; /* Make sure offset is within the range */ offset %= buf_size; /* * Each table can address 1MB and a single kernel page can * contain "CATU_PAGES_PER_SYSPAGE" CATU tables. */ table_nr = offset >> 20; /* Find the table page where the table_nr lies in */ pg_idx = table_nr / CATU_PAGES_PER_SYSPAGE; pg_offset = (table_nr % CATU_PAGES_PER_SYSPAGE) * CATU_PAGE_SIZE; if (daddrp) *daddrp = table_pages->daddrs[pg_idx] + pg_offset; ptr = page_address(table_pages->pages[pg_idx]); return (cate_t *)((unsigned long)ptr + pg_offset); } #ifdef CATU_DEBUG static void catu_dump_table(struct tmc_sg_table *catu_table) { int i; cate_t *table; unsigned long table_end, buf_size, offset = 0; buf_size = tmc_sg_table_buf_size(catu_table); dev_dbg(catu_table->dev, "Dump table %p, tdaddr: %llx\n", catu_table, catu_table->table_daddr); while (offset < buf_size) { table_end = offset + SZ_1M < buf_size ? offset + SZ_1M : buf_size; table = catu_get_table(catu_table, offset, NULL); for (i = 0; offset < table_end; i++, offset += CATU_PAGE_SIZE) dev_dbg(catu_table->dev, "%d: %llx\n", i, table[i]); dev_dbg(catu_table->dev, "Prev : %llx, Next: %llx\n", table[CATU_LINK_PREV], table[CATU_LINK_NEXT]); dev_dbg(catu_table->dev, "== End of sub-table ==="); } dev_dbg(catu_table->dev, "== End of Table ==="); } #else static inline void catu_dump_table(struct tmc_sg_table *catu_table) { } #endif static inline cate_t catu_make_entry(dma_addr_t addr) { return addr ? CATU_VALID_ENTRY(addr) : 0; } /* * catu_populate_table : Populate the given CATU table. * The table is always populated as a circular table. * i.e, the "prev" link of the "first" table points to the "last" * table and the "next" link of the "last" table points to the * "first" table. The buffer should be made linear by calling * catu_set_table(). */ static void catu_populate_table(struct tmc_sg_table *catu_table) { int i; int sys_pidx; /* Index to current system data page */ int catu_pidx; /* Index of CATU page within the system data page */ unsigned long offset, buf_size, table_end; dma_addr_t data_daddr; dma_addr_t prev_taddr, next_taddr, cur_taddr; cate_t *table_ptr, *next_table; buf_size = tmc_sg_table_buf_size(catu_table); sys_pidx = catu_pidx = 0; offset = 0; table_ptr = catu_get_table(catu_table, 0, &cur_taddr); prev_taddr = 0; /* Prev link for the first table */ while (offset < buf_size) { /* * The @offset is always 1M aligned here and we have an * empty table @table_ptr to fill. Each table can address * upto 1MB data buffer. The last table may have fewer * entries if the buffer size is not aligned. */ table_end = (offset + SZ_1M) < buf_size ? (offset + SZ_1M) : buf_size; for (i = 0; offset < table_end; i++, offset += CATU_PAGE_SIZE) { data_daddr = catu_table->data_pages.daddrs[sys_pidx] + catu_pidx * CATU_PAGE_SIZE; catu_dbg(catu_table->dev, "[table %5ld:%03d] 0x%llx\n", (offset >> 20), i, data_daddr); table_ptr[i] = catu_make_entry(data_daddr); /* Move the pointers for data pages */ catu_pidx = (catu_pidx + 1) % CATU_PAGES_PER_SYSPAGE; if (catu_pidx == 0) sys_pidx++; } /* * If we have finished all the valid entries, fill the rest of * the table (i.e, last table page) with invalid entries, * to fail the lookups. */ if (offset == buf_size) { memset(&table_ptr[i], 0, sizeof(cate_t) * (CATU_PTRS_PER_PAGE - i)); next_taddr = 0; } else { next_table = catu_get_table(catu_table, offset, &next_taddr); } table_ptr[CATU_LINK_PREV] = catu_make_entry(prev_taddr); table_ptr[CATU_LINK_NEXT] = catu_make_entry(next_taddr); catu_dbg(catu_table->dev, "[table%5ld]: Cur: 0x%llx Prev: 0x%llx, Next: 0x%llx\n", (offset >> 20) - 1, cur_taddr, prev_taddr, next_taddr); /* Update the prev/next addresses */ if (next_taddr) { prev_taddr = cur_taddr; cur_taddr = next_taddr; table_ptr = next_table; } } /* Sync the table for device */ tmc_sg_table_sync_table(catu_table); } static struct tmc_sg_table * catu_init_sg_table(struct device *catu_dev, int node, ssize_t size, void **pages) { int nr_tpages; struct tmc_sg_table *catu_table; /* * Each table can address upto 1MB and we can have * CATU_PAGES_PER_SYSPAGE tables in a system page. */ nr_tpages = DIV_ROUND_UP(size, SZ_1M) / CATU_PAGES_PER_SYSPAGE; catu_table = tmc_alloc_sg_table(catu_dev, node, nr_tpages, size >> PAGE_SHIFT, pages); if (IS_ERR(catu_table)) return catu_table; catu_populate_table(catu_table); dev_dbg(catu_dev, "Setup table %p, size %ldKB, %d table pages\n", catu_table, (unsigned long)size >> 10, nr_tpages); catu_dump_table(catu_table); return catu_table; } static void catu_free_etr_buf(struct etr_buf *etr_buf) { struct catu_etr_buf *catu_buf; if (!etr_buf || etr_buf->mode != ETR_MODE_CATU || !etr_buf->private) return; catu_buf = etr_buf->private; tmc_free_sg_table(catu_buf->catu_table); kfree(catu_buf); } static ssize_t catu_get_data_etr_buf(struct etr_buf *etr_buf, u64 offset, size_t len, char **bufpp) { struct catu_etr_buf *catu_buf = etr_buf->private; return tmc_sg_table_get_data(catu_buf->catu_table, offset, len, bufpp); } static void catu_sync_etr_buf(struct etr_buf *etr_buf, u64 rrp, u64 rwp) { struct catu_etr_buf *catu_buf = etr_buf->private; struct tmc_sg_table *catu_table = catu_buf->catu_table; u64 r_offset, w_offset; /* * ETR started off at etr_buf->hwaddr. Convert the RRP/RWP to * offsets within the trace buffer. */ r_offset = rrp - etr_buf->hwaddr; w_offset = rwp - etr_buf->hwaddr; if (!etr_buf->full) { etr_buf->len = w_offset - r_offset; if (w_offset < r_offset) etr_buf->len += etr_buf->size; } else { etr_buf->len = etr_buf->size; } etr_buf->offset = r_offset; tmc_sg_table_sync_data_range(catu_table, r_offset, etr_buf->len); } static int catu_alloc_etr_buf(struct tmc_drvdata *tmc_drvdata, struct etr_buf *etr_buf, int node, void **pages) { struct coresight_device *csdev; struct tmc_sg_table *catu_table; struct catu_etr_buf *catu_buf; csdev = tmc_etr_get_catu_device(tmc_drvdata); if (!csdev) return -ENODEV; catu_buf = kzalloc(sizeof(*catu_buf), GFP_KERNEL); if (!catu_buf) return -ENOMEM; catu_table = catu_init_sg_table(&csdev->dev, node, etr_buf->size, pages); if (IS_ERR(catu_table)) { kfree(catu_buf); return PTR_ERR(catu_table); } etr_buf->mode = ETR_MODE_CATU; etr_buf->private = catu_buf; etr_buf->hwaddr = CATU_DEFAULT_INADDR; catu_buf->catu_table = catu_table; /* Get the table base address */ catu_buf->sladdr = catu_table->table_daddr; return 0; } static const struct etr_buf_operations etr_catu_buf_ops = { .alloc = catu_alloc_etr_buf, .free = catu_free_etr_buf, .sync = catu_sync_etr_buf, .get_data = catu_get_data_etr_buf, }; static struct attribute *catu_mgmt_attrs[] = { coresight_simple_reg32(devid, CORESIGHT_DEVID), coresight_simple_reg32(control, CATU_CONTROL), coresight_simple_reg32(status, CATU_STATUS), coresight_simple_reg32(mode, CATU_MODE), coresight_simple_reg32(axictrl, CATU_AXICTRL), coresight_simple_reg32(irqen, CATU_IRQEN), coresight_simple_reg64(sladdr, CATU_SLADDRLO, CATU_SLADDRHI), coresight_simple_reg64(inaddr, CATU_INADDRLO, CATU_INADDRHI), NULL, }; static const struct attribute_group catu_mgmt_group = { .attrs = catu_mgmt_attrs, .name = "mgmt", }; static const struct attribute_group *catu_groups[] = { &catu_mgmt_group, NULL, }; static inline int catu_wait_for_ready(struct catu_drvdata *drvdata) { struct csdev_access *csa = &drvdata->csdev->access; return coresight_timeout(csa, CATU_STATUS, CATU_STATUS_READY, 1); } static int catu_enable_hw(struct catu_drvdata *drvdata, enum cs_mode cs_mode, void *data) { int rc; u32 control, mode; struct etr_buf *etr_buf = NULL; struct device *dev = &drvdata->csdev->dev; struct coresight_device *csdev = drvdata->csdev; struct coresight_device *etrdev; union coresight_dev_subtype etr_subtype = { .sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_SYSMEM }; if (catu_wait_for_ready(drvdata)) dev_warn(dev, "Timeout while waiting for READY\n"); control = catu_read_control(drvdata); if (control & BIT(CATU_CONTROL_ENABLE)) { dev_warn(dev, "CATU is already enabled\n"); return -EBUSY; } rc = coresight_claim_device_unlocked(csdev); if (rc) return rc; etrdev = coresight_find_input_type( csdev->pdata, CORESIGHT_DEV_TYPE_SINK, etr_subtype); if (etrdev) { etr_buf = tmc_etr_get_buffer(etrdev, cs_mode, data); if (IS_ERR(etr_buf)) return PTR_ERR(etr_buf); } control |= BIT(CATU_CONTROL_ENABLE); if (etr_buf && etr_buf->mode == ETR_MODE_CATU) { struct catu_etr_buf *catu_buf = etr_buf->private; mode = CATU_MODE_TRANSLATE; catu_write_axictrl(drvdata, CATU_OS_AXICTRL); catu_write_sladdr(drvdata, catu_buf->sladdr); catu_write_inaddr(drvdata, CATU_DEFAULT_INADDR); } else { mode = CATU_MODE_PASS_THROUGH; catu_write_sladdr(drvdata, 0); catu_write_inaddr(drvdata, 0); } catu_write_irqen(drvdata, 0); catu_write_mode(drvdata, mode); catu_write_control(drvdata, control); dev_dbg(dev, "Enabled in %s mode\n", (mode == CATU_MODE_PASS_THROUGH) ? "Pass through" : "Translate"); return 0; } static int catu_enable(struct coresight_device *csdev, enum cs_mode mode, void *data) { int rc; struct catu_drvdata *catu_drvdata = csdev_to_catu_drvdata(csdev); CS_UNLOCK(catu_drvdata->base); rc = catu_enable_hw(catu_drvdata, mode, data); CS_LOCK(catu_drvdata->base); return rc; } static int catu_disable_hw(struct catu_drvdata *drvdata) { int rc = 0; struct device *dev = &drvdata->csdev->dev; struct coresight_device *csdev = drvdata->csdev; catu_write_control(drvdata, 0); coresight_disclaim_device_unlocked(csdev); if (catu_wait_for_ready(drvdata)) { dev_info(dev, "Timeout while waiting for READY\n"); rc = -EAGAIN; } dev_dbg(dev, "Disabled\n"); return rc; } static int catu_disable(struct coresight_device *csdev, void *__unused) { int rc; struct catu_drvdata *catu_drvdata = csdev_to_catu_drvdata(csdev); CS_UNLOCK(catu_drvdata->base); rc = catu_disable_hw(catu_drvdata); CS_LOCK(catu_drvdata->base); return rc; } static const struct coresight_ops_helper catu_helper_ops = { .enable = catu_enable, .disable = catu_disable, }; static const struct coresight_ops catu_ops = { .helper_ops = &catu_helper_ops, }; static int catu_probe(struct amba_device *adev, const struct amba_id *id) { int ret = 0; u32 dma_mask; struct catu_drvdata *drvdata; struct coresight_desc catu_desc; struct coresight_platform_data *pdata = NULL; struct device *dev = &adev->dev; void __iomem *base; catu_desc.name = coresight_alloc_device_name(&catu_devs, dev); if (!catu_desc.name) return -ENOMEM; drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); if (!drvdata) { ret = -ENOMEM; goto out; } dev_set_drvdata(dev, drvdata); base = devm_ioremap_resource(dev, &adev->res); if (IS_ERR(base)) { ret = PTR_ERR(base); goto out; } /* Setup dma mask for the device */ dma_mask = readl_relaxed(base + CORESIGHT_DEVID) & 0x3f; switch (dma_mask) { case 32: case 40: case 44: case 48: case 52: case 56: case 64: break; default: /* Default to the 40bits as supported by TMC-ETR */ dma_mask = 40; } ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_mask)); if (ret) goto out; pdata = coresight_get_platform_data(dev); if (IS_ERR(pdata)) { ret = PTR_ERR(pdata); goto out; } dev->platform_data = pdata; drvdata->base = base; catu_desc.access = CSDEV_ACCESS_IOMEM(base); catu_desc.pdata = pdata; catu_desc.dev = dev; catu_desc.groups = catu_groups; catu_desc.type = CORESIGHT_DEV_TYPE_HELPER; catu_desc.subtype.helper_subtype = CORESIGHT_DEV_SUBTYPE_HELPER_CATU; catu_desc.ops = &catu_ops; drvdata->csdev = coresight_register(&catu_desc); if (IS_ERR(drvdata->csdev)) ret = PTR_ERR(drvdata->csdev); else pm_runtime_put(&adev->dev); out: return ret; } static void catu_remove(struct amba_device *adev) { struct catu_drvdata *drvdata = dev_get_drvdata(&adev->dev); coresight_unregister(drvdata->csdev); } static struct amba_id catu_ids[] = { CS_AMBA_ID(0x000bb9ee), {}, }; MODULE_DEVICE_TABLE(amba, catu_ids); static struct amba_driver catu_driver = { .drv = { .name = "coresight-catu", .owner = THIS_MODULE, .suppress_bind_attrs = true, }, .probe = catu_probe, .remove = catu_remove, .id_table = catu_ids, }; static int __init catu_init(void) { int ret; ret = amba_driver_register(&catu_driver); if (ret) pr_info("Error registering catu driver\n"); tmc_etr_set_catu_ops(&etr_catu_buf_ops); return ret; } static void __exit catu_exit(void) { tmc_etr_remove_catu_ops(); amba_driver_unregister(&catu_driver); } module_init(catu_init); module_exit(catu_exit); MODULE_AUTHOR("Suzuki K Poulose <suzuki.poulose@arm.com>"); MODULE_DESCRIPTION("Arm CoreSight Address Translation Unit (CATU) Driver"); MODULE_LICENSE("GPL v2");