mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	coresight: tmc-etr: Allocate and free ETR memory buffers for CPU-wide scenarios
This patch uses the PID of the process being traced to allocate and free ETR memory buffers for CPU-wide scenarios. The implementation is tailored to handle both N:1 and 1:1 source/sink HW topologies. Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org> Tested-by: Leo Yan <leo.yan@linaro.org> Tested-by: Robert Walker <robert.walker@arm.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									c5ff734462
								
							
						
					
					
						commit
						3147da92a8
					
				
					 1 changed files with 104 additions and 3 deletions
				
			
		| 
						 | 
					@ -8,6 +8,8 @@
 | 
				
			||||||
#include <linux/coresight.h>
 | 
					#include <linux/coresight.h>
 | 
				
			||||||
#include <linux/dma-mapping.h>
 | 
					#include <linux/dma-mapping.h>
 | 
				
			||||||
#include <linux/iommu.h>
 | 
					#include <linux/iommu.h>
 | 
				
			||||||
 | 
					#include <linux/idr.h>
 | 
				
			||||||
 | 
					#include <linux/mutex.h>
 | 
				
			||||||
#include <linux/refcount.h>
 | 
					#include <linux/refcount.h>
 | 
				
			||||||
#include <linux/slab.h>
 | 
					#include <linux/slab.h>
 | 
				
			||||||
#include <linux/types.h>
 | 
					#include <linux/types.h>
 | 
				
			||||||
