| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2018, 2020-2021 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU license. |
| * |
| * This program is distributed in the hope that 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| */ |
| |
| #include "mali_kbase_hwcnt_virtualizer.h" |
| #include "mali_kbase_hwcnt_accumulator.h" |
| #include "mali_kbase_hwcnt_context.h" |
| #include "mali_kbase_hwcnt_types.h" |
| |
| #include <linux/mutex.h> |
| #include <linux/slab.h> |
| |
| /** |
| * struct kbase_hwcnt_virtualizer - Hardware counter virtualizer structure. |
| * @hctx: Hardware counter context being virtualized. |
| * @dump_threshold_ns: Minimum threshold period for dumps between different |
| * clients where a new accumulator dump will not be |
| * performed, and instead accumulated values will be used. |
| * If 0, rate limiting is disabled. |
| * @metadata: Hardware counter metadata. |
| * @lock: Lock acquired at all entrypoints, to protect mutable |
| * state. |
| * @client_count: Current number of virtualizer clients. |
| * @clients: List of virtualizer clients. |
| * @accum: Hardware counter accumulator. NULL if no clients. |
| * @scratch_map: Enable map used as scratch space during counter changes. |
| * @scratch_buf: Dump buffer used as scratch space during dumps. |
| * @ts_last_dump_ns: End time of most recent dump across all clients. |
| */ |
| struct kbase_hwcnt_virtualizer { |
| struct kbase_hwcnt_context *hctx; |
| u64 dump_threshold_ns; |
| const struct kbase_hwcnt_metadata *metadata; |
| struct mutex lock; |
| size_t client_count; |
| struct list_head clients; |
| struct kbase_hwcnt_accumulator *accum; |
| struct kbase_hwcnt_enable_map scratch_map; |
| struct kbase_hwcnt_dump_buffer scratch_buf; |
| u64 ts_last_dump_ns; |
| }; |
| |
| /** |
| * struct kbase_hwcnt_virtualizer_client - Virtualizer client structure. |
| * @node: List node used for virtualizer client list. |
| * @hvirt: Hardware counter virtualizer. |
| * @enable_map: Enable map with client's current enabled counters. |
| * @accum_buf: Dump buffer with client's current accumulated counters. |
| * @has_accum: True if accum_buf contains any accumulated counters. |
| * @ts_start_ns: Counter collection start time of current dump. |
| */ |
| struct kbase_hwcnt_virtualizer_client { |
| struct list_head node; |
| struct kbase_hwcnt_virtualizer *hvirt; |
| struct kbase_hwcnt_enable_map enable_map; |
| struct kbase_hwcnt_dump_buffer accum_buf; |
| bool has_accum; |
| u64 ts_start_ns; |
| }; |
| |
| const struct kbase_hwcnt_metadata *kbase_hwcnt_virtualizer_metadata( |
| struct kbase_hwcnt_virtualizer *hvirt) |
| { |
| if (!hvirt) |
| return NULL; |
| |
| return hvirt->metadata; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_free - Free a virtualizer client's memory. |
| * @hvcli: Pointer to virtualizer client. |
| * |
| * Will safely free a client in any partial state of construction. |
| */ |
| static void kbasep_hwcnt_virtualizer_client_free( |
| struct kbase_hwcnt_virtualizer_client *hvcli) |
| { |
| if (!hvcli) |
| return; |
| |
| kbase_hwcnt_dump_buffer_free(&hvcli->accum_buf); |
| kbase_hwcnt_enable_map_free(&hvcli->enable_map); |
| kfree(hvcli); |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_alloc - Allocate memory for a virtualizer |
| * client. |
| * @metadata: Non-NULL pointer to counter metadata. |
| * @out_hvcli: Non-NULL pointer to where created client will be stored on |
| * success. |
| * |
| * Return: 0 on success, else error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_client_alloc( |
| const struct kbase_hwcnt_metadata *metadata, |
| struct kbase_hwcnt_virtualizer_client **out_hvcli) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer_client *hvcli = NULL; |
| |
| WARN_ON(!metadata); |
| WARN_ON(!out_hvcli); |
| |
| hvcli = kzalloc(sizeof(*hvcli), GFP_KERNEL); |
| if (!hvcli) |
| return -ENOMEM; |
| |
| errcode = kbase_hwcnt_enable_map_alloc(metadata, &hvcli->enable_map); |
| if (errcode) |
| goto error; |
| |
| errcode = kbase_hwcnt_dump_buffer_alloc(metadata, &hvcli->accum_buf); |
| if (errcode) |
| goto error; |
| |
| *out_hvcli = hvcli; |
| return 0; |
| error: |
| kbasep_hwcnt_virtualizer_client_free(hvcli); |
| return errcode; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_accumulate - Accumulate a dump buffer into a |
| * client's accumulation buffer. |
| * @hvcli: Non-NULL pointer to virtualizer client. |
| * @dump_buf: Non-NULL pointer to dump buffer to accumulate from. |
| */ |
| static void kbasep_hwcnt_virtualizer_client_accumulate( |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| const struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| WARN_ON(!hvcli); |
| WARN_ON(!dump_buf); |
| lockdep_assert_held(&hvcli->hvirt->lock); |
| |
| if (hvcli->has_accum) { |
| /* If already some accumulation, accumulate */ |
| kbase_hwcnt_dump_buffer_accumulate( |
| &hvcli->accum_buf, dump_buf, &hvcli->enable_map); |
| } else { |
| /* If no accumulation, copy */ |
| kbase_hwcnt_dump_buffer_copy( |
| &hvcli->accum_buf, dump_buf, &hvcli->enable_map); |
| } |
| hvcli->has_accum = true; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_accumulator_term - Terminate the hardware counter |
| * accumulator after final client |
| * removal. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * |
| * Will safely terminate the accumulator in any partial state of initialisation. |
| */ |
| static void kbasep_hwcnt_virtualizer_accumulator_term( |
| struct kbase_hwcnt_virtualizer *hvirt) |
| { |
| WARN_ON(!hvirt); |
| lockdep_assert_held(&hvirt->lock); |
| WARN_ON(hvirt->client_count); |
| |
| kbase_hwcnt_dump_buffer_free(&hvirt->scratch_buf); |
| kbase_hwcnt_enable_map_free(&hvirt->scratch_map); |
| kbase_hwcnt_accumulator_release(hvirt->accum); |
| hvirt->accum = NULL; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_accumulator_init - Initialise the hardware counter |
| * accumulator before first client |
| * addition. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * |
| * Return: 0 on success, else error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_accumulator_init( |
| struct kbase_hwcnt_virtualizer *hvirt) |
| { |
| int errcode; |
| |
| WARN_ON(!hvirt); |
| lockdep_assert_held(&hvirt->lock); |
| WARN_ON(hvirt->client_count); |
| WARN_ON(hvirt->accum); |
| |
| errcode = kbase_hwcnt_accumulator_acquire( |
| hvirt->hctx, &hvirt->accum); |
| if (errcode) |
| goto error; |
| |
| errcode = kbase_hwcnt_enable_map_alloc( |
| hvirt->metadata, &hvirt->scratch_map); |
| if (errcode) |
| goto error; |
| |
| errcode = kbase_hwcnt_dump_buffer_alloc( |
| hvirt->metadata, &hvirt->scratch_buf); |
| if (errcode) |
| goto error; |
| |
| return 0; |
| error: |
| kbasep_hwcnt_virtualizer_accumulator_term(hvirt); |
| return errcode; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_add - Add a newly allocated client to the |
| * virtualizer. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * @hvcli: Non-NULL pointer to the virtualizer client to add. |
| * @enable_map: Non-NULL pointer to client's initial enable map. |
| * |
| * Return: 0 on success, else error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_client_add( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| const struct kbase_hwcnt_enable_map *enable_map) |
| { |
| int errcode = 0; |
| u64 ts_start_ns; |
| u64 ts_end_ns; |
| |
| WARN_ON(!hvirt); |
| WARN_ON(!hvcli); |
| WARN_ON(!enable_map); |
| lockdep_assert_held(&hvirt->lock); |
| |
| if (hvirt->client_count == 0) |
| /* First client added, so initialise the accumulator */ |
| errcode = kbasep_hwcnt_virtualizer_accumulator_init(hvirt); |
| if (errcode) |
| return errcode; |
| |
| hvirt->client_count += 1; |
| |
| if (hvirt->client_count == 1) { |
| /* First client, so just pass the enable map onwards as is */ |
| errcode = kbase_hwcnt_accumulator_set_counters(hvirt->accum, |
| enable_map, &ts_start_ns, &ts_end_ns, NULL); |
| } else { |
| struct kbase_hwcnt_virtualizer_client *pos; |
| |
| /* Make the scratch enable map the union of all enable maps */ |
| kbase_hwcnt_enable_map_copy( |
| &hvirt->scratch_map, enable_map); |
| list_for_each_entry(pos, &hvirt->clients, node) |
| kbase_hwcnt_enable_map_union( |
| &hvirt->scratch_map, &pos->enable_map); |
| |
| /* Set the counters with the new union enable map */ |
| errcode = kbase_hwcnt_accumulator_set_counters(hvirt->accum, |
| &hvirt->scratch_map, |
| &ts_start_ns, &ts_end_ns, |
| &hvirt->scratch_buf); |
| /* Accumulate into only existing clients' accumulation bufs */ |
| if (!errcode) |
| list_for_each_entry(pos, &hvirt->clients, node) |
| kbasep_hwcnt_virtualizer_client_accumulate( |
| pos, &hvirt->scratch_buf); |
| } |
| if (errcode) |
| goto error; |
| |
| list_add(&hvcli->node, &hvirt->clients); |
| hvcli->hvirt = hvirt; |
| kbase_hwcnt_enable_map_copy(&hvcli->enable_map, enable_map); |
| hvcli->has_accum = false; |
| hvcli->ts_start_ns = ts_end_ns; |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = ts_end_ns; |
| |
| return 0; |
| error: |
| hvirt->client_count -= 1; |
| if (hvirt->client_count == 0) |
| kbasep_hwcnt_virtualizer_accumulator_term(hvirt); |
| return errcode; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_remove - Remove a client from the |
| * virtualizer. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * @hvcli: Non-NULL pointer to the virtualizer client to remove. |
| */ |
| static void kbasep_hwcnt_virtualizer_client_remove( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| struct kbase_hwcnt_virtualizer_client *hvcli) |
| { |
| int errcode = 0; |
| u64 ts_start_ns; |
| u64 ts_end_ns; |
| |
| WARN_ON(!hvirt); |
| WARN_ON(!hvcli); |
| lockdep_assert_held(&hvirt->lock); |
| |
| list_del(&hvcli->node); |
| hvirt->client_count -= 1; |
| |
| if (hvirt->client_count == 0) { |
| /* Last client removed, so terminate the accumulator */ |
| kbasep_hwcnt_virtualizer_accumulator_term(hvirt); |
| } else { |
| struct kbase_hwcnt_virtualizer_client *pos; |
| /* Make the scratch enable map the union of all enable maps */ |
| kbase_hwcnt_enable_map_disable_all(&hvirt->scratch_map); |
| list_for_each_entry(pos, &hvirt->clients, node) |
| kbase_hwcnt_enable_map_union( |
| &hvirt->scratch_map, &pos->enable_map); |
| /* Set the counters with the new union enable map */ |
| errcode = kbase_hwcnt_accumulator_set_counters(hvirt->accum, |
| &hvirt->scratch_map, |
| &ts_start_ns, &ts_end_ns, |
| &hvirt->scratch_buf); |
| /* Accumulate into remaining clients' accumulation bufs */ |
| if (!errcode) |
| list_for_each_entry(pos, &hvirt->clients, node) |
| kbasep_hwcnt_virtualizer_client_accumulate( |
| pos, &hvirt->scratch_buf); |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = ts_end_ns; |
| } |
| WARN_ON(errcode); |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_set_counters - Perform a dump of the client's |
| * currently enabled counters, |
| * and enable a new set of |
| * counters that will be used for |
| * subsequent dumps. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * @hvcli: Non-NULL pointer to the virtualizer client. |
| * @enable_map: Non-NULL pointer to the new counter enable map for the client. |
| * Must have the same metadata as the virtualizer. |
| * @ts_start_ns: Non-NULL pointer where the start timestamp of the dump will |
| * be written out to on success. |
| * @ts_end_ns: Non-NULL pointer where the end timestamp of the dump will |
| * be written out to on success. |
| * @dump_buf: Pointer to the buffer where the dump will be written out to on |
| * success. If non-NULL, must have the same metadata as the |
| * accumulator. If NULL, the dump will be discarded. |
| * |
| * Return: 0 on success or error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_client_set_counters( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| const struct kbase_hwcnt_enable_map *enable_map, |
| u64 *ts_start_ns, |
| u64 *ts_end_ns, |
| struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer_client *pos; |
| |
| WARN_ON(!hvirt); |
| WARN_ON(!hvcli); |
| WARN_ON(!enable_map); |
| WARN_ON(!ts_start_ns); |
| WARN_ON(!ts_end_ns); |
| WARN_ON(enable_map->metadata != hvirt->metadata); |
| WARN_ON(dump_buf && (dump_buf->metadata != hvirt->metadata)); |
| lockdep_assert_held(&hvirt->lock); |
| |
| /* Make the scratch enable map the union of all enable maps */ |
| kbase_hwcnt_enable_map_copy(&hvirt->scratch_map, enable_map); |
| list_for_each_entry(pos, &hvirt->clients, node) |
| /* Ignore the enable map of the selected client */ |
| if (pos != hvcli) |
| kbase_hwcnt_enable_map_union( |
| &hvirt->scratch_map, &pos->enable_map); |
| |
| /* Set the counters with the new union enable map */ |
| errcode = kbase_hwcnt_accumulator_set_counters(hvirt->accum, |
| &hvirt->scratch_map, ts_start_ns, ts_end_ns, |
| &hvirt->scratch_buf); |
| if (errcode) |
| return errcode; |
| |
| /* Accumulate into all accumulation bufs except the selected client's */ |
| list_for_each_entry(pos, &hvirt->clients, node) |
| if (pos != hvcli) |
| kbasep_hwcnt_virtualizer_client_accumulate( |
| pos, &hvirt->scratch_buf); |
| |
| /* Finally, write into the dump buf */ |
| if (dump_buf) { |
| const struct kbase_hwcnt_dump_buffer *src = &hvirt->scratch_buf; |
| |
| if (hvcli->has_accum) { |
| kbase_hwcnt_dump_buffer_accumulate( |
| &hvcli->accum_buf, src, &hvcli->enable_map); |
| src = &hvcli->accum_buf; |
| } |
| kbase_hwcnt_dump_buffer_copy(dump_buf, src, &hvcli->enable_map); |
| } |
| hvcli->has_accum = false; |
| |
| /* Update the selected client's enable map */ |
| kbase_hwcnt_enable_map_copy(&hvcli->enable_map, enable_map); |
| |
| /* Fix up the timestamps */ |
| *ts_start_ns = hvcli->ts_start_ns; |
| hvcli->ts_start_ns = *ts_end_ns; |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = *ts_end_ns; |
| |
| return errcode; |
| } |
| |
| int kbase_hwcnt_virtualizer_client_set_counters( |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| const struct kbase_hwcnt_enable_map *enable_map, |
| u64 *ts_start_ns, |
| u64 *ts_end_ns, |
| struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer *hvirt; |
| |
| if (!hvcli || !enable_map || !ts_start_ns || !ts_end_ns) |
| return -EINVAL; |
| |
| hvirt = hvcli->hvirt; |
| |
| if ((enable_map->metadata != hvirt->metadata) || |
| (dump_buf && (dump_buf->metadata != hvirt->metadata))) |
| return -EINVAL; |
| |
| mutex_lock(&hvirt->lock); |
| |
| if ((hvirt->client_count == 1) && (!hvcli->has_accum)) { |
| /* |
| * If there's only one client with no prior accumulation, we can |
| * completely skip the virtualize and just pass through the call |
| * to the accumulator, saving a fair few copies and |
| * accumulations. |
| */ |
| errcode = kbase_hwcnt_accumulator_set_counters( |
| hvirt->accum, enable_map, |
| ts_start_ns, ts_end_ns, dump_buf); |
| |
| if (!errcode) { |
| /* Update the selected client's enable map */ |
| kbase_hwcnt_enable_map_copy( |
| &hvcli->enable_map, enable_map); |
| |
| /* Fix up the timestamps */ |
| *ts_start_ns = hvcli->ts_start_ns; |
| hvcli->ts_start_ns = *ts_end_ns; |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = *ts_end_ns; |
| } |
| } else { |
| /* Otherwise, do the full virtualize */ |
| errcode = kbasep_hwcnt_virtualizer_client_set_counters( |
| hvirt, hvcli, enable_map, |
| ts_start_ns, ts_end_ns, dump_buf); |
| } |
| |
| mutex_unlock(&hvirt->lock); |
| |
| return errcode; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_dump - Perform a dump of the client's |
| * currently enabled counters. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * @hvcli: Non-NULL pointer to the virtualizer client. |
| * @ts_start_ns: Non-NULL pointer where the start timestamp of the dump will |
| * be written out to on success. |
| * @ts_end_ns: Non-NULL pointer where the end timestamp of the dump will |
| * be written out to on success. |
| * @dump_buf: Pointer to the buffer where the dump will be written out to on |
| * success. If non-NULL, must have the same metadata as the |
| * accumulator. If NULL, the dump will be discarded. |
| * |
| * Return: 0 on success or error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_client_dump( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| u64 *ts_start_ns, |
| u64 *ts_end_ns, |
| struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer_client *pos; |
| |
| WARN_ON(!hvirt); |
| WARN_ON(!hvcli); |
| WARN_ON(!ts_start_ns); |
| WARN_ON(!ts_end_ns); |
| WARN_ON(dump_buf && (dump_buf->metadata != hvirt->metadata)); |
| lockdep_assert_held(&hvirt->lock); |
| |
| /* Perform the dump */ |
| errcode = kbase_hwcnt_accumulator_dump(hvirt->accum, |
| ts_start_ns, ts_end_ns, &hvirt->scratch_buf); |
| if (errcode) |
| return errcode; |
| |
| /* Accumulate into all accumulation bufs except the selected client's */ |
| list_for_each_entry(pos, &hvirt->clients, node) |
| if (pos != hvcli) |
| kbasep_hwcnt_virtualizer_client_accumulate( |
| pos, &hvirt->scratch_buf); |
| |
| /* Finally, write into the dump buf */ |
| if (dump_buf) { |
| const struct kbase_hwcnt_dump_buffer *src = &hvirt->scratch_buf; |
| |
| if (hvcli->has_accum) { |
| kbase_hwcnt_dump_buffer_accumulate( |
| &hvcli->accum_buf, src, &hvcli->enable_map); |
| src = &hvcli->accum_buf; |
| } |
| kbase_hwcnt_dump_buffer_copy(dump_buf, src, &hvcli->enable_map); |
| } |
| hvcli->has_accum = false; |
| |
| /* Fix up the timestamps */ |
| *ts_start_ns = hvcli->ts_start_ns; |
| hvcli->ts_start_ns = *ts_end_ns; |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = *ts_end_ns; |
| |
| return errcode; |
| } |
| |
| /** |
| * kbasep_hwcnt_virtualizer_client_dump_rate_limited - Perform a dump of the |
| * client's currently enabled counters |
| * if it hasn't been rate limited, |
| * otherwise return the client's most |
| * recent accumulation. |
| * @hvirt: Non-NULL pointer to the hardware counter virtualizer. |
| * @hvcli: Non-NULL pointer to the virtualizer client. |
| * @ts_start_ns: Non-NULL pointer where the start timestamp of the dump will |
| * be written out to on success. |
| * @ts_end_ns: Non-NULL pointer where the end timestamp of the dump will |
| * be written out to on success. |
| * @dump_buf: Pointer to the buffer where the dump will be written out to on |
| * success. If non-NULL, must have the same metadata as the |
| * accumulator. If NULL, the dump will be discarded. |
| * |
| * Return: 0 on success or error code. |
| */ |
| static int kbasep_hwcnt_virtualizer_client_dump_rate_limited( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| u64 *ts_start_ns, |
| u64 *ts_end_ns, |
| struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| bool rate_limited = true; |
| |
| WARN_ON(!hvirt); |
| WARN_ON(!hvcli); |
| WARN_ON(!ts_start_ns); |
| WARN_ON(!ts_end_ns); |
| WARN_ON(dump_buf && (dump_buf->metadata != hvirt->metadata)); |
| lockdep_assert_held(&hvirt->lock); |
| |
| if (hvirt->dump_threshold_ns == 0) { |
| /* Threshold == 0, so rate limiting disabled */ |
| rate_limited = false; |
| } else if (hvirt->ts_last_dump_ns == hvcli->ts_start_ns) { |
| /* Last dump was performed by this client, and dumps from an |
| * individual client are never rate limited |
| */ |
| rate_limited = false; |
| } else { |
| const u64 ts_ns = |
| kbase_hwcnt_accumulator_timestamp_ns(hvirt->accum); |
| const u64 time_since_last_dump_ns = |
| ts_ns - hvirt->ts_last_dump_ns; |
| |
| /* Dump period equals or exceeds the threshold */ |
| if (time_since_last_dump_ns >= hvirt->dump_threshold_ns) |
| rate_limited = false; |
| } |
| |
| if (!rate_limited) |
| return kbasep_hwcnt_virtualizer_client_dump( |
| hvirt, hvcli, ts_start_ns, ts_end_ns, dump_buf); |
| |
| /* If we've gotten this far, the client must have something accumulated |
| * otherwise it is a logic error |
| */ |
| WARN_ON(!hvcli->has_accum); |
| |
| if (dump_buf) |
| kbase_hwcnt_dump_buffer_copy( |
| dump_buf, &hvcli->accum_buf, &hvcli->enable_map); |
| hvcli->has_accum = false; |
| |
| *ts_start_ns = hvcli->ts_start_ns; |
| *ts_end_ns = hvirt->ts_last_dump_ns; |
| hvcli->ts_start_ns = hvirt->ts_last_dump_ns; |
| |
| return 0; |
| } |
| |
| int kbase_hwcnt_virtualizer_client_dump( |
| struct kbase_hwcnt_virtualizer_client *hvcli, |
| u64 *ts_start_ns, |
| u64 *ts_end_ns, |
| struct kbase_hwcnt_dump_buffer *dump_buf) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer *hvirt; |
| |
| if (!hvcli || !ts_start_ns || !ts_end_ns) |
| return -EINVAL; |
| |
| hvirt = hvcli->hvirt; |
| |
| if (dump_buf && (dump_buf->metadata != hvirt->metadata)) |
| return -EINVAL; |
| |
| mutex_lock(&hvirt->lock); |
| |
| if ((hvirt->client_count == 1) && (!hvcli->has_accum)) { |
| /* |
| * If there's only one client with no prior accumulation, we can |
| * completely skip the virtualize and just pass through the call |
| * to the accumulator, saving a fair few copies and |
| * accumulations. |
| */ |
| errcode = kbase_hwcnt_accumulator_dump( |
| hvirt->accum, ts_start_ns, ts_end_ns, dump_buf); |
| |
| if (!errcode) { |
| /* Fix up the timestamps */ |
| *ts_start_ns = hvcli->ts_start_ns; |
| hvcli->ts_start_ns = *ts_end_ns; |
| |
| /* Store the most recent dump time for rate limiting */ |
| hvirt->ts_last_dump_ns = *ts_end_ns; |
| } |
| } else { |
| /* Otherwise, do the full virtualize */ |
| errcode = kbasep_hwcnt_virtualizer_client_dump_rate_limited( |
| hvirt, hvcli, ts_start_ns, ts_end_ns, dump_buf); |
| } |
| |
| mutex_unlock(&hvirt->lock); |
| |
| return errcode; |
| } |
| |
| int kbase_hwcnt_virtualizer_client_create( |
| struct kbase_hwcnt_virtualizer *hvirt, |
| const struct kbase_hwcnt_enable_map *enable_map, |
| struct kbase_hwcnt_virtualizer_client **out_hvcli) |
| { |
| int errcode; |
| struct kbase_hwcnt_virtualizer_client *hvcli; |
| |
| if (!hvirt || !enable_map || !out_hvcli || |
| (enable_map->metadata != hvirt->metadata)) |
| return -EINVAL; |
| |
| errcode = kbasep_hwcnt_virtualizer_client_alloc( |
| hvirt->metadata, &hvcli); |
| if (errcode) |
| return errcode; |
| |
| mutex_lock(&hvirt->lock); |
| |
| errcode = kbasep_hwcnt_virtualizer_client_add(hvirt, hvcli, enable_map); |
| |
| mutex_unlock(&hvirt->lock); |
| |
| if (errcode) { |
| kbasep_hwcnt_virtualizer_client_free(hvcli); |
| return errcode; |
| } |
| |
| *out_hvcli = hvcli; |
| return 0; |
| } |
| |
| void kbase_hwcnt_virtualizer_client_destroy( |
| struct kbase_hwcnt_virtualizer_client *hvcli) |
| { |
| if (!hvcli) |
| return; |
| |
| mutex_lock(&hvcli->hvirt->lock); |
| |
| kbasep_hwcnt_virtualizer_client_remove(hvcli->hvirt, hvcli); |
| |
| mutex_unlock(&hvcli->hvirt->lock); |
| |
| kbasep_hwcnt_virtualizer_client_free(hvcli); |
| } |
| |
| int kbase_hwcnt_virtualizer_init( |
| struct kbase_hwcnt_context *hctx, |
| u64 dump_threshold_ns, |
| struct kbase_hwcnt_virtualizer **out_hvirt) |
| { |
| struct kbase_hwcnt_virtualizer *virt; |
| const struct kbase_hwcnt_metadata *metadata; |
| |
| if (!hctx || !out_hvirt) |
| return -EINVAL; |
| |
| metadata = kbase_hwcnt_context_metadata(hctx); |
| if (!metadata) |
| return -EINVAL; |
| |
| virt = kzalloc(sizeof(*virt), GFP_KERNEL); |
| if (!virt) |
| return -ENOMEM; |
| |
| virt->hctx = hctx; |
| virt->dump_threshold_ns = dump_threshold_ns; |
| virt->metadata = metadata; |
| |
| mutex_init(&virt->lock); |
| INIT_LIST_HEAD(&virt->clients); |
| |
| *out_hvirt = virt; |
| return 0; |
| } |
| |
| void kbase_hwcnt_virtualizer_term( |
| struct kbase_hwcnt_virtualizer *hvirt) |
| { |
| if (!hvirt) |
| return; |
| |
| /* Non-zero client count implies client leak */ |
| if (WARN_ON(hvirt->client_count != 0)) { |
| struct kbase_hwcnt_virtualizer_client *pos, *n; |
| |
| list_for_each_entry_safe(pos, n, &hvirt->clients, node) |
| kbase_hwcnt_virtualizer_client_destroy(pos); |
| } |
| |
| WARN_ON(hvirt->client_count != 0); |
| WARN_ON(hvirt->accum); |
| |
| kfree(hvirt); |
| } |
| |
| bool kbase_hwcnt_virtualizer_queue_work(struct kbase_hwcnt_virtualizer *hvirt, |
| struct work_struct *work) |
| { |
| if (WARN_ON(!hvirt) || WARN_ON(!work)) |
| return false; |
| |
| return kbase_hwcnt_context_queue_work(hvirt->hctx, work); |
| } |