162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT) 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Siemens System Memory Buffer driver. 462306a36Sopenharmony_ci * Copyright(c) 2022, HiSilicon Limited. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/atomic.h> 862306a36Sopenharmony_ci#include <linux/acpi.h> 962306a36Sopenharmony_ci#include <linux/circ_buf.h> 1062306a36Sopenharmony_ci#include <linux/err.h> 1162306a36Sopenharmony_ci#include <linux/fs.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "coresight-etm-perf.h" 1762306a36Sopenharmony_ci#include "coresight-priv.h" 1862306a36Sopenharmony_ci#include "ultrasoc-smb.h" 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ciDEFINE_CORESIGHT_DEVLIST(sink_devs, "ultra_smb"); 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define ULTRASOC_SMB_DSM_UUID "82ae1283-7f6a-4cbe-aa06-53e8fb24db18" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistatic bool smb_buffer_not_empty(struct smb_drv_data *drvdata) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci u32 buf_status = readl(drvdata->base + SMB_LB_INT_STS_REG); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci return FIELD_GET(SMB_LB_INT_STS_NOT_EMPTY_MSK, buf_status); 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void smb_update_data_size(struct smb_drv_data *drvdata) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 3462306a36Sopenharmony_ci u32 buf_wrptr; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci buf_wrptr = readl(drvdata->base + SMB_LB_WR_ADDR_REG) - 3762306a36Sopenharmony_ci sdb->buf_hw_base; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci /* Buffer is full */ 4062306a36Sopenharmony_ci if (buf_wrptr == sdb->buf_rdptr && smb_buffer_not_empty(drvdata)) { 4162306a36Sopenharmony_ci sdb->data_size = sdb->buf_size; 4262306a36Sopenharmony_ci return; 4362306a36Sopenharmony_ci } 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* The buffer mode is circular buffer mode */ 4662306a36Sopenharmony_ci sdb->data_size = CIRC_CNT(buf_wrptr, sdb->buf_rdptr, 4762306a36Sopenharmony_ci sdb->buf_size); 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* 5162306a36Sopenharmony_ci * The read pointer adds @nbytes bytes (may round up to the beginning) 5262306a36Sopenharmony_ci * after the data is read or discarded, while needing to update the 5362306a36Sopenharmony_ci * available data size. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_cistatic void smb_update_read_ptr(struct smb_drv_data *drvdata, u32 nbytes) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci sdb->buf_rdptr += nbytes; 6062306a36Sopenharmony_ci sdb->buf_rdptr %= sdb->buf_size; 6162306a36Sopenharmony_ci writel(sdb->buf_hw_base + sdb->buf_rdptr, 6262306a36Sopenharmony_ci drvdata->base + SMB_LB_RD_ADDR_REG); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci sdb->data_size -= nbytes; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic void smb_reset_buffer(struct smb_drv_data *drvdata) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 7062306a36Sopenharmony_ci u32 write_ptr; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* 7362306a36Sopenharmony_ci * We must flush and discard any data left in hardware path 7462306a36Sopenharmony_ci * to avoid corrupting the next session. 7562306a36Sopenharmony_ci * Note: The write pointer will never exceed the read pointer. 7662306a36Sopenharmony_ci */ 7762306a36Sopenharmony_ci writel(SMB_LB_PURGE_PURGED, drvdata->base + SMB_LB_PURGE_REG); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci /* Reset SMB logical buffer status flags */ 8062306a36Sopenharmony_ci writel(SMB_LB_INT_STS_RESET, drvdata->base + SMB_LB_INT_STS_REG); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci write_ptr = readl(drvdata->base + SMB_LB_WR_ADDR_REG); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci /* Do nothing, not data left in hardware path */ 8562306a36Sopenharmony_ci if (!write_ptr || write_ptr == sdb->buf_rdptr + sdb->buf_hw_base) 8662306a36Sopenharmony_ci return; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* 8962306a36Sopenharmony_ci * The SMB_LB_WR_ADDR_REG register is read-only, 9062306a36Sopenharmony_ci * Synchronize the read pointer to write pointer. 9162306a36Sopenharmony_ci */ 9262306a36Sopenharmony_ci writel(write_ptr, drvdata->base + SMB_LB_RD_ADDR_REG); 9362306a36Sopenharmony_ci sdb->buf_rdptr = write_ptr - sdb->buf_hw_base; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int smb_open(struct inode *inode, struct file *file) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct smb_drv_data *drvdata = container_of(file->private_data, 9962306a36Sopenharmony_ci struct smb_drv_data, miscdev); 10062306a36Sopenharmony_ci int ret = 0; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci spin_lock(&drvdata->spinlock); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (drvdata->reading) { 10562306a36Sopenharmony_ci ret = -EBUSY; 10662306a36Sopenharmony_ci goto out; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci if (atomic_read(&drvdata->csdev->refcnt)) { 11062306a36Sopenharmony_ci ret = -EBUSY; 11162306a36Sopenharmony_ci goto out; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci smb_update_data_size(drvdata); 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci drvdata->reading = true; 11762306a36Sopenharmony_ciout: 11862306a36Sopenharmony_ci spin_unlock(&drvdata->spinlock); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci return ret; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic ssize_t smb_read(struct file *file, char __user *data, size_t len, 12462306a36Sopenharmony_ci loff_t *ppos) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct smb_drv_data *drvdata = container_of(file->private_data, 12762306a36Sopenharmony_ci struct smb_drv_data, miscdev); 12862306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 12962306a36Sopenharmony_ci struct device *dev = &drvdata->csdev->dev; 13062306a36Sopenharmony_ci ssize_t to_copy = 0; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci if (!len) 13362306a36Sopenharmony_ci return 0; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (!sdb->data_size) 13662306a36Sopenharmony_ci return 0; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci to_copy = min(sdb->data_size, len); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* Copy parts of trace data when read pointer wrap around SMB buffer */ 14162306a36Sopenharmony_ci if (sdb->buf_rdptr + to_copy > sdb->buf_size) 14262306a36Sopenharmony_ci to_copy = sdb->buf_size - sdb->buf_rdptr; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (copy_to_user(data, sdb->buf_base + sdb->buf_rdptr, to_copy)) { 14562306a36Sopenharmony_ci dev_dbg(dev, "Failed to copy data to user\n"); 14662306a36Sopenharmony_ci return -EFAULT; 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci *ppos += to_copy; 15062306a36Sopenharmony_ci smb_update_read_ptr(drvdata, to_copy); 15162306a36Sopenharmony_ci if (!sdb->data_size) 15262306a36Sopenharmony_ci smb_reset_buffer(drvdata); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci dev_dbg(dev, "%zu bytes copied\n", to_copy); 15562306a36Sopenharmony_ci return to_copy; 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic int smb_release(struct inode *inode, struct file *file) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct smb_drv_data *drvdata = container_of(file->private_data, 16162306a36Sopenharmony_ci struct smb_drv_data, miscdev); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci spin_lock(&drvdata->spinlock); 16462306a36Sopenharmony_ci drvdata->reading = false; 16562306a36Sopenharmony_ci spin_unlock(&drvdata->spinlock); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic const struct file_operations smb_fops = { 17162306a36Sopenharmony_ci .owner = THIS_MODULE, 17262306a36Sopenharmony_ci .open = smb_open, 17362306a36Sopenharmony_ci .read = smb_read, 17462306a36Sopenharmony_ci .release = smb_release, 17562306a36Sopenharmony_ci .llseek = no_llseek, 17662306a36Sopenharmony_ci}; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic ssize_t buf_size_show(struct device *dev, struct device_attribute *attr, 17962306a36Sopenharmony_ci char *buf) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(dev->parent); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci return sysfs_emit(buf, "0x%lx\n", drvdata->sdb.buf_size); 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(buf_size); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic struct attribute *smb_sink_attrs[] = { 18862306a36Sopenharmony_ci coresight_simple_reg32(read_pos, SMB_LB_RD_ADDR_REG), 18962306a36Sopenharmony_ci coresight_simple_reg32(write_pos, SMB_LB_WR_ADDR_REG), 19062306a36Sopenharmony_ci coresight_simple_reg32(buf_status, SMB_LB_INT_STS_REG), 19162306a36Sopenharmony_ci &dev_attr_buf_size.attr, 19262306a36Sopenharmony_ci NULL 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic const struct attribute_group smb_sink_group = { 19662306a36Sopenharmony_ci .attrs = smb_sink_attrs, 19762306a36Sopenharmony_ci .name = "mgmt", 19862306a36Sopenharmony_ci}; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic const struct attribute_group *smb_sink_groups[] = { 20162306a36Sopenharmony_ci &smb_sink_group, 20262306a36Sopenharmony_ci NULL 20362306a36Sopenharmony_ci}; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic void smb_enable_hw(struct smb_drv_data *drvdata) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci writel(SMB_GLB_EN_HW_ENABLE, drvdata->base + SMB_GLB_EN_REG); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic void smb_disable_hw(struct smb_drv_data *drvdata) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci writel(0x0, drvdata->base + SMB_GLB_EN_REG); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void smb_enable_sysfs(struct coresight_device *csdev) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (drvdata->mode != CS_MODE_DISABLED) 22062306a36Sopenharmony_ci return; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci smb_enable_hw(drvdata); 22362306a36Sopenharmony_ci drvdata->mode = CS_MODE_SYSFS; 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic int smb_enable_perf(struct coresight_device *csdev, void *data) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent); 22962306a36Sopenharmony_ci struct perf_output_handle *handle = data; 23062306a36Sopenharmony_ci struct cs_buffers *buf = etm_perf_sink_config(handle); 23162306a36Sopenharmony_ci pid_t pid; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (!buf) 23462306a36Sopenharmony_ci return -EINVAL; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci /* Get a handle on the pid of the target process */ 23762306a36Sopenharmony_ci pid = buf->pid; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci /* Device is already in used by other session */ 24062306a36Sopenharmony_ci if (drvdata->pid != -1 && drvdata->pid != pid) 24162306a36Sopenharmony_ci return -EBUSY; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci if (drvdata->pid == -1) { 24462306a36Sopenharmony_ci smb_enable_hw(drvdata); 24562306a36Sopenharmony_ci drvdata->pid = pid; 24662306a36Sopenharmony_ci drvdata->mode = CS_MODE_PERF; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return 0; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic int smb_enable(struct coresight_device *csdev, enum cs_mode mode, 25362306a36Sopenharmony_ci void *data) 25462306a36Sopenharmony_ci{ 25562306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent); 25662306a36Sopenharmony_ci int ret = 0; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci spin_lock(&drvdata->spinlock); 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Do nothing, the trace data is reading by other interface now */ 26162306a36Sopenharmony_ci if (drvdata->reading) { 26262306a36Sopenharmony_ci ret = -EBUSY; 26362306a36Sopenharmony_ci goto out; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* Do nothing, the SMB is already enabled as other mode */ 26762306a36Sopenharmony_ci if (drvdata->mode != CS_MODE_DISABLED && drvdata->mode != mode) { 26862306a36Sopenharmony_ci ret = -EBUSY; 26962306a36Sopenharmony_ci goto out; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci switch (mode) { 27362306a36Sopenharmony_ci case CS_MODE_SYSFS: 27462306a36Sopenharmony_ci smb_enable_sysfs(csdev); 27562306a36Sopenharmony_ci break; 27662306a36Sopenharmony_ci case CS_MODE_PERF: 27762306a36Sopenharmony_ci ret = smb_enable_perf(csdev, data); 27862306a36Sopenharmony_ci break; 27962306a36Sopenharmony_ci default: 28062306a36Sopenharmony_ci ret = -EINVAL; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci if (ret) 28462306a36Sopenharmony_ci goto out; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci atomic_inc(&csdev->refcnt); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci dev_dbg(&csdev->dev, "Ultrasoc SMB enabled\n"); 28962306a36Sopenharmony_ciout: 29062306a36Sopenharmony_ci spin_unlock(&drvdata->spinlock); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return ret; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic int smb_disable(struct coresight_device *csdev) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent); 29862306a36Sopenharmony_ci int ret = 0; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci spin_lock(&drvdata->spinlock); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci if (drvdata->reading) { 30362306a36Sopenharmony_ci ret = -EBUSY; 30462306a36Sopenharmony_ci goto out; 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (atomic_dec_return(&csdev->refcnt)) { 30862306a36Sopenharmony_ci ret = -EBUSY; 30962306a36Sopenharmony_ci goto out; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Complain if we (somehow) got out of sync */ 31362306a36Sopenharmony_ci WARN_ON_ONCE(drvdata->mode == CS_MODE_DISABLED); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci smb_disable_hw(drvdata); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci /* Dissociate from the target process. */ 31862306a36Sopenharmony_ci drvdata->pid = -1; 31962306a36Sopenharmony_ci drvdata->mode = CS_MODE_DISABLED; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci dev_dbg(&csdev->dev, "Ultrasoc SMB disabled\n"); 32262306a36Sopenharmony_ciout: 32362306a36Sopenharmony_ci spin_unlock(&drvdata->spinlock); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci return ret; 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic void *smb_alloc_buffer(struct coresight_device *csdev, 32962306a36Sopenharmony_ci struct perf_event *event, void **pages, 33062306a36Sopenharmony_ci int nr_pages, bool overwrite) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci struct cs_buffers *buf; 33362306a36Sopenharmony_ci int node; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci node = (event->cpu == -1) ? NUMA_NO_NODE : cpu_to_node(event->cpu); 33662306a36Sopenharmony_ci buf = kzalloc_node(sizeof(struct cs_buffers), GFP_KERNEL, node); 33762306a36Sopenharmony_ci if (!buf) 33862306a36Sopenharmony_ci return NULL; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci buf->snapshot = overwrite; 34162306a36Sopenharmony_ci buf->nr_pages = nr_pages; 34262306a36Sopenharmony_ci buf->data_pages = pages; 34362306a36Sopenharmony_ci buf->pid = task_pid_nr(event->owner); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci return buf; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void smb_free_buffer(void *config) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci struct cs_buffers *buf = config; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci kfree(buf); 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_cistatic void smb_sync_perf_buffer(struct smb_drv_data *drvdata, 35662306a36Sopenharmony_ci struct cs_buffers *buf, 35762306a36Sopenharmony_ci unsigned long head) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 36062306a36Sopenharmony_ci char **dst_pages = (char **)buf->data_pages; 36162306a36Sopenharmony_ci unsigned long to_copy; 36262306a36Sopenharmony_ci long pg_idx, pg_offset; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci pg_idx = head >> PAGE_SHIFT; 36562306a36Sopenharmony_ci pg_offset = head & (PAGE_SIZE - 1); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci while (sdb->data_size) { 36862306a36Sopenharmony_ci unsigned long pg_space = PAGE_SIZE - pg_offset; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci to_copy = min(sdb->data_size, pg_space); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci /* Copy parts of trace data when read pointer wrap around */ 37362306a36Sopenharmony_ci if (sdb->buf_rdptr + to_copy > sdb->buf_size) 37462306a36Sopenharmony_ci to_copy = sdb->buf_size - sdb->buf_rdptr; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci memcpy(dst_pages[pg_idx] + pg_offset, 37762306a36Sopenharmony_ci sdb->buf_base + sdb->buf_rdptr, to_copy); 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci pg_offset += to_copy; 38062306a36Sopenharmony_ci if (pg_offset >= PAGE_SIZE) { 38162306a36Sopenharmony_ci pg_offset = 0; 38262306a36Sopenharmony_ci pg_idx++; 38362306a36Sopenharmony_ci pg_idx %= buf->nr_pages; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci smb_update_read_ptr(drvdata, to_copy); 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci smb_reset_buffer(drvdata); 38962306a36Sopenharmony_ci} 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_cistatic unsigned long smb_update_buffer(struct coresight_device *csdev, 39262306a36Sopenharmony_ci struct perf_output_handle *handle, 39362306a36Sopenharmony_ci void *sink_config) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct smb_drv_data *drvdata = dev_get_drvdata(csdev->dev.parent); 39662306a36Sopenharmony_ci struct smb_data_buffer *sdb = &drvdata->sdb; 39762306a36Sopenharmony_ci struct cs_buffers *buf = sink_config; 39862306a36Sopenharmony_ci unsigned long data_size = 0; 39962306a36Sopenharmony_ci bool lost = false; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci if (!buf) 40262306a36Sopenharmony_ci return 0; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci spin_lock(&drvdata->spinlock); 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci /* Don't do anything if another tracer is using this sink. */ 40762306a36Sopenharmony_ci if (atomic_read(&csdev->refcnt) != 1) 40862306a36Sopenharmony_ci goto out; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci smb_disable_hw(drvdata); 41162306a36Sopenharmony_ci smb_update_data_size(drvdata); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci /* 41462306a36Sopenharmony_ci * The SMB buffer may be bigger than the space available in the 41562306a36Sopenharmony_ci * perf ring buffer (handle->size). If so advance the offset so 41662306a36Sopenharmony_ci * that we get the latest trace data. 41762306a36Sopenharmony_ci */ 41862306a36Sopenharmony_ci if (sdb->data_size > handle->size) { 41962306a36Sopenharmony_ci smb_update_read_ptr(drvdata, sdb->data_size - handle->size); 42062306a36Sopenharmony_ci lost = true; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci data_size = sdb->data_size; 42462306a36Sopenharmony_ci smb_sync_perf_buffer(drvdata, buf, handle->head); 42562306a36Sopenharmony_ci if (!buf->snapshot && lost) 42662306a36Sopenharmony_ci perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED); 42762306a36Sopenharmony_ciout: 42862306a36Sopenharmony_ci spin_unlock(&drvdata->spinlock); 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci return data_size; 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic const struct coresight_ops_sink smb_cs_ops = { 43462306a36Sopenharmony_ci .enable = smb_enable, 43562306a36Sopenharmony_ci .disable = smb_disable, 43662306a36Sopenharmony_ci .alloc_buffer = smb_alloc_buffer, 43762306a36Sopenharmony_ci .free_buffer = smb_free_buffer, 43862306a36Sopenharmony_ci .update_buffer = smb_update_buffer, 43962306a36Sopenharmony_ci}; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic const struct coresight_ops cs_ops = { 44262306a36Sopenharmony_ci .sink_ops = &smb_cs_ops, 44362306a36Sopenharmony_ci}; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_cistatic int smb_init_data_buffer(struct platform_device *pdev, 44662306a36Sopenharmony_ci struct smb_data_buffer *sdb) 44762306a36Sopenharmony_ci{ 44862306a36Sopenharmony_ci struct resource *res; 44962306a36Sopenharmony_ci void *base; 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, SMB_BUF_ADDR_RES); 45262306a36Sopenharmony_ci if (!res) { 45362306a36Sopenharmony_ci dev_err(&pdev->dev, "SMB device failed to get resource\n"); 45462306a36Sopenharmony_ci return -EINVAL; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci sdb->buf_rdptr = 0; 45862306a36Sopenharmony_ci sdb->buf_hw_base = FIELD_GET(SMB_BUF_ADDR_LO_MSK, res->start); 45962306a36Sopenharmony_ci sdb->buf_size = resource_size(res); 46062306a36Sopenharmony_ci if (sdb->buf_size == 0) 46162306a36Sopenharmony_ci return -EINVAL; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci /* 46462306a36Sopenharmony_ci * This is a chunk of memory, use classic mapping with better 46562306a36Sopenharmony_ci * performance. 46662306a36Sopenharmony_ci */ 46762306a36Sopenharmony_ci base = devm_memremap(&pdev->dev, sdb->buf_hw_base, sdb->buf_size, 46862306a36Sopenharmony_ci MEMREMAP_WB); 46962306a36Sopenharmony_ci if (IS_ERR(base)) 47062306a36Sopenharmony_ci return PTR_ERR(base); 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci sdb->buf_base = base; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci return 0; 47562306a36Sopenharmony_ci} 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_cistatic void smb_init_hw(struct smb_drv_data *drvdata) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci smb_disable_hw(drvdata); 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci writel(SMB_LB_CFG_LO_DEFAULT, drvdata->base + SMB_LB_CFG_LO_REG); 48262306a36Sopenharmony_ci writel(SMB_LB_CFG_HI_DEFAULT, drvdata->base + SMB_LB_CFG_HI_REG); 48362306a36Sopenharmony_ci writel(SMB_GLB_CFG_DEFAULT, drvdata->base + SMB_GLB_CFG_REG); 48462306a36Sopenharmony_ci writel(SMB_GLB_INT_CFG, drvdata->base + SMB_GLB_INT_REG); 48562306a36Sopenharmony_ci writel(SMB_LB_INT_CTRL_CFG, drvdata->base + SMB_LB_INT_CTRL_REG); 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_cistatic int smb_register_sink(struct platform_device *pdev, 48962306a36Sopenharmony_ci struct smb_drv_data *drvdata) 49062306a36Sopenharmony_ci{ 49162306a36Sopenharmony_ci struct coresight_platform_data *pdata = NULL; 49262306a36Sopenharmony_ci struct coresight_desc desc = { 0 }; 49362306a36Sopenharmony_ci int ret; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci pdata = coresight_get_platform_data(&pdev->dev); 49662306a36Sopenharmony_ci if (IS_ERR(pdata)) 49762306a36Sopenharmony_ci return PTR_ERR(pdata); 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci desc.type = CORESIGHT_DEV_TYPE_SINK; 50062306a36Sopenharmony_ci desc.subtype.sink_subtype = CORESIGHT_DEV_SUBTYPE_SINK_BUFFER; 50162306a36Sopenharmony_ci desc.ops = &cs_ops; 50262306a36Sopenharmony_ci desc.pdata = pdata; 50362306a36Sopenharmony_ci desc.dev = &pdev->dev; 50462306a36Sopenharmony_ci desc.groups = smb_sink_groups; 50562306a36Sopenharmony_ci desc.name = coresight_alloc_device_name(&sink_devs, &pdev->dev); 50662306a36Sopenharmony_ci if (!desc.name) { 50762306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to alloc coresight device name"); 50862306a36Sopenharmony_ci return -ENOMEM; 50962306a36Sopenharmony_ci } 51062306a36Sopenharmony_ci desc.access = CSDEV_ACCESS_IOMEM(drvdata->base); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci drvdata->csdev = coresight_register(&desc); 51362306a36Sopenharmony_ci if (IS_ERR(drvdata->csdev)) 51462306a36Sopenharmony_ci return PTR_ERR(drvdata->csdev); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci drvdata->miscdev.name = desc.name; 51762306a36Sopenharmony_ci drvdata->miscdev.minor = MISC_DYNAMIC_MINOR; 51862306a36Sopenharmony_ci drvdata->miscdev.fops = &smb_fops; 51962306a36Sopenharmony_ci ret = misc_register(&drvdata->miscdev); 52062306a36Sopenharmony_ci if (ret) { 52162306a36Sopenharmony_ci coresight_unregister(drvdata->csdev); 52262306a36Sopenharmony_ci dev_err(&pdev->dev, "Failed to register misc, ret=%d\n", ret); 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci return ret; 52662306a36Sopenharmony_ci} 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_cistatic void smb_unregister_sink(struct smb_drv_data *drvdata) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci misc_deregister(&drvdata->miscdev); 53162306a36Sopenharmony_ci coresight_unregister(drvdata->csdev); 53262306a36Sopenharmony_ci} 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_cistatic int smb_config_inport(struct device *dev, bool enable) 53562306a36Sopenharmony_ci{ 53662306a36Sopenharmony_ci u64 func = enable ? 1 : 0; 53762306a36Sopenharmony_ci union acpi_object *obj; 53862306a36Sopenharmony_ci guid_t guid; 53962306a36Sopenharmony_ci u64 rev = 0; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* 54262306a36Sopenharmony_ci * Using DSM calls to enable/disable ultrasoc hardwares on 54362306a36Sopenharmony_ci * tracing path, to prevent ultrasoc packet format being exposed. 54462306a36Sopenharmony_ci */ 54562306a36Sopenharmony_ci if (guid_parse(ULTRASOC_SMB_DSM_UUID, &guid)) { 54662306a36Sopenharmony_ci dev_err(dev, "Get GUID failed\n"); 54762306a36Sopenharmony_ci return -EINVAL; 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci obj = acpi_evaluate_dsm(ACPI_HANDLE(dev), &guid, rev, func, NULL); 55162306a36Sopenharmony_ci if (!obj) { 55262306a36Sopenharmony_ci dev_err(dev, "ACPI handle failed\n"); 55362306a36Sopenharmony_ci return -ENODEV; 55462306a36Sopenharmony_ci } 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci ACPI_FREE(obj); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci return 0; 55962306a36Sopenharmony_ci} 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cistatic int smb_probe(struct platform_device *pdev) 56262306a36Sopenharmony_ci{ 56362306a36Sopenharmony_ci struct device *dev = &pdev->dev; 56462306a36Sopenharmony_ci struct smb_drv_data *drvdata; 56562306a36Sopenharmony_ci int ret; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); 56862306a36Sopenharmony_ci if (!drvdata) 56962306a36Sopenharmony_ci return -ENOMEM; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci drvdata->base = devm_platform_ioremap_resource(pdev, SMB_REG_ADDR_RES); 57262306a36Sopenharmony_ci if (IS_ERR(drvdata->base)) { 57362306a36Sopenharmony_ci dev_err(dev, "Failed to ioremap resource\n"); 57462306a36Sopenharmony_ci return PTR_ERR(drvdata->base); 57562306a36Sopenharmony_ci } 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci smb_init_hw(drvdata); 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci ret = smb_init_data_buffer(pdev, &drvdata->sdb); 58062306a36Sopenharmony_ci if (ret) { 58162306a36Sopenharmony_ci dev_err(dev, "Failed to init buffer, ret = %d\n", ret); 58262306a36Sopenharmony_ci return ret; 58362306a36Sopenharmony_ci } 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci ret = smb_config_inport(dev, true); 58662306a36Sopenharmony_ci if (ret) 58762306a36Sopenharmony_ci return ret; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci smb_reset_buffer(drvdata); 59062306a36Sopenharmony_ci platform_set_drvdata(pdev, drvdata); 59162306a36Sopenharmony_ci spin_lock_init(&drvdata->spinlock); 59262306a36Sopenharmony_ci drvdata->pid = -1; 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci ret = smb_register_sink(pdev, drvdata); 59562306a36Sopenharmony_ci if (ret) { 59662306a36Sopenharmony_ci smb_config_inport(&pdev->dev, false); 59762306a36Sopenharmony_ci dev_err(dev, "Failed to register SMB sink\n"); 59862306a36Sopenharmony_ci return ret; 59962306a36Sopenharmony_ci } 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci return 0; 60262306a36Sopenharmony_ci} 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cistatic int smb_remove(struct platform_device *pdev) 60562306a36Sopenharmony_ci{ 60662306a36Sopenharmony_ci struct smb_drv_data *drvdata = platform_get_drvdata(pdev); 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci smb_unregister_sink(drvdata); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci smb_config_inport(&pdev->dev, false); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci return 0; 61362306a36Sopenharmony_ci} 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci#ifdef CONFIG_ACPI 61662306a36Sopenharmony_cistatic const struct acpi_device_id ultrasoc_smb_acpi_match[] = { 61762306a36Sopenharmony_ci {"HISI03A1", 0}, 61862306a36Sopenharmony_ci {} 61962306a36Sopenharmony_ci}; 62062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, ultrasoc_smb_acpi_match); 62162306a36Sopenharmony_ci#endif 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_cistatic struct platform_driver smb_driver = { 62462306a36Sopenharmony_ci .driver = { 62562306a36Sopenharmony_ci .name = "ultrasoc-smb", 62662306a36Sopenharmony_ci .acpi_match_table = ACPI_PTR(ultrasoc_smb_acpi_match), 62762306a36Sopenharmony_ci .suppress_bind_attrs = true, 62862306a36Sopenharmony_ci }, 62962306a36Sopenharmony_ci .probe = smb_probe, 63062306a36Sopenharmony_ci .remove = smb_remove, 63162306a36Sopenharmony_ci}; 63262306a36Sopenharmony_cimodule_platform_driver(smb_driver); 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ciMODULE_DESCRIPTION("UltraSoc SMB CoreSight driver"); 63562306a36Sopenharmony_ciMODULE_LICENSE("Dual MIT/GPL"); 63662306a36Sopenharmony_ciMODULE_AUTHOR("Jonathan Zhou <jonathan.zhouwen@huawei.com>"); 63762306a36Sopenharmony_ciMODULE_AUTHOR("Qi Liu <liuqi115@huawei.com>"); 638