| 
						 | 
					@ -26,6 +28,7 @@ struct etr_flat_buf {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * etr_perf_buffer - Perf buffer used for ETR
 | 
					 * etr_perf_buffer - Perf buffer used for ETR
 | 
				
			||||||
 | 
					 * @drvdata		- The ETR drvdaga this buffer has been allocated for.
 | 
				
			||||||
 * @etr_buf		- Actual buffer used by the ETR
 | 
					 * @etr_buf		- Actual buffer used by the ETR
 | 
				
			||||||
 * @pid			- The PID this etr_perf_buffer belongs to.
 | 
					 * @pid			- The PID this etr_perf_buffer belongs to.
 | 
				
			||||||
 * @snaphost		- Perf session mode
 | 
					 * @snaphost		- Perf session mode
 | 
				
			||||||
| 
						 | 
					@ -34,6 +37,7 @@ struct etr_flat_buf {
 | 
				
			||||||
 * @pages		- Array of Pages in the ring buffer.
 | 
					 * @pages		- Array of Pages in the ring buffer.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct etr_perf_buffer {
 | 
					struct etr_perf_buffer {
 | 
				
			||||||
 | 
						struct tmc_drvdata	*drvdata;
 | 
				
			||||||
	struct etr_buf		*etr_buf;
 | 
						struct etr_buf		*etr_buf;
 | 
				
			||||||
	pid_t			pid;
 | 
						pid_t			pid;
 | 
				
			||||||
	bool			snapshot;
 | 
						bool			snapshot;
 | 
				
			||||||
| 
						 | 
					@ -1211,6 +1215,72 @@ alloc_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
 | 
				
			||||||
	return etr_buf;
 | 
						return etr_buf;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct etr_buf *
 | 
				
			||||||
 | 
					get_perf_etr_buf_cpu_wide(struct tmc_drvdata *drvdata,
 | 
				
			||||||
 | 
								  struct perf_event *event, int nr_pages,
 | 
				
			||||||
 | 
								  void **pages, bool snapshot)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
						pid_t pid = task_pid_nr(event->owner);
 | 
				
			||||||
 | 
						struct etr_buf *etr_buf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					retry:
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * An etr_perf_buffer is associated with an event and holds a reference
 | 
				
			||||||
 | 
						 * to the AUX ring buffer that was created for that event.  In CPU-wide
 | 
				
			||||||
 | 
						 * N:1 mode multiple events (one per CPU), each with its own AUX ring
 | 
				
			||||||
 | 
						 * buffer, share a sink.  As such an etr_perf_buffer is created for each
 | 
				
			||||||
 | 
						 * event but a single etr_buf associated with the ETR is shared between
 | 
				
			||||||
 | 
						 * them.  The last event in a trace session will copy the content of the
 | 
				
			||||||
 | 
						 * etr_buf to its AUX ring buffer.  Ring buffer associated to other
 | 
				
			||||||
 | 
						 * events are simply not used an freed as events are destoyed.  We still
 | 
				
			||||||
 | 
						 * need to allocate a ring buffer for each event since we don't know
 | 
				
			||||||
 | 
						 * which event will be last.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * The first thing to do here is check if an etr_buf has already been
 | 
				
			||||||
 | 
						 * allocated for this session.  If so it is shared with this event,
 | 
				
			||||||
 | 
						 * otherwise it is created.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						mutex_lock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
						etr_buf = idr_find(&drvdata->idr, pid);
 | 
				
			||||||
 | 
						if (etr_buf) {
 | 
				
			||||||
 | 
							refcount_inc(&etr_buf->refcount);
 | 
				
			||||||
 | 
							mutex_unlock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
							return etr_buf;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* If we made it here no buffer has been allocated, do so now. */
 | 
				
			||||||
 | 
						mutex_unlock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						etr_buf = alloc_etr_buf(drvdata, event, nr_pages, pages, snapshot);
 | 
				
			||||||
 | 
						if (IS_ERR(etr_buf))
 | 
				
			||||||
 | 
							return etr_buf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						refcount_set(&etr_buf->refcount, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Now that we have a buffer, add it to the IDR. */
 | 
				
			||||||
 | 
						mutex_lock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
						ret = idr_alloc(&drvdata->idr, etr_buf, pid, pid + 1, GFP_KERNEL);
 | 
				
			||||||
 | 
						mutex_unlock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* Another event with this session ID has allocated this buffer. */
 | 
				
			||||||
 | 
						if (ret == -ENOSPC) {
 | 
				
			||||||
 | 
							tmc_free_etr_buf(etr_buf);
 | 
				
			||||||
 | 
							goto retry;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* The IDR can't allocate room for a new session, abandon ship. */
 | 
				
			||||||
 | 
						if (ret == -ENOMEM) {
 | 
				
			||||||
 | 
							tmc_free_etr_buf(etr_buf);
 | 
				
			||||||
 | 
							return ERR_PTR(ret);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return etr_buf;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct etr_buf *
 | 
					static struct etr_buf *
 | 
				
			||||||
get_perf_etr_buf_per_thread(struct tmc_drvdata *drvdata,
 | 
					get_perf_etr_buf_per_thread(struct tmc_drvdata *drvdata,
 | 
				
			||||||
			    struct perf_event *event, int nr_pages,
 | 
								    struct perf_event *event, int nr_pages,
 | 
				
			||||||
| 
						 | 
					@ -1239,7 +1309,8 @@ get_perf_etr_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
 | 
				
			||||||
		return get_perf_etr_buf_per_thread(drvdata, event, nr_pages,
 | 
							return get_perf_etr_buf_per_thread(drvdata, event, nr_pages,
 | 
				
			||||||
						   pages, snapshot);
 | 
											   pages, snapshot);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ERR_PTR(-ENOENT);
 | 
						return get_perf_etr_buf_cpu_wide(drvdata, event, nr_pages,
 | 
				
			||||||
 | 
										 pages, snapshot);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct etr_perf_buffer *
 | 
					static struct etr_perf_buffer *
 | 
				
			||||||
| 
						 | 
					@ -1266,7 +1337,13 @@ tmc_etr_setup_perf_buf(struct tmc_drvdata *drvdata, struct perf_event *event,
 | 
				
			||||||
	return ERR_PTR(-ENOMEM);
 | 
						return ERR_PTR(-ENOMEM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
done:
 | 
					done:
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Keep a reference to the ETR this buffer has been allocated for
 | 
				
			||||||
 | 
						 * in order to have access to the IDR in tmc_free_etr_buffer().
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						etr_perf->drvdata = drvdata;
 | 
				
			||||||
	etr_perf->etr_buf = etr_buf;
 | 
						etr_perf->etr_buf = etr_buf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return etr_perf;
 | 
						return etr_perf;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1296,9 +1373,33 @@ static void *tmc_alloc_etr_buffer(struct coresight_device *csdev,
 | 
				
			||||||
static void tmc_free_etr_buffer(void *config)
 | 
					static void tmc_free_etr_buffer(void *config)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct etr_perf_buffer *etr_perf = config;
 | 
						struct etr_perf_buffer *etr_perf = config;
 | 
				
			||||||
 | 
						struct tmc_drvdata *drvdata = etr_perf->drvdata;
 | 
				
			||||||
 | 
						struct etr_buf *buf, *etr_buf = etr_perf->etr_buf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (etr_perf->etr_buf)
 | 
						if (!etr_buf)
 | 
				
			||||||
		tmc_free_etr_buf(etr_perf->etr_buf);
 | 
							goto free_etr_perf_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mutex_lock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
						/* If we are not the last one to use the buffer, don't touch it. */
 | 
				
			||||||
 | 
						if (!refcount_dec_and_test(&etr_buf->refcount)) {
 | 
				
			||||||
 | 
							mutex_unlock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
							goto free_etr_perf_buffer;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* We are the last one, remove from the IDR and free the buffer. */
 | 
				
			||||||
 | 
						buf = idr_remove(&drvdata->idr, etr_perf->pid);
 | 
				
			||||||
 | 
						mutex_unlock(&drvdata->idr_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Something went very wrong if the buffer associated with this ID
 | 
				
			||||||
 | 
						 * is not the same in the IDR.  Leak to avoid use after free.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (buf && WARN_ON(buf != etr_buf))
 | 
				
			||||||
 | 
							goto free_etr_perf_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tmc_free_etr_buf(etr_perf->etr_buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					free_etr_perf_buffer:
 | 
				
			||||||
	kfree(etr_perf);
 | 
						kfree(etr_perf);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue