Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
include $(src)/common/mmu/Makefile
habanalabs-y += $(HL_COMMON_MMU_FILES)
include $(src)/common/pci/Makefile
habanalabs-y += $(HL_COMMON_PCI_FILES)
HL_COMMON_FILES := common/habanalabs_drv.o common/device.o common/context.o \
common/asid.o common/habanalabs_ioctl.o \
common/command_buffer.o common/hw_queue.o common/irq.o \
common/sysfs.o common/hwmon.o common/memory.o \
common/command_submission.o common/firmware_if.o \
common/security.o common/state_dump.o \
common/memory_mgr.o common/decoder.o
# Conditionally add HLDIO support
ifdef CONFIG_HL_HLDIO
HL_COMMON_FILES += common/hldio.o
endif
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2019 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include <linux/slab.h>
int hl_asid_init(struct hl_device *hdev)
{
hdev->asid_bitmap = bitmap_zalloc(hdev->asic_prop.max_asid, GFP_KERNEL);
if (!hdev->asid_bitmap)
return -ENOMEM;
mutex_init(&hdev->asid_mutex);
/* ASID 0 is reserved for the kernel driver and device CPU */
set_bit(0, hdev->asid_bitmap);
return 0;
}
void hl_asid_fini(struct hl_device *hdev)
{
mutex_destroy(&hdev->asid_mutex);
bitmap_free(hdev->asid_bitmap);
}
unsigned long hl_asid_alloc(struct hl_device *hdev)
{
unsigned long found;
mutex_lock(&hdev->asid_mutex);
found = find_first_zero_bit(hdev->asid_bitmap,
hdev->asic_prop.max_asid);
if (found == hdev->asic_prop.max_asid)
found = 0;
else
set_bit(found, hdev->asid_bitmap);
mutex_unlock(&hdev->asid_mutex);
return found;
}
void hl_asid_free(struct hl_device *hdev, unsigned long asid)
{
if (asid == HL_KERNEL_ASID_ID || asid >= hdev->asic_prop.max_asid) {
dev_crit(hdev->dev, "Invalid ASID %lu", asid);
return;
}
clear_bit(asid, hdev->asid_bitmap);
}
@@ -0,0 +1,558 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2019 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include <uapi/drm/habanalabs_accel.h>
#include "habanalabs.h"
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CB_VA_POOL_SIZE (4UL * SZ_1G)
static int cb_map_mem(struct hl_ctx *ctx, struct hl_cb *cb)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
u32 page_size = prop->pmmu.page_size;
int rc;
if (!hdev->supports_cb_mapping) {
dev_err_ratelimited(hdev->dev,
"Mapping a CB to the device's MMU is not supported\n");
return -EINVAL;
}
if (cb->is_mmu_mapped)
return 0;
cb->roundup_size = roundup(cb->size, page_size);
cb->virtual_addr = (u64) gen_pool_alloc(ctx->cb_va_pool, cb->roundup_size);
if (!cb->virtual_addr) {
dev_err(hdev->dev, "Failed to allocate device virtual address for CB\n");
return -ENOMEM;
}
mutex_lock(&hdev->mmu_lock);
rc = hl_mmu_map_contiguous(ctx, cb->virtual_addr, cb->bus_address, cb->roundup_size);
if (rc) {
dev_err(hdev->dev, "Failed to map VA %#llx to CB\n", cb->virtual_addr);
goto err_va_pool_free;
}
rc = hl_mmu_invalidate_cache(hdev, false, MMU_OP_USERPTR | MMU_OP_SKIP_LOW_CACHE_INV);
if (rc)
goto err_mmu_unmap;
mutex_unlock(&hdev->mmu_lock);
cb->is_mmu_mapped = true;
return 0;
err_mmu_unmap:
hl_mmu_unmap_contiguous(ctx, cb->virtual_addr, cb->roundup_size);
err_va_pool_free:
mutex_unlock(&hdev->mmu_lock);
gen_pool_free(ctx->cb_va_pool, cb->virtual_addr, cb->roundup_size);
return rc;
}
static void cb_unmap_mem(struct hl_ctx *ctx, struct hl_cb *cb)
{
struct hl_device *hdev = ctx->hdev;
mutex_lock(&hdev->mmu_lock);
hl_mmu_unmap_contiguous(ctx, cb->virtual_addr, cb->roundup_size);
hl_mmu_invalidate_cache(hdev, true, MMU_OP_USERPTR);
mutex_unlock(&hdev->mmu_lock);
gen_pool_free(ctx->cb_va_pool, cb->virtual_addr, cb->roundup_size);
}
static void cb_fini(struct hl_device *hdev, struct hl_cb *cb)
{
if (cb->is_internal)
gen_pool_free(hdev->internal_cb_pool,
(uintptr_t)cb->kernel_address, cb->size);
else
hl_asic_dma_free_coherent(hdev, cb->size, cb->kernel_address, cb->bus_address);
kfree(cb);
}
static void cb_do_release(struct hl_device *hdev, struct hl_cb *cb)
{
if (cb->is_pool) {
atomic_set(&cb->is_handle_destroyed, 0);
spin_lock(&hdev->cb_pool_lock);
list_add(&cb->pool_list, &hdev->cb_pool);
spin_unlock(&hdev->cb_pool_lock);
} else {
cb_fini(hdev, cb);
}
}
static struct hl_cb *hl_cb_alloc(struct hl_device *hdev, u32 cb_size,
int ctx_id, bool internal_cb)
{
struct hl_cb *cb = NULL;
u32 cb_offset;
void *p;
/*
* We use of GFP_ATOMIC here because this function can be called from
* the latency-sensitive code path for command submission. Due to H/W
* limitations in some of the ASICs, the kernel must copy the user CB
* that is designated for an external queue and actually enqueue
* the kernel's copy. Hence, we must never sleep in this code section
* and must use GFP_ATOMIC for all memory allocations.
*/
if (ctx_id == HL_KERNEL_ASID_ID && !hdev->disabled)
cb = kzalloc_obj(*cb, GFP_ATOMIC);
if (!cb)
cb = kzalloc_obj(*cb);
if (!cb)
return NULL;
if (internal_cb) {
p = (void *) gen_pool_alloc(hdev->internal_cb_pool, cb_size);
if (!p) {
kfree(cb);
return NULL;
}
cb_offset = p - hdev->internal_cb_pool_virt_addr;
cb->is_internal = true;
cb->bus_address = hdev->internal_cb_va_base + cb_offset;
} else if (ctx_id == HL_KERNEL_ASID_ID) {
p = hl_asic_dma_alloc_coherent(hdev, cb_size, &cb->bus_address, GFP_ATOMIC);
if (!p)
p = hl_asic_dma_alloc_coherent(hdev, cb_size, &cb->bus_address, GFP_KERNEL);
} else {
p = hl_asic_dma_alloc_coherent(hdev, cb_size, &cb->bus_address,
GFP_USER | __GFP_ZERO);
}
if (!p) {
dev_err(hdev->dev,
"failed to allocate %d of dma memory for CB\n",
cb_size);
kfree(cb);
return NULL;
}
cb->kernel_address = p;
cb->size = cb_size;
return cb;
}
struct hl_cb_mmap_mem_alloc_args {
struct hl_device *hdev;
struct hl_ctx *ctx;
u32 cb_size;
bool internal_cb;
bool map_cb;
};
static void hl_cb_mmap_mem_release(struct hl_mmap_mem_buf *buf)
{
struct hl_cb *cb = buf->private;
hl_debugfs_remove_cb(cb);
if (cb->is_mmu_mapped)
cb_unmap_mem(cb->ctx, cb);
hl_ctx_put(cb->ctx);
cb_do_release(cb->hdev, cb);
}
static int hl_cb_mmap_mem_alloc(struct hl_mmap_mem_buf *buf, gfp_t gfp, void *args)
{
struct hl_cb_mmap_mem_alloc_args *cb_args = args;
struct hl_cb *cb;
int rc, ctx_id = cb_args->ctx->asid;
bool alloc_new_cb = true;
if (!cb_args->internal_cb) {
/* Minimum allocation must be PAGE SIZE */
if (cb_args->cb_size < PAGE_SIZE)
cb_args->cb_size = PAGE_SIZE;
if (ctx_id == HL_KERNEL_ASID_ID &&
cb_args->cb_size <= cb_args->hdev->asic_prop.cb_pool_cb_size) {
spin_lock(&cb_args->hdev->cb_pool_lock);
if (!list_empty(&cb_args->hdev->cb_pool)) {
cb = list_first_entry(&cb_args->hdev->cb_pool,
typeof(*cb), pool_list);
list_del(&cb->pool_list);
spin_unlock(&cb_args->hdev->cb_pool_lock);
alloc_new_cb = false;
} else {
spin_unlock(&cb_args->hdev->cb_pool_lock);
dev_dbg(cb_args->hdev->dev, "CB pool is empty\n");
}
}
}
if (alloc_new_cb) {
cb = hl_cb_alloc(cb_args->hdev, cb_args->cb_size, ctx_id, cb_args->internal_cb);
if (!cb)
return -ENOMEM;
}
cb->hdev = cb_args->hdev;
cb->ctx = cb_args->ctx;
cb->buf = buf;
cb->buf->mappable_size = cb->size;
cb->buf->private = cb;
hl_ctx_get(cb->ctx);
if (cb_args->map_cb) {
if (ctx_id == HL_KERNEL_ASID_ID) {
dev_err(cb_args->hdev->dev,
"CB mapping is not supported for kernel context\n");
rc = -EINVAL;
goto release_cb;
}
rc = cb_map_mem(cb_args->ctx, cb);
if (rc)
goto release_cb;
}
hl_debugfs_add_cb(cb);
return 0;
release_cb:
hl_ctx_put(cb->ctx);
cb_do_release(cb_args->hdev, cb);
return rc;
}
static int hl_cb_mmap(struct hl_mmap_mem_buf *buf,
struct vm_area_struct *vma, void *args)
{
struct hl_cb *cb = buf->private;
return cb->hdev->asic_funcs->mmap(cb->hdev, vma, cb->kernel_address,
cb->bus_address, cb->size);
}
static struct hl_mmap_mem_buf_behavior cb_behavior = {
.topic = "CB",
.mem_id = HL_MMAP_TYPE_CB,
.alloc = hl_cb_mmap_mem_alloc,
.release = hl_cb_mmap_mem_release,
.mmap = hl_cb_mmap,
};
int hl_cb_create(struct hl_device *hdev, struct hl_mem_mgr *mmg,
struct hl_ctx *ctx, u32 cb_size, bool internal_cb,
bool map_cb, u64 *handle)
{
struct hl_cb_mmap_mem_alloc_args args = {
.hdev = hdev,
.ctx = ctx,
.cb_size = cb_size,
.internal_cb = internal_cb,
.map_cb = map_cb,
};
struct hl_mmap_mem_buf *buf;
int ctx_id = ctx->asid;
if ((hdev->disabled) || (hdev->reset_info.in_reset && (ctx_id != HL_KERNEL_ASID_ID))) {
dev_warn_ratelimited(hdev->dev,
"Device is disabled or in reset. Can't create new CBs\n");
return -EBUSY;
}
if (cb_size > SZ_2M) {
dev_err(hdev->dev, "CB size %d must be less than %d\n",
cb_size, SZ_2M);
return -EINVAL;
}
buf = hl_mmap_mem_buf_alloc(
mmg, &cb_behavior,
ctx_id == HL_KERNEL_ASID_ID ? GFP_ATOMIC : GFP_KERNEL, &args);
if (!buf)
return -ENOMEM;
*handle = buf->handle;
return 0;
}
int hl_cb_destroy(struct hl_mem_mgr *mmg, u64 cb_handle)
{
struct hl_cb *cb;
int rc;
cb = hl_cb_get(mmg, cb_handle);
if (!cb) {
dev_dbg(mmg->dev, "CB destroy failed, no CB was found for handle %#llx\n",
cb_handle);
return -EINVAL;
}
/* Make sure that CB handle isn't destroyed more than once */
rc = atomic_cmpxchg(&cb->is_handle_destroyed, 0, 1);
hl_cb_put(cb);
if (rc) {
dev_dbg(mmg->dev, "CB destroy failed, handle %#llx was already destroyed\n",
cb_handle);
return -EINVAL;
}
rc = hl_mmap_mem_buf_put_handle(mmg, cb_handle);
if (rc < 0)
return rc; /* Invalid handle */
if (rc == 0)
dev_dbg(mmg->dev, "CB 0x%llx is destroyed while still in use\n", cb_handle);
return 0;
}
static int hl_cb_info(struct hl_mem_mgr *mmg,
u64 handle, u32 flags, u32 *usage_cnt, u64 *device_va)
{
struct hl_cb *cb;
int rc = 0;
cb = hl_cb_get(mmg, handle);
if (!cb) {
dev_err(mmg->dev,
"CB info failed, no match to handle 0x%llx\n", handle);
return -EINVAL;
}
if (flags & HL_CB_FLAGS_GET_DEVICE_VA) {
if (cb->is_mmu_mapped) {
*device_va = cb->virtual_addr;
} else {
dev_err(mmg->dev, "CB is not mapped to the device's MMU\n");
rc = -EINVAL;
goto out;
}
} else {
*usage_cnt = atomic_read(&cb->cs_cnt);
}
out:
hl_cb_put(cb);
return rc;
}
int hl_cb_ioctl(struct drm_device *ddev, void *data, struct drm_file *file_priv)
{
struct hl_fpriv *hpriv = file_priv->driver_priv;
struct hl_device *hdev = hpriv->hdev;
union hl_cb_args *args = data;
u64 handle = 0, device_va = 0;
enum hl_device_status status;
u32 usage_cnt = 0;
int rc;
if (!hl_device_operational(hdev, &status)) {
dev_dbg_ratelimited(hdev->dev,
"Device is %s. Can't execute CB IOCTL\n",
hdev->status[status]);
return -EBUSY;
}
switch (args->in.op) {
case HL_CB_OP_CREATE:
if (args->in.cb_size > HL_MAX_CB_SIZE) {
dev_err(hdev->dev,
"User requested CB size %d must be less than %d\n",
args->in.cb_size, HL_MAX_CB_SIZE);
rc = -EINVAL;
} else {
rc = hl_cb_create(hdev, &hpriv->mem_mgr, hpriv->ctx,
args->in.cb_size, false,
!!(args->in.flags & HL_CB_FLAGS_MAP),
&handle);
}
memset(args, 0, sizeof(*args));
args->out.cb_handle = handle;
break;
case HL_CB_OP_DESTROY:
rc = hl_cb_destroy(&hpriv->mem_mgr,
args->in.cb_handle);
break;
case HL_CB_OP_INFO:
rc = hl_cb_info(&hpriv->mem_mgr, args->in.cb_handle,
args->in.flags,
&usage_cnt,
&device_va);
if (rc)
break;
memset(&args->out, 0, sizeof(args->out));
if (args->in.flags & HL_CB_FLAGS_GET_DEVICE_VA)
args->out.device_va = device_va;
else
args->out.usage_cnt = usage_cnt;
break;
default:
rc = -EINVAL;
break;
}
return rc;
}
struct hl_cb *hl_cb_get(struct hl_mem_mgr *mmg, u64 handle)
{
struct hl_mmap_mem_buf *buf;
buf = hl_mmap_mem_buf_get(mmg, handle);
if (!buf)
return NULL;
return buf->private;
}
void hl_cb_put(struct hl_cb *cb)
{
hl_mmap_mem_buf_put(cb->buf);
}
struct hl_cb *hl_cb_kernel_create(struct hl_device *hdev, u32 cb_size,
bool internal_cb)
{
u64 cb_handle;
struct hl_cb *cb;
int rc;
rc = hl_cb_create(hdev, &hdev->kernel_mem_mgr, hdev->kernel_ctx, cb_size,
internal_cb, false, &cb_handle);
if (rc) {
dev_err(hdev->dev,
"Failed to allocate CB for the kernel driver %d\n", rc);
return NULL;
}
cb = hl_cb_get(&hdev->kernel_mem_mgr, cb_handle);
/* hl_cb_get should never fail here */
if (!cb) {
dev_crit(hdev->dev, "Kernel CB handle invalid 0x%x\n",
(u32) cb_handle);
goto destroy_cb;
}
return cb;
destroy_cb:
hl_cb_destroy(&hdev->kernel_mem_mgr, cb_handle);
return NULL;
}
int hl_cb_pool_init(struct hl_device *hdev)
{
struct hl_cb *cb;
int i;
INIT_LIST_HEAD(&hdev->cb_pool);
spin_lock_init(&hdev->cb_pool_lock);
for (i = 0 ; i < hdev->asic_prop.cb_pool_cb_cnt ; i++) {
cb = hl_cb_alloc(hdev, hdev->asic_prop.cb_pool_cb_size,
HL_KERNEL_ASID_ID, false);
if (cb) {
cb->is_pool = true;
list_add(&cb->pool_list, &hdev->cb_pool);
} else {
hl_cb_pool_fini(hdev);
return -ENOMEM;
}
}
return 0;
}
int hl_cb_pool_fini(struct hl_device *hdev)
{
struct hl_cb *cb, *tmp;
list_for_each_entry_safe(cb, tmp, &hdev->cb_pool, pool_list) {
list_del(&cb->pool_list);
cb_fini(hdev, cb);
}
return 0;
}
int hl_cb_va_pool_init(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
int rc;
if (!hdev->supports_cb_mapping)
return 0;
ctx->cb_va_pool = gen_pool_create(__ffs(prop->pmmu.page_size), -1);
if (!ctx->cb_va_pool) {
dev_err(hdev->dev,
"Failed to create VA gen pool for CB mapping\n");
return -ENOMEM;
}
ctx->cb_va_pool_base = hl_reserve_va_block(hdev, ctx, HL_VA_RANGE_TYPE_HOST,
CB_VA_POOL_SIZE, HL_MMU_VA_ALIGNMENT_NOT_NEEDED);
if (!ctx->cb_va_pool_base) {
rc = -ENOMEM;
goto err_pool_destroy;
}
rc = gen_pool_add(ctx->cb_va_pool, ctx->cb_va_pool_base, CB_VA_POOL_SIZE, -1);
if (rc) {
dev_err(hdev->dev,
"Failed to add memory to VA gen pool for CB mapping\n");
goto err_unreserve_va_block;
}
return 0;
err_unreserve_va_block:
hl_unreserve_va_block(hdev, ctx, ctx->cb_va_pool_base, CB_VA_POOL_SIZE);
err_pool_destroy:
gen_pool_destroy(ctx->cb_va_pool);
return rc;
}
void hl_cb_va_pool_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
if (!hdev->supports_cb_mapping)
return;
gen_pool_destroy(ctx->cb_va_pool);
hl_unreserve_va_block(hdev, ctx, ctx->cb_va_pool_base, CB_VA_POOL_SIZE);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,448 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2021 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include <linux/slab.h>
static void encaps_handle_do_release(struct hl_cs_encaps_sig_handle *handle, bool put_hw_sob,
bool put_ctx)
{
struct hl_encaps_signals_mgr *mgr = &handle->ctx->sig_mgr;
if (put_hw_sob)
hw_sob_put(handle->hw_sob);
spin_lock(&mgr->lock);
idr_remove(&mgr->handles, handle->id);
spin_unlock(&mgr->lock);
if (put_ctx)
hl_ctx_put(handle->ctx);
kfree(handle);
}
void hl_encaps_release_handle_and_put_ctx(struct kref *ref)
{
struct hl_cs_encaps_sig_handle *handle =
container_of(ref, struct hl_cs_encaps_sig_handle, refcount);
encaps_handle_do_release(handle, false, true);
}
static void hl_encaps_release_handle_and_put_sob(struct kref *ref)
{
struct hl_cs_encaps_sig_handle *handle =
container_of(ref, struct hl_cs_encaps_sig_handle, refcount);
encaps_handle_do_release(handle, true, false);
}
void hl_encaps_release_handle_and_put_sob_ctx(struct kref *ref)
{
struct hl_cs_encaps_sig_handle *handle =
container_of(ref, struct hl_cs_encaps_sig_handle, refcount);
encaps_handle_do_release(handle, true, true);
}
static void hl_encaps_sig_mgr_init(struct hl_encaps_signals_mgr *mgr)
{
spin_lock_init(&mgr->lock);
idr_init(&mgr->handles);
}
static void hl_encaps_sig_mgr_fini(struct hl_device *hdev, struct hl_encaps_signals_mgr *mgr)
{
struct hl_cs_encaps_sig_handle *handle;
struct idr *idp;
u32 id;
idp = &mgr->handles;
/* The IDR is expected to be empty at this stage, because any left signal should have been
* released as part of CS roll-back.
*/
if (!idr_is_empty(idp)) {
dev_warn(hdev->dev,
"device released while some encaps signals handles are still allocated\n");
idr_for_each_entry(idp, handle, id)
kref_put(&handle->refcount, hl_encaps_release_handle_and_put_sob);
}
idr_destroy(&mgr->handles);
}
static void hl_ctx_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
int i;
/* Release all allocated HW block mapped list entries and destroy
* the mutex.
*/
hl_hw_block_mem_fini(ctx);
/*
* If we arrived here, there are no jobs waiting for this context
* on its queues so we can safely remove it.
* This is because for each CS, we increment the ref count and for
* every CS that was finished we decrement it and we won't arrive
* to this function unless the ref count is 0
*/
for (i = 0 ; i < hdev->asic_prop.max_pending_cs ; i++)
hl_fence_put(ctx->cs_pending[i]);
kfree(ctx->cs_pending);
if (ctx->asid != HL_KERNEL_ASID_ID) {
dev_dbg(hdev->dev, "closing user context, asid=%u\n", ctx->asid);
/* The engines are stopped as there is no executing CS, but the
* Coresight might be still working by accessing addresses
* related to the stopped engines. Hence stop it explicitly.
*/
if (hdev->in_debug)
hl_device_set_debug_mode(hdev, ctx, false);
hdev->asic_funcs->ctx_fini(ctx);
hl_dec_ctx_fini(ctx);
hl_cb_va_pool_fini(ctx);
hl_vm_ctx_fini(ctx);
hl_asid_free(hdev, ctx->asid);
hl_encaps_sig_mgr_fini(hdev, &ctx->sig_mgr);
mutex_destroy(&ctx->ts_reg_lock);
} else {
dev_dbg(hdev->dev, "closing kernel context\n");
hdev->asic_funcs->ctx_fini(ctx);
hl_vm_ctx_fini(ctx);
hl_mmu_ctx_fini(ctx);
}
}
void hl_ctx_do_release(struct kref *ref)
{
struct hl_ctx *ctx;
ctx = container_of(ref, struct hl_ctx, refcount);
hl_ctx_fini(ctx);
if (ctx->hpriv) {
struct hl_fpriv *hpriv = ctx->hpriv;
mutex_lock(&hpriv->ctx_lock);
hpriv->ctx = NULL;
mutex_unlock(&hpriv->ctx_lock);
hl_hpriv_put(hpriv);
}
kfree(ctx);
}
int hl_ctx_create(struct hl_device *hdev, struct hl_fpriv *hpriv)
{
struct hl_ctx_mgr *ctx_mgr = &hpriv->ctx_mgr;
struct hl_ctx *ctx;
int rc;
ctx = kzalloc_obj(*ctx);
if (!ctx) {
rc = -ENOMEM;
goto out_err;
}
mutex_lock(&ctx_mgr->lock);
rc = idr_alloc(&ctx_mgr->handles, ctx, 1, 0, GFP_KERNEL);
mutex_unlock(&ctx_mgr->lock);
if (rc < 0) {
dev_err(hdev->dev, "Failed to allocate IDR for a new CTX\n");
goto free_ctx;
}
ctx->handle = rc;
rc = hl_ctx_init(hdev, ctx, false);
if (rc)
goto remove_from_idr;
hl_hpriv_get(hpriv);
ctx->hpriv = hpriv;
/* TODO: remove for multiple contexts per process */
hpriv->ctx = ctx;
/* TODO: remove the following line for multiple process support */
hdev->is_compute_ctx_active = true;
return 0;
remove_from_idr:
mutex_lock(&ctx_mgr->lock);
idr_remove(&ctx_mgr->handles, ctx->handle);
mutex_unlock(&ctx_mgr->lock);
free_ctx:
kfree(ctx);
out_err:
return rc;
}
int hl_ctx_init(struct hl_device *hdev, struct hl_ctx *ctx, bool is_kernel_ctx)
{
int rc = 0, i;
ctx->hdev = hdev;
kref_init(&ctx->refcount);
ctx->cs_sequence = 1;
spin_lock_init(&ctx->cs_lock);
atomic_set(&ctx->thread_ctx_switch_token, 1);
ctx->thread_ctx_switch_wait_token = 0;
ctx->cs_pending = kzalloc_objs(struct hl_fence *,
hdev->asic_prop.max_pending_cs);
if (!ctx->cs_pending)
return -ENOMEM;
INIT_LIST_HEAD(&ctx->outcome_store.used_list);
INIT_LIST_HEAD(&ctx->outcome_store.free_list);
hash_init(ctx->outcome_store.outcome_map);
for (i = 0; i < ARRAY_SIZE(ctx->outcome_store.nodes_pool); ++i)
list_add(&ctx->outcome_store.nodes_pool[i].list_link,
&ctx->outcome_store.free_list);
hl_hw_block_mem_init(ctx);
if (is_kernel_ctx) {
ctx->asid = HL_KERNEL_ASID_ID; /* Kernel driver gets ASID 0 */
rc = hl_vm_ctx_init(ctx);
if (rc) {
dev_err(hdev->dev, "Failed to init mem ctx module\n");
rc = -ENOMEM;
goto err_hw_block_mem_fini;
}
rc = hdev->asic_funcs->ctx_init(ctx);
if (rc) {
dev_err(hdev->dev, "ctx_init failed\n");
goto err_vm_ctx_fini;
}
} else {
ctx->asid = hl_asid_alloc(hdev);
if (!ctx->asid) {
dev_err(hdev->dev, "No free ASID, failed to create context\n");
rc = -ENOMEM;
goto err_hw_block_mem_fini;
}
rc = hl_vm_ctx_init(ctx);
if (rc) {
dev_err(hdev->dev, "Failed to init mem ctx module\n");
rc = -ENOMEM;
goto err_asid_free;
}
rc = hl_cb_va_pool_init(ctx);
if (rc) {
dev_err(hdev->dev,
"Failed to init VA pool for mapped CB\n");
goto err_vm_ctx_fini;
}
rc = hdev->asic_funcs->ctx_init(ctx);
if (rc) {
dev_err(hdev->dev, "ctx_init failed\n");
goto err_cb_va_pool_fini;
}
hl_encaps_sig_mgr_init(&ctx->sig_mgr);
mutex_init(&ctx->ts_reg_lock);
dev_dbg(hdev->dev, "create user context, comm=\"%s\", asid=%u\n",
current->comm, ctx->asid);
}
return 0;
err_cb_va_pool_fini:
hl_cb_va_pool_fini(ctx);
err_vm_ctx_fini:
hl_vm_ctx_fini(ctx);
err_asid_free:
if (ctx->asid != HL_KERNEL_ASID_ID)
hl_asid_free(hdev, ctx->asid);
err_hw_block_mem_fini:
hl_hw_block_mem_fini(ctx);
kfree(ctx->cs_pending);
return rc;
}
static int hl_ctx_get_unless_zero(struct hl_ctx *ctx)
{
return kref_get_unless_zero(&ctx->refcount);
}
void hl_ctx_get(struct hl_ctx *ctx)
{
kref_get(&ctx->refcount);
}
int hl_ctx_put(struct hl_ctx *ctx)
{
return kref_put(&ctx->refcount, hl_ctx_do_release);
}
struct hl_ctx *hl_get_compute_ctx(struct hl_device *hdev)
{
struct hl_ctx *ctx = NULL;
struct hl_fpriv *hpriv;
mutex_lock(&hdev->fpriv_list_lock);
list_for_each_entry(hpriv, &hdev->fpriv_list, dev_node) {
mutex_lock(&hpriv->ctx_lock);
ctx = hpriv->ctx;
if (ctx && !hl_ctx_get_unless_zero(ctx))
ctx = NULL;
mutex_unlock(&hpriv->ctx_lock);
/* There can only be a single user which has opened the compute device, so exit
* immediately once we find its context or if we see that it has been released
*/
break;
}
mutex_unlock(&hdev->fpriv_list_lock);
return ctx;
}
/*
* hl_ctx_get_fence_locked - get CS fence under CS lock
*
* @ctx: pointer to the context structure.
* @seq: CS sequences number
*
* @return valid fence pointer on success, NULL if fence is gone, otherwise
* error pointer.
*
* NOTE: this function shall be called with cs_lock locked
*/
static struct hl_fence *hl_ctx_get_fence_locked(struct hl_ctx *ctx, u64 seq)
{
struct asic_fixed_properties *asic_prop = &ctx->hdev->asic_prop;
struct hl_fence *fence;
if (seq >= ctx->cs_sequence)
return ERR_PTR(-EINVAL);
if (seq + asic_prop->max_pending_cs < ctx->cs_sequence)
return NULL;
fence = ctx->cs_pending[seq & (asic_prop->max_pending_cs - 1)];
hl_fence_get(fence);
return fence;
}
struct hl_fence *hl_ctx_get_fence(struct hl_ctx *ctx, u64 seq)
{
struct hl_fence *fence;
spin_lock(&ctx->cs_lock);
fence = hl_ctx_get_fence_locked(ctx, seq);
spin_unlock(&ctx->cs_lock);
return fence;
}
/*
* hl_ctx_get_fences - get multiple CS fences under the same CS lock
*
* @ctx: pointer to the context structure.
* @seq_arr: array of CS sequences to wait for
* @fence: fence array to store the CS fences
* @arr_len: length of seq_arr and fence_arr
*
* @return 0 on success, otherwise non 0 error code
*/
int hl_ctx_get_fences(struct hl_ctx *ctx, u64 *seq_arr,
struct hl_fence **fence, u32 arr_len)
{
struct hl_fence **fence_arr_base = fence;
int i, rc = 0;
spin_lock(&ctx->cs_lock);
for (i = 0; i < arr_len; i++, fence++) {
u64 seq = seq_arr[i];
*fence = hl_ctx_get_fence_locked(ctx, seq);
if (IS_ERR(*fence)) {
dev_err(ctx->hdev->dev,
"Failed to get fence for CS with seq 0x%llx\n",
seq);
rc = PTR_ERR(*fence);
break;
}
}
spin_unlock(&ctx->cs_lock);
if (rc)
hl_fences_put(fence_arr_base, i);
return rc;
}
/*
* hl_ctx_mgr_init - initialize the context manager
*
* @ctx_mgr: pointer to context manager structure
*
* This manager is an object inside the hpriv object of the user process.
* The function is called when a user process opens the FD.
*/
void hl_ctx_mgr_init(struct hl_ctx_mgr *ctx_mgr)
{
mutex_init(&ctx_mgr->lock);
idr_init(&ctx_mgr->handles);
}
/*
* hl_ctx_mgr_fini - finalize the context manager
*
* @hdev: pointer to device structure
* @ctx_mgr: pointer to context manager structure
*
* This function goes over all the contexts in the manager and frees them.
* It is called when a process closes the FD.
*/
void hl_ctx_mgr_fini(struct hl_device *hdev, struct hl_ctx_mgr *ctx_mgr)
{
struct hl_ctx *ctx;
struct idr *idp;
u32 id;
idp = &ctx_mgr->handles;
idr_for_each_entry(idp, ctx, id)
kref_put(&ctx->refcount, hl_ctx_do_release);
idr_destroy(&ctx_mgr->handles);
mutex_destroy(&ctx_mgr->lock);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2022 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#define VCMD_CONTROL_OFFSET 0x40 /* SWREG16 */
#define VCMD_IRQ_STATUS_OFFSET 0x44 /* SWREG17 */
#define VCMD_IRQ_STATUS_ENDCMD_MASK 0x1
#define VCMD_IRQ_STATUS_BUSERR_MASK 0x2
#define VCMD_IRQ_STATUS_TIMEOUT_MASK 0x4
#define VCMD_IRQ_STATUS_CMDERR_MASK 0x8
#define VCMD_IRQ_STATUS_ABORT_MASK 0x10
#define VCMD_IRQ_STATUS_RESET_MASK 0x20
static void dec_print_abnrm_intr_source(struct hl_device *hdev, u32 irq_status)
{
const char *format = "abnormal interrupt source:%s%s%s%s%s%s\n";
char *intr_source[6] = {"Unknown", "", "", "", "", ""};
int i = 0;
if (!irq_status)
return;
if (irq_status & VCMD_IRQ_STATUS_ENDCMD_MASK)
intr_source[i++] = " ENDCMD";
if (irq_status & VCMD_IRQ_STATUS_BUSERR_MASK)
intr_source[i++] = " BUSERR";
if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK)
intr_source[i++] = " TIMEOUT";
if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
intr_source[i++] = " CMDERR";
if (irq_status & VCMD_IRQ_STATUS_ABORT_MASK)
intr_source[i++] = " ABORT";
if (irq_status & VCMD_IRQ_STATUS_RESET_MASK)
intr_source[i++] = " RESET";
dev_err(hdev->dev, format, intr_source[0], intr_source[1],
intr_source[2], intr_source[3], intr_source[4], intr_source[5]);
}
static void dec_abnrm_intr_work(struct work_struct *work)
{
struct hl_dec *dec = container_of(work, struct hl_dec, abnrm_intr_work);
struct hl_device *hdev = dec->hdev;
u32 irq_status, event_mask = 0;
bool reset_required = false;
irq_status = RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);
dev_err(hdev->dev, "Decoder abnormal interrupt %#x, core %d\n", irq_status, dec->core_id);
dec_print_abnrm_intr_source(hdev, irq_status);
/* Clear the interrupt */
WREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET, irq_status);
/* Flush the interrupt clear */
RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);
if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK) {
reset_required = true;
event_mask |= HL_NOTIFIER_EVENT_GENERAL_HW_ERR;
}
if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
event_mask |= HL_NOTIFIER_EVENT_UNDEFINED_OPCODE;
if (irq_status & (VCMD_IRQ_STATUS_ENDCMD_MASK |
VCMD_IRQ_STATUS_BUSERR_MASK |
VCMD_IRQ_STATUS_ABORT_MASK))
event_mask |= HL_NOTIFIER_EVENT_USER_ENGINE_ERR;
if (reset_required) {
event_mask |= HL_NOTIFIER_EVENT_DEVICE_RESET;
hl_device_cond_reset(hdev, 0, event_mask);
} else if (event_mask) {
hl_notifier_event_send_all(hdev, event_mask);
}
}
void hl_dec_fini(struct hl_device *hdev)
{
kfree(hdev->dec);
}
int hl_dec_init(struct hl_device *hdev)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_dec *dec;
int rc, j;
/* if max core is 0, nothing to do*/
if (!prop->max_dec)
return 0;
hdev->dec = kzalloc_objs(struct hl_dec, prop->max_dec);
if (!hdev->dec)
return -ENOMEM;
for (j = 0 ; j < prop->max_dec ; j++) {
dec = hdev->dec + j;
dec->hdev = hdev;
INIT_WORK(&dec->abnrm_intr_work, dec_abnrm_intr_work);
dec->core_id = j;
dec->base_addr = hdev->asic_funcs->get_dec_base_addr(hdev, j);
if (!dec->base_addr) {
dev_err(hdev->dev, "Invalid base address of decoder %d\n", j);
rc = -EINVAL;
goto err_dec_fini;
}
}
return 0;
err_dec_fini:
hl_dec_fini(hdev);
return rc;
}
void hl_dec_ctx_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_dec *dec;
int j;
for (j = 0 ; j < prop->max_dec ; j++) {
if (!!(prop->decoder_enabled_mask & BIT(j))) {
dec = hdev->dec + j;
/* Stop the decoder */
WREG32(dec->base_addr + VCMD_CONTROL_OFFSET, 0);
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,784 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2021 HabanaLabs, Ltd.
* All Rights Reserved.
*
*/
#define pr_fmt(fmt) "habanalabs: " fmt
#include "habanalabs.h"
#include "../include/hw_ip/pci/pci_general.h"
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/version.h>
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>
#include <drm/drm_ioctl.h>
#define CREATE_TRACE_POINTS
#include <trace/events/habanalabs.h>
#define HL_DRIVER_AUTHOR "HabanaLabs Kernel Driver Team"
#define HL_DRIVER_DESC "Driver for HabanaLabs's AI Accelerators"
MODULE_AUTHOR(HL_DRIVER_AUTHOR);
MODULE_DESCRIPTION(HL_DRIVER_DESC);
MODULE_LICENSE("GPL v2");
static int hl_major;
static DEFINE_IDR(hl_devs_idr);
static DEFINE_MUTEX(hl_devs_idr_lock);
#define HL_DEFAULT_TIMEOUT_LOCKED 30 /* 30 seconds */
#define GAUDI_DEFAULT_TIMEOUT_LOCKED 600 /* 10 minutes */
static int timeout_locked = HL_DEFAULT_TIMEOUT_LOCKED;
static int reset_on_lockup = 1;
static int memory_scrub;
static ulong boot_error_status_mask = ULONG_MAX;
module_param(timeout_locked, int, 0444);
MODULE_PARM_DESC(timeout_locked,
"Device lockup timeout in seconds (0 = disabled, default 30s)");
module_param(reset_on_lockup, int, 0444);
MODULE_PARM_DESC(reset_on_lockup,
"Do device reset on lockup (0 = no, 1 = yes, default yes)");
module_param(memory_scrub, int, 0444);
MODULE_PARM_DESC(memory_scrub,
"Scrub device memory in various states (0 = no, 1 = yes, default no)");
module_param(boot_error_status_mask, ulong, 0444);
MODULE_PARM_DESC(boot_error_status_mask,
"Mask of the error status during device CPU boot (If bitX is cleared then error X is masked. Default all 1's)");
#define PCI_IDS_GOYA 0x0001
#define PCI_IDS_GAUDI 0x1000
#define PCI_IDS_GAUDI_SEC 0x1010
#define PCI_IDS_GAUDI2 0x1020
static const struct pci_device_id ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GOYA), },
{ PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GAUDI), },
{ PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GAUDI_SEC), },
{ PCI_DEVICE(PCI_VENDOR_ID_HABANALABS, PCI_IDS_GAUDI2), },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);
static const struct drm_ioctl_desc hl_drm_ioctls[] = {
DRM_IOCTL_DEF_DRV(HL_INFO, hl_info_ioctl, 0),
DRM_IOCTL_DEF_DRV(HL_CB, hl_cb_ioctl, 0),
DRM_IOCTL_DEF_DRV(HL_CS, hl_cs_ioctl, 0),
DRM_IOCTL_DEF_DRV(HL_WAIT_CS, hl_wait_ioctl, 0),
DRM_IOCTL_DEF_DRV(HL_MEMORY, hl_mem_ioctl, 0),
DRM_IOCTL_DEF_DRV(HL_DEBUG, hl_debug_ioctl, 0),
};
static const struct file_operations hl_fops = {
.owner = THIS_MODULE,
.open = accel_open,
.release = drm_release,
.unlocked_ioctl = drm_ioctl,
.compat_ioctl = drm_compat_ioctl,
.llseek = noop_llseek,
.mmap = hl_mmap
};
static const struct drm_driver hl_driver = {
.driver_features = DRIVER_COMPUTE_ACCEL,
.name = HL_NAME,
.desc = HL_DRIVER_DESC,
.major = LINUX_VERSION_MAJOR,
.minor = LINUX_VERSION_PATCHLEVEL,
.patchlevel = LINUX_VERSION_SUBLEVEL,
.fops = &hl_fops,
.open = hl_device_open,
.postclose = hl_device_release,
.ioctls = hl_drm_ioctls,
.num_ioctls = ARRAY_SIZE(hl_drm_ioctls)
};
/*
* get_asic_type - translate device id to asic type
*
* @hdev: pointer to habanalabs device structure.
*
* Translate device id and revision id to asic type.
* In case of unidentified device, return -1
*/
static enum hl_asic_type get_asic_type(struct hl_device *hdev)
{
struct pci_dev *pdev = hdev->pdev;
enum hl_asic_type asic_type = ASIC_INVALID;
switch (pdev->device) {
case PCI_IDS_GOYA:
asic_type = ASIC_GOYA;
break;
case PCI_IDS_GAUDI:
asic_type = ASIC_GAUDI;
break;
case PCI_IDS_GAUDI_SEC:
asic_type = ASIC_GAUDI_SEC;
break;
case PCI_IDS_GAUDI2:
switch (pdev->revision) {
case REV_ID_A:
asic_type = ASIC_GAUDI2;
break;
case REV_ID_B:
asic_type = ASIC_GAUDI2B;
break;
case REV_ID_C:
asic_type = ASIC_GAUDI2C;
break;
case REV_ID_D:
asic_type = ASIC_GAUDI2D;
break;
default:
break;
}
break;
default:
break;
}
return asic_type;
}
static bool is_asic_secured(enum hl_asic_type asic_type)
{
switch (asic_type) {
case ASIC_GAUDI_SEC:
return true;
default:
return false;
}
}
/*
* hl_device_open() - open function for habanalabs device.
* @ddev: pointer to DRM device structure.
* @file: pointer to DRM file private data structure.
*
* Called when process opens an habanalabs device.
*/
int hl_device_open(struct drm_device *ddev, struct drm_file *file_priv)
{
struct hl_device *hdev = to_hl_device(ddev);
enum hl_device_status status;
struct hl_fpriv *hpriv;
int rc;
hpriv = kzalloc_obj(*hpriv);
if (!hpriv)
return -ENOMEM;
hpriv->hdev = hdev;
mutex_init(&hpriv->notifier_event.lock);
mutex_init(&hpriv->restore_phase_mutex);
mutex_init(&hpriv->ctx_lock);
kref_init(&hpriv->refcount);
hl_ctx_mgr_init(&hpriv->ctx_mgr);
hl_mem_mgr_init(hpriv->hdev->dev, &hpriv->mem_mgr);
hpriv->taskpid = get_task_pid(current, PIDTYPE_PID);
mutex_lock(&hdev->fpriv_list_lock);
if (!hl_device_operational(hdev, &status)) {
dev_dbg_ratelimited(hdev->dev,
"Can't open %s because it is %s\n",
dev_name(hdev->dev), hdev->status[status]);
if (status == HL_DEVICE_STATUS_IN_RESET ||
status == HL_DEVICE_STATUS_IN_RESET_AFTER_DEVICE_RELEASE)
rc = -EAGAIN;
else
rc = -EPERM;
goto out_err;
}
if (hdev->is_in_dram_scrub) {
dev_dbg_ratelimited(hdev->dev,
"Can't open %s during dram scrub\n",
dev_name(hdev->dev));
rc = -EAGAIN;
goto out_err;
}
if (hdev->compute_ctx_in_release) {
dev_dbg_ratelimited(hdev->dev,
"Can't open %s because another user is still releasing it\n",
dev_name(hdev->dev));
rc = -EAGAIN;
goto out_err;
}
if (hdev->is_compute_ctx_active) {
dev_dbg_ratelimited(hdev->dev,
"Can't open %s because another user is working on it\n",
dev_name(hdev->dev));
rc = -EBUSY;
goto out_err;
}
rc = hl_ctx_create(hdev, hpriv);
if (rc) {
dev_err(hdev->dev, "Failed to create context %d\n", rc);
goto out_err;
}
list_add(&hpriv->dev_node, &hdev->fpriv_list);
mutex_unlock(&hdev->fpriv_list_lock);
hdev->asic_funcs->send_device_activity(hdev, true);
hl_debugfs_add_file(hpriv);
hl_enable_err_info_capture(&hdev->captured_err_info);
hdev->open_counter++;
hdev->last_successful_open_jif = jiffies;
hdev->last_successful_open_ktime = ktime_get();
file_priv->driver_priv = hpriv;
hpriv->file_priv = file_priv;
return 0;
out_err:
mutex_unlock(&hdev->fpriv_list_lock);
hl_mem_mgr_fini(&hpriv->mem_mgr, NULL);
hl_mem_mgr_idr_destroy(&hpriv->mem_mgr);
hl_ctx_mgr_fini(hpriv->hdev, &hpriv->ctx_mgr);
mutex_destroy(&hpriv->ctx_lock);
mutex_destroy(&hpriv->restore_phase_mutex);
mutex_destroy(&hpriv->notifier_event.lock);
put_pid(hpriv->taskpid);
kfree(hpriv);
return rc;
}
int hl_device_open_ctrl(struct inode *inode, struct file *filp)
{
struct hl_device *hdev;
struct hl_fpriv *hpriv;
int rc;
mutex_lock(&hl_devs_idr_lock);
hdev = idr_find(&hl_devs_idr, iminor(inode));
mutex_unlock(&hl_devs_idr_lock);
if (!hdev) {
pr_err("Couldn't find device %d:%d\n",
imajor(inode), iminor(inode));
return -ENXIO;
}
hpriv = kzalloc_obj(*hpriv);
if (!hpriv)
return -ENOMEM;
/* Prevent other routines from reading partial hpriv data by
* initializing hpriv fields before inserting it to the list
*/
hpriv->hdev = hdev;
filp->private_data = hpriv;
nonseekable_open(inode, filp);
hpriv->taskpid = get_task_pid(current, PIDTYPE_PID);
mutex_lock(&hdev->fpriv_ctrl_list_lock);
if (!hl_ctrl_device_operational(hdev, NULL)) {
dev_dbg_ratelimited(hdev->dev_ctrl,
"Can't open %s because it is disabled\n",
dev_name(hdev->dev_ctrl));
rc = -EPERM;
goto out_err;
}
list_add(&hpriv->dev_node, &hdev->fpriv_ctrl_list);
mutex_unlock(&hdev->fpriv_ctrl_list_lock);
return 0;
out_err:
mutex_unlock(&hdev->fpriv_ctrl_list_lock);
filp->private_data = NULL;
put_pid(hpriv->taskpid);
kfree(hpriv);
return rc;
}
static void set_driver_behavior_per_device(struct hl_device *hdev)
{
hdev->nic_ports_mask = 0;
hdev->fw_components = FW_TYPE_ALL_TYPES;
hdev->cpu_queues_enable = 1;
hdev->pldm = 0;
hdev->hard_reset_on_fw_events = 1;
hdev->bmc_enable = 1;
hdev->reset_on_preboot_fail = 1;
hdev->heartbeat = 1;
}
static void copy_kernel_module_params_to_device(struct hl_device *hdev)
{
hdev->asic_prop.fw_security_enabled = is_asic_secured(hdev->asic_type);
hdev->major = hl_major;
hdev->memory_scrub = memory_scrub;
hdev->reset_on_lockup = reset_on_lockup;
hdev->boot_error_status_mask = boot_error_status_mask;
}
static void fixup_device_params_per_asic(struct hl_device *hdev, int timeout)
{
switch (hdev->asic_type) {
case ASIC_GAUDI:
case ASIC_GAUDI_SEC:
/* If user didn't request a different timeout than the default one, we have
* a different default timeout for Gaudi
*/
if (timeout == HL_DEFAULT_TIMEOUT_LOCKED)
hdev->timeout_jiffies = secs_to_jiffies(GAUDI_DEFAULT_TIMEOUT_LOCKED);
hdev->reset_upon_device_release = 0;
break;
case ASIC_GOYA:
hdev->reset_upon_device_release = 0;
break;
default:
hdev->reset_upon_device_release = 1;
break;
}
}
static int fixup_device_params(struct hl_device *hdev)
{
int tmp_timeout;
tmp_timeout = timeout_locked;
hdev->fw_poll_interval_usec = HL_FW_STATUS_POLL_INTERVAL_USEC;
hdev->fw_comms_poll_interval_usec = HL_FW_STATUS_POLL_INTERVAL_USEC;
if (tmp_timeout)
hdev->timeout_jiffies = secs_to_jiffies(tmp_timeout);
else
hdev->timeout_jiffies = MAX_SCHEDULE_TIMEOUT;
hdev->stop_on_err = true;
hdev->reset_info.curr_reset_cause = HL_RESET_CAUSE_UNKNOWN;
hdev->reset_info.prev_reset_trigger = HL_RESET_TRIGGER_DEFAULT;
/* Enable only after the initialization of the device */
hdev->disabled = true;
if (!(hdev->fw_components & FW_TYPE_PREBOOT_CPU) &&
(hdev->fw_components & ~FW_TYPE_PREBOOT_CPU)) {
pr_err("Preboot must be set along with other components");
return -EINVAL;
}
/* If CPU queues not enabled, no way to do heartbeat */
if (!hdev->cpu_queues_enable)
hdev->heartbeat = 0;
fixup_device_params_per_asic(hdev, tmp_timeout);
return 0;
}
static int allocate_device_id(struct hl_device *hdev)
{
int id;
mutex_lock(&hl_devs_idr_lock);
id = idr_alloc(&hl_devs_idr, hdev, 0, HL_MAX_MINORS, GFP_KERNEL);
mutex_unlock(&hl_devs_idr_lock);
if (id < 0) {
if (id == -ENOSPC)
pr_err("too many devices in the system\n");
return -EBUSY;
}
hdev->id = id;
/*
* Firstly initialized with the internal device ID.
* Will be updated later after the DRM device registration to hold the minor ID.
*/
hdev->cdev_idx = hdev->id;
return 0;
}
/**
* create_hdev - create habanalabs device instance
*
* @dev: will hold the pointer to the new habanalabs device structure
* @pdev: pointer to the pci device
*
* Allocate memory for habanalabs device and initialize basic fields
* Identify the ASIC type
* Allocate ID (minor) for the device (only for real devices)
*/
static int create_hdev(struct hl_device **dev, struct pci_dev *pdev)
{
struct hl_device *hdev;
int rc;
*dev = NULL;
hdev = devm_drm_dev_alloc(&pdev->dev, &hl_driver, struct hl_device, drm);
if (IS_ERR(hdev))
return PTR_ERR(hdev);
hdev->dev = hdev->drm.dev;
/* Will be NULL in case of simulator device */
hdev->pdev = pdev;
/* Assign status description string */
strscpy(hdev->status[HL_DEVICE_STATUS_OPERATIONAL], "operational", HL_STR_MAX);
strscpy(hdev->status[HL_DEVICE_STATUS_IN_RESET], "in reset", HL_STR_MAX);
strscpy(hdev->status[HL_DEVICE_STATUS_MALFUNCTION], "disabled", HL_STR_MAX);
strscpy(hdev->status[HL_DEVICE_STATUS_NEEDS_RESET], "needs reset", HL_STR_MAX);
strscpy(hdev->status[HL_DEVICE_STATUS_IN_DEVICE_CREATION],
"in device creation", HL_STR_MAX);
strscpy(hdev->status[HL_DEVICE_STATUS_IN_RESET_AFTER_DEVICE_RELEASE],
"in reset after device release", HL_STR_MAX);
/* First, we must find out which ASIC are we handling. This is needed
* to configure the behavior of the driver (kernel parameters)
*/
hdev->asic_type = get_asic_type(hdev);
if (hdev->asic_type == ASIC_INVALID) {
dev_err(&pdev->dev, "Unsupported ASIC\n");
rc = -ENODEV;
goto out_err;
}
copy_kernel_module_params_to_device(hdev);
set_driver_behavior_per_device(hdev);
fixup_device_params(hdev);
rc = allocate_device_id(hdev);
if (rc)
goto out_err;
*dev = hdev;
return 0;
out_err:
return rc;
}
/*
* destroy_hdev - destroy habanalabs device instance
*
* @dev: pointer to the habanalabs device structure
*
*/
static void destroy_hdev(struct hl_device *hdev)
{
/* Remove device from the device list */
mutex_lock(&hl_devs_idr_lock);
idr_remove(&hl_devs_idr, hdev->id);
mutex_unlock(&hl_devs_idr_lock);
}
static int hl_pmops_suspend(struct device *dev)
{
struct hl_device *hdev = dev_get_drvdata(dev);
pr_debug("Going to suspend PCI device\n");
if (!hdev) {
pr_err("device pointer is NULL in suspend\n");
return 0;
}
return hl_device_suspend(hdev);
}
static int hl_pmops_resume(struct device *dev)
{
struct hl_device *hdev = dev_get_drvdata(dev);
pr_debug("Going to resume PCI device\n");
if (!hdev) {
pr_err("device pointer is NULL in resume\n");
return 0;
}
return hl_device_resume(hdev);
}
/**
* hl_pci_probe - probe PCI habanalabs devices
*
* @pdev: pointer to pci device
* @id: pointer to pci device id structure
*
* Standard PCI probe function for habanalabs device.
* Create a new habanalabs device and initialize it according to the
* device's type
*/
static int hl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct hl_device *hdev;
int rc;
dev_info(&pdev->dev, HL_NAME
" device found [%04x:%04x] (rev %x)\n",
(int)pdev->vendor, (int)pdev->device, (int)pdev->revision);
rc = create_hdev(&hdev, pdev);
if (rc)
return rc;
pci_set_drvdata(pdev, hdev);
rc = hl_device_init(hdev);
if (rc) {
dev_err(&pdev->dev, "Fatal error during habanalabs device init\n");
rc = -ENODEV;
goto disable_device;
}
return 0;
disable_device:
pci_set_drvdata(pdev, NULL);
destroy_hdev(hdev);
return rc;
}
/*
* hl_pci_remove - remove PCI habanalabs devices
*
* @pdev: pointer to pci device
*
* Standard PCI remove function for habanalabs device
*/
static void hl_pci_remove(struct pci_dev *pdev)
{
struct hl_device *hdev;
hdev = pci_get_drvdata(pdev);
if (!hdev)
return;
hl_device_fini(hdev);
pci_set_drvdata(pdev, NULL);
destroy_hdev(hdev);
}
/**
* hl_pci_err_detected - a PCI bus error detected on this device
*
* @pdev: pointer to pci device
* @state: PCI error type
*
* Called by the PCI subsystem whenever a non-correctable
* PCI bus error is detected
*/
static pci_ers_result_t
hl_pci_err_detected(struct pci_dev *pdev, pci_channel_state_t state)
{
struct hl_device *hdev = pci_get_drvdata(pdev);
enum pci_ers_result result;
switch (state) {
case pci_channel_io_normal:
dev_warn(hdev->dev, "PCI normal state error detected\n");
return PCI_ERS_RESULT_CAN_RECOVER;
case pci_channel_io_frozen:
dev_warn(hdev->dev, "PCI frozen state error detected\n");
result = PCI_ERS_RESULT_NEED_RESET;
break;
case pci_channel_io_perm_failure:
dev_warn(hdev->dev, "PCI failure state error detected\n");
result = PCI_ERS_RESULT_DISCONNECT;
break;
default:
result = PCI_ERS_RESULT_NONE;
}
hdev->asic_funcs->halt_engines(hdev, true, false);
return result;
}
/**
* hl_pci_err_resume - resume after a PCI slot reset
*
* @pdev: pointer to pci device
*
*/
static void hl_pci_err_resume(struct pci_dev *pdev)
{
struct hl_device *hdev = pci_get_drvdata(pdev);
dev_warn(hdev->dev, "Resuming device after PCI slot reset\n");
hl_device_resume(hdev);
}
/**
* hl_pci_err_slot_reset - a PCI slot reset has just happened
*
* @pdev: pointer to pci device
*
* Determine if the driver can recover from the PCI slot reset
*/
static pci_ers_result_t hl_pci_err_slot_reset(struct pci_dev *pdev)
{
struct hl_device *hdev = pci_get_drvdata(pdev);
dev_warn(hdev->dev, "PCI slot reset detected\n");
return PCI_ERS_RESULT_RECOVERED;
}
static void hl_pci_reset_prepare(struct pci_dev *pdev)
{
struct hl_device *hdev;
hdev = pci_get_drvdata(pdev);
if (!hdev)
return;
hdev->disabled = true;
}
static void hl_pci_reset_done(struct pci_dev *pdev)
{
struct hl_device *hdev;
u32 flags;
hdev = pci_get_drvdata(pdev);
if (!hdev)
return;
/*
* Schedule a thread to trigger hard reset.
* The reason for this handler, is for rare cases where the driver is up
* and FLR occurs. This is valid only when working with no VM, so FW handles FLR
* and resets the device. FW will go back preboot stage, so driver needs to perform
* hard reset in order to load FW fit again.
*/
flags = HL_DRV_RESET_HARD | HL_DRV_RESET_BYPASS_REQ_TO_FW;
hl_device_reset(hdev, flags);
}
static const struct dev_pm_ops hl_pm_ops = {
.suspend = hl_pmops_suspend,
.resume = hl_pmops_resume,
};
static const struct pci_error_handlers hl_pci_err_handler = {
.error_detected = hl_pci_err_detected,
.slot_reset = hl_pci_err_slot_reset,
.resume = hl_pci_err_resume,
.reset_prepare = hl_pci_reset_prepare,
.reset_done = hl_pci_reset_done,
};
static struct pci_driver hl_pci_driver = {
.name = HL_NAME,
.id_table = ids,
.probe = hl_pci_probe,
.remove = hl_pci_remove,
.shutdown = hl_pci_remove,
.driver = {
.name = HL_NAME,
.pm = &hl_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.err_handler = &hl_pci_err_handler,
};
/*
* hl_init - Initialize the habanalabs kernel driver
*/
static int __init hl_init(void)
{
int rc;
dev_t dev;
pr_info("loading driver\n");
rc = alloc_chrdev_region(&dev, 0, HL_MAX_MINORS, HL_NAME);
if (rc < 0) {
pr_err("unable to get major\n");
return rc;
}
hl_major = MAJOR(dev);
rc = pci_register_driver(&hl_pci_driver);
if (rc) {
pr_err("failed to register pci device\n");
goto remove_major;
}
pr_debug("driver loaded\n");
return 0;
remove_major:
unregister_chrdev_region(MKDEV(hl_major, 0), HL_MAX_MINORS);
return rc;
}
/*
* hl_exit - Release all resources of the habanalabs kernel driver
*/
static void __exit hl_exit(void)
{
pci_unregister_driver(&hl_pci_driver);
unregister_chrdev_region(MKDEV(hl_major, 0), HL_MAX_MINORS);
idr_destroy(&hl_devs_idr);
pr_debug("driver removed\n");
}
module_init(hl_init);
module_exit(hl_exit);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,437 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2024 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include "hldio.h"
#include <generated/uapi/linux/version.h>
#include <linux/pci-p2pdma.h>
#include <linux/blkdev.h>
#include <linux/vmalloc.h>
/*
* NVMe Direct I/O implementation for habanalabs driver
*
* ASSUMPTIONS
* ===========
* 1. No IOMMU (well, technically it can work with IOMMU, but it is *almost useless).
* 2. Only READ operations (can extend in the future).
* 3. No sparse files (can overcome this in the future).
* 4. Kernel version >= 6.9
* 5. Requiring page alignment is OK (I don't see a solution to this one right,
* now, how do we read partial pages?)
* 6. Kernel compiled with CONFIG_PCI_P2PDMA. This requires a CUSTOM kernel.
* Theoretically I have a slight idea on how this could be solvable, but it
* is probably inacceptable for the upstream. Also may not work in the end.
* 7. Either make sure our cards and disks are under the same PCI bridge, or
* compile a custom kernel to hack around this.
*/
#define IO_STABILIZE_TIMEOUT 10000000 /* 10 seconds in microseconds */
/*
* This struct contains all the useful data I could milk out of the file handle
* provided by the user.
* @TODO: right now it is retrieved on each IO, but can be done once with some
* dedicated IOCTL, call it for example HL_REGISTER_HANDLE.
*/
struct hl_dio_fd {
/* Back pointer in case we need it in async completion */
struct hl_ctx *ctx;
/* Associated fd struct */
struct file *filp;
};
/*
* This is a single IO descriptor
*/
struct hl_direct_io {
struct hl_dio_fd f;
struct kiocb kio;
struct bio_vec *bv;
struct iov_iter iter;
u64 device_va;
u64 off_bytes;
u64 len_bytes;
u32 type;
};
bool hl_device_supports_nvme(struct hl_device *hdev)
{
return hdev->asic_prop.supports_nvme;
}
static int hl_dio_fd_register(struct hl_ctx *ctx, int fd, struct hl_dio_fd *f)
{
struct hl_device *hdev = ctx->hdev;
struct block_device *bd;
struct super_block *sb;
struct inode *inode;
struct gendisk *gd;
struct device *disk_dev;
int rc;
f->filp = fget(fd);
if (!f->filp) {
rc = -ENOENT;
goto out;
}
if (!(f->filp->f_flags & O_DIRECT)) {
dev_err(hdev->dev, "file is not in the direct mode\n");
rc = -EINVAL;
goto fput;
}
if (!f->filp->f_op->read_iter) {
dev_err(hdev->dev, "read iter is not supported, need to fall back to legacy\n");
rc = -EINVAL;
goto fput;
}
inode = file_inode(f->filp);
sb = inode->i_sb;
bd = sb->s_bdev;
gd = bd->bd_disk;
if (inode->i_blocks << sb->s_blocksize_bits < i_size_read(inode)) {
dev_err(hdev->dev, "sparse files are not currently supported\n");
rc = -EINVAL;
goto fput;
}
if (!bd || !gd) {
dev_err(hdev->dev, "invalid block device\n");
rc = -ENODEV;
goto fput;
}
/* Get the underlying device from the block device */
disk_dev = disk_to_dev(gd);
if (!dma_pci_p2pdma_supported(disk_dev)) {
dev_err(hdev->dev, "device does not support PCI P2P DMA\n");
rc = -EOPNOTSUPP;
goto fput;
}
/*
* @TODO: Maybe we need additional checks here
*/
f->ctx = ctx;
rc = 0;
goto out;
fput:
fput(f->filp);
out:
return rc;
}
static void hl_dio_fd_unregister(struct hl_dio_fd *f)
{
fput(f->filp);
}
static long hl_dio_count_io(struct hl_device *hdev)
{
s64 sum = 0;
int i;
for_each_possible_cpu(i)
sum += per_cpu(*hdev->hldio.inflight_ios, i);
return sum;
}
static bool hl_dio_get_iopath(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
if (hdev->hldio.io_enabled) {
this_cpu_inc(*hdev->hldio.inflight_ios);
/* Avoid race conditions */
if (!hdev->hldio.io_enabled) {
this_cpu_dec(*hdev->hldio.inflight_ios);
return false;
}
hl_ctx_get(ctx);
return true;
}
return false;
}
static void hl_dio_put_iopath(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
hl_ctx_put(ctx);
this_cpu_dec(*hdev->hldio.inflight_ios);
}
static void hl_dio_set_io_enabled(struct hl_device *hdev, bool enabled)
{
hdev->hldio.io_enabled = enabled;
}
static bool hl_dio_validate_io(struct hl_device *hdev, struct hl_direct_io *io)
{
if ((u64)io->device_va & ~PAGE_MASK) {
dev_dbg(hdev->dev, "device address must be 4K aligned\n");
return false;
}
if (io->len_bytes & ~PAGE_MASK) {
dev_dbg(hdev->dev, "IO length must be 4K aligned\n");
return false;
}
if (io->off_bytes & ~PAGE_MASK) {
dev_dbg(hdev->dev, "IO offset must be 4K aligned\n");
return false;
}
return true;
}
static struct page *hl_dio_va2page(struct hl_device *hdev, struct hl_ctx *ctx, u64 device_va)
{
struct hl_dio *hldio = &hdev->hldio;
u64 device_pa;
int rc, i;
rc = hl_mmu_va_to_pa(ctx, device_va, &device_pa);
if (rc) {
dev_err(hdev->dev, "device virtual address translation error: %#llx (%d)",
device_va, rc);
return NULL;
}
for (i = 0 ; i < hldio->np2prs ; ++i) {
if (device_pa >= hldio->p2prs[i].device_pa &&
device_pa < hldio->p2prs[i].device_pa + hldio->p2prs[i].size)
return hldio->p2prs[i].p2ppages[(device_pa - hldio->p2prs[i].device_pa) >>
PAGE_SHIFT];
}
return NULL;
}
static ssize_t hl_direct_io(struct hl_device *hdev, struct hl_direct_io *io)
{
u64 npages, device_va;
ssize_t rc;
int i;
if (!hl_dio_validate_io(hdev, io))
return -EINVAL;
if (!hl_dio_get_iopath(io->f.ctx)) {
dev_info(hdev->dev, "can't schedule a new IO, IO is disabled\n");
return -ESHUTDOWN;
}
init_sync_kiocb(&io->kio, io->f.filp);
io->kio.ki_pos = io->off_bytes;
npages = (io->len_bytes >> PAGE_SHIFT);
/* @TODO: this can be implemented smarter, vmalloc in iopath is not
* ideal. Maybe some variation of genpool. Number of pages may differ
* greatly, so maybe even use pools of different sizes and chose the
* closest one.
*/
io->bv = vzalloc(npages * sizeof(struct bio_vec));
if (!io->bv)
return -ENOMEM;
for (i = 0, device_va = io->device_va; i < npages ; ++i, device_va += PAGE_SIZE) {
io->bv[i].bv_page = hl_dio_va2page(hdev, io->f.ctx, device_va);
if (!io->bv[i].bv_page) {
dev_err(hdev->dev, "error getting page struct for device va %#llx",
device_va);
rc = -EFAULT;
goto cleanup;
}
io->bv[i].bv_offset = 0;
io->bv[i].bv_len = PAGE_SIZE;
}
iov_iter_bvec(&io->iter, io->type, io->bv, 1, io->len_bytes);
if (io->f.filp->f_op && io->f.filp->f_op->read_iter)
rc = io->f.filp->f_op->read_iter(&io->kio, &io->iter);
else
rc = -EINVAL;
cleanup:
vfree(io->bv);
hl_dio_put_iopath(io->f.ctx);
dev_dbg(hdev->dev, "IO ended with %ld\n", rc);
return rc;
}
/*
* @TODO: This function can be used as a callback for io completion under
* kio->ki_complete in order to implement async IO.
* Note that on more recent kernels there is no ret2.
*/
__maybe_unused static void hl_direct_io_complete(struct kiocb *kio, long ret, long ret2)
{
struct hl_direct_io *io = container_of(kio, struct hl_direct_io, kio);
dev_dbg(io->f.ctx->hdev->dev, "IO completed with %ld\n", ret);
/* Do something to copy result to user / notify completion */
hl_dio_put_iopath(io->f.ctx);
hl_dio_fd_unregister(&io->f);
}
/*
* DMA disk to ASIC, wait for results. Must be invoked from the user context
*/
int hl_dio_ssd2hl(struct hl_device *hdev, struct hl_ctx *ctx, int fd,
u64 device_va, off_t off_bytes, size_t len_bytes,
size_t *len_read)
{
struct hl_direct_io *io;
ssize_t rc;
dev_dbg(hdev->dev, "SSD2HL fd=%d va=%#llx len=%#lx\n", fd, device_va, len_bytes);
io = kzalloc_obj(*io);
if (!io) {
rc = -ENOMEM;
goto out;
}
*io = (struct hl_direct_io){
.device_va = device_va,
.len_bytes = len_bytes,
.off_bytes = off_bytes,
.type = READ,
};
rc = hl_dio_fd_register(ctx, fd, &io->f);
if (rc)
goto kfree_io;
rc = hl_direct_io(hdev, io);
if (rc >= 0) {
*len_read = rc;
rc = 0;
}
/* This shall be called only in the case of a sync IO */
hl_dio_fd_unregister(&io->f);
kfree_io:
kfree(io);
out:
return rc;
}
static void hl_p2p_region_fini(struct hl_device *hdev, struct hl_p2p_region *p2pr)
{
if (p2pr->p2ppages) {
vfree(p2pr->p2ppages);
p2pr->p2ppages = NULL;
}
if (p2pr->p2pmem) {
dev_dbg(hdev->dev, "freeing P2P mem from %p, size=%#llx\n",
p2pr->p2pmem, p2pr->size);
pci_free_p2pmem(hdev->pdev, p2pr->p2pmem, p2pr->size);
p2pr->p2pmem = NULL;
}
}
void hl_p2p_region_fini_all(struct hl_device *hdev)
{
int i;
for (i = 0 ; i < hdev->hldio.np2prs ; ++i)
hl_p2p_region_fini(hdev, &hdev->hldio.p2prs[i]);
kvfree(hdev->hldio.p2prs);
hdev->hldio.p2prs = NULL;
hdev->hldio.np2prs = 0;
}
int hl_p2p_region_init(struct hl_device *hdev, struct hl_p2p_region *p2pr)
{
void *addr;
int rc, i;
/* Start by publishing our p2p memory */
rc = pci_p2pdma_add_resource(hdev->pdev, p2pr->bar, p2pr->size, p2pr->bar_offset);
if (rc) {
dev_err(hdev->dev, "error adding p2p resource: %d\n", rc);
goto err;
}
/* Alloc all p2p mem */
p2pr->p2pmem = pci_alloc_p2pmem(hdev->pdev, p2pr->size);
if (!p2pr->p2pmem) {
dev_err(hdev->dev, "error allocating p2p memory\n");
rc = -ENOMEM;
goto err;
}
p2pr->p2ppages = vmalloc((p2pr->size >> PAGE_SHIFT) * sizeof(struct page *));
if (!p2pr->p2ppages) {
rc = -ENOMEM;
goto err;
}
for (i = 0, addr = p2pr->p2pmem ; i < (p2pr->size >> PAGE_SHIFT) ; ++i, addr += PAGE_SIZE) {
p2pr->p2ppages[i] = virt_to_page(addr);
if (!p2pr->p2ppages[i]) {
rc = -EFAULT;
goto err;
}
}
return 0;
err:
hl_p2p_region_fini(hdev, p2pr);
return rc;
}
int hl_dio_start(struct hl_device *hdev)
{
dev_dbg(hdev->dev, "initializing HLDIO\n");
/* Initialize the IO counter and enable IO */
hdev->hldio.inflight_ios = alloc_percpu(s64);
if (!hdev->hldio.inflight_ios)
return -ENOMEM;
hl_dio_set_io_enabled(hdev, true);
return 0;
}
void hl_dio_stop(struct hl_device *hdev)
{
dev_dbg(hdev->dev, "deinitializing HLDIO\n");
if (hdev->hldio.io_enabled) {
/* Wait for all the IO to finish */
hl_dio_set_io_enabled(hdev, false);
hl_poll_timeout_condition(hdev, !hl_dio_count_io(hdev), 1000, IO_STABILIZE_TIMEOUT);
}
if (hdev->hldio.inflight_ios) {
free_percpu(hdev->hldio.inflight_ios);
hdev->hldio.inflight_ios = NULL;
}
}
@@ -0,0 +1,146 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* hldio.h - NVMe Direct I/O (HLDIO) infrastructure for Habana Labs Driver
*
* This feature requires specific hardware setup and must not be built
* under COMPILE_TEST.
*/
#ifndef __HL_HLDIO_H__
#define __HL_HLDIO_H__
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <linux/ktime.h> /* ktime functions */
#include <linux/delay.h> /* usleep_range */
#include <linux/kernel.h> /* might_sleep_if */
#include <linux/errno.h> /* error codes */
/* Forward declarations */
struct hl_device;
struct file;
/* Enable only if Kconfig selected */
#ifdef CONFIG_HL_HLDIO
/**
* struct hl_p2p_region - describes a single P2P memory region
* @p2ppages: array of page structs for the P2P memory
* @p2pmem: virtual address of the P2P memory region
* @device_pa: physical address on the device
* @bar_offset: offset within the BAR
* @size: size of the region in bytes
* @bar: BAR number containing this region
*/
struct hl_p2p_region {
struct page **p2ppages;
void *p2pmem;
u64 device_pa;
u64 bar_offset;
u64 size;
int bar;
};
/**
* struct hl_dio_stats - Direct I/O statistics
* @total_ops: total number of operations attempted
* @successful_ops: number of successful operations
* @failed_ops: number of failed operations
* @bytes_transferred: total bytes successfully transferred
* @last_len_read: length of the last read operation
*/
struct hl_dio_stats {
u64 total_ops;
u64 successful_ops;
u64 failed_ops;
u64 bytes_transferred;
size_t last_len_read;
};
/**
* struct hl_dio - describes habanalabs direct storage interaction interface
* @p2prs: array of p2p regions
* @inflight_ios: percpu counter for inflight ios
* @np2prs: number of elements in p2prs
* @io_enabled: 1 if io is enabled 0 otherwise
*/
struct hl_dio {
struct hl_p2p_region *p2prs;
s64 __percpu *inflight_ios;
u8 np2prs;
u8 io_enabled;
};
int hl_dio_ssd2hl(struct hl_device *hdev, struct hl_ctx *ctx, int fd,
u64 device_va, off_t off_bytes, size_t len_bytes,
size_t *len_read);
void hl_p2p_region_fini_all(struct hl_device *hdev);
int hl_p2p_region_init(struct hl_device *hdev, struct hl_p2p_region *p2pr);
int hl_dio_start(struct hl_device *hdev);
void hl_dio_stop(struct hl_device *hdev);
/* Init/teardown */
int hl_hldio_init(struct hl_device *hdev);
void hl_hldio_fini(struct hl_device *hdev);
/* File operations */
long hl_hldio_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
/* DebugFS hooks */
#ifdef CONFIG_DEBUG_FS
void hl_hldio_debugfs_init(struct hl_device *hdev);
void hl_hldio_debugfs_fini(struct hl_device *hdev);
#else
static inline void hl_hldio_debugfs_init(struct hl_device *hdev) { }
static inline void hl_hldio_debugfs_fini(struct hl_device *hdev) { }
#endif
#else /* !CONFIG_HL_HLDIO */
struct hl_p2p_region;
/* Stubs when HLDIO is disabled */
static inline int hl_dio_ssd2hl(struct hl_device *hdev, struct hl_ctx *ctx, int fd,
u64 device_va, off_t off_bytes, size_t len_bytes,
size_t *len_read)
{ return -EOPNOTSUPP; }
static inline void hl_p2p_region_fini_all(struct hl_device *hdev) {}
static inline int hl_p2p_region_init(struct hl_device *hdev, struct hl_p2p_region *p2pr)
{ return -EOPNOTSUPP; }
static inline int hl_dio_start(struct hl_device *hdev) { return -EOPNOTSUPP; }
static inline void hl_dio_stop(struct hl_device *hdev) {}
static inline int hl_hldio_init(struct hl_device *hdev) { return 0; }
static inline void hl_hldio_fini(struct hl_device *hdev) { }
static inline long hl_hldio_ioctl(struct file *f, unsigned int c,
unsigned long a)
{ return -ENOTTY; }
static inline void hl_hldio_debugfs_init(struct hl_device *hdev) { }
static inline void hl_hldio_debugfs_fini(struct hl_device *hdev) { }
#endif /* CONFIG_HL_HLDIO */
/* Simplified polling macro for HLDIO (no simulator support) */
#define hl_poll_timeout_condition(hdev, cond, sleep_us, timeout_us) \
({ \
ktime_t __timeout = ktime_add_us(ktime_get(), timeout_us); \
might_sleep_if(sleep_us); \
(void)(hdev); /* keep signature consistent, hdev unused */ \
for (;;) { \
mb(); /* ensure ordering of memory operations */ \
if (cond) \
break; \
if (timeout_us && ktime_compare(ktime_get(), __timeout) > 0) \
break; \
if (sleep_us) \
usleep_range((sleep_us >> 2) + 1, sleep_us); \
} \
(cond) ? 0 : -ETIMEDOUT; \
})
#ifdef CONFIG_HL_HLDIO
bool hl_device_supports_nvme(struct hl_device *hdev);
#else
static inline bool hl_device_supports_nvme(struct hl_device *hdev) { return false; }
#endif
#endif /* __HL_HLDIO_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,931 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2019 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include <linux/pci.h>
#include <linux/hwmon.h>
#define HWMON_NR_SENSOR_TYPES (hwmon_max)
#ifdef _HAS_HWMON_HWMON_T_ENABLE
static u32 fixup_flags_legacy_fw(struct hl_device *hdev, enum hwmon_sensor_types type,
u32 cpucp_flags)
{
u32 flags;
switch (type) {
case hwmon_temp:
flags = (cpucp_flags << 1) | HWMON_T_ENABLE;
break;
case hwmon_in:
flags = (cpucp_flags << 1) | HWMON_I_ENABLE;
break;
case hwmon_curr:
flags = (cpucp_flags << 1) | HWMON_C_ENABLE;
break;
case hwmon_fan:
flags = (cpucp_flags << 1) | HWMON_F_ENABLE;
break;
case hwmon_power:
flags = (cpucp_flags << 1) | HWMON_P_ENABLE;
break;
case hwmon_pwm:
/* enable bit was here from day 1, so no need to adjust */
flags = cpucp_flags;
break;
default:
dev_err_ratelimited(hdev->dev, "unsupported h/w sensor type %d\n", type);
flags = cpucp_flags;
break;
}
return flags;
}
static u32 fixup_attr_legacy_fw(u32 attr)
{
return (attr - 1);
}
#else
static u32 fixup_flags_legacy_fw(struct hl_device *hdev, enum hwmon_sensor_types type,
u32 cpucp_flags)
{
return cpucp_flags;
}
static u32 fixup_attr_legacy_fw(u32 attr)
{
return attr;
}
#endif /* !_HAS_HWMON_HWMON_T_ENABLE */
static u32 adjust_hwmon_flags(struct hl_device *hdev, enum hwmon_sensor_types type, u32 cpucp_flags)
{
u32 flags, cpucp_input_val;
bool use_cpucp_enum;
use_cpucp_enum = (hdev->asic_prop.fw_app_cpu_boot_dev_sts0 &
CPU_BOOT_DEV_STS0_MAP_HWMON_EN) ? true : false;
/* If f/w is using it's own enum, we need to check if the properties values are aligned.
* If not, it means we need to adjust the values to the new format that is used in the
* kernel since 5.6 (enum values were incremented by 1 by adding a new enable value).
*/
if (use_cpucp_enum) {
switch (type) {
case hwmon_temp:
cpucp_input_val = cpucp_temp_input;
if (cpucp_input_val == hwmon_temp_input)
flags = cpucp_flags;
else
flags = (cpucp_flags << 1) | HWMON_T_ENABLE;
break;
case hwmon_in:
cpucp_input_val = cpucp_in_input;
if (cpucp_input_val == hwmon_in_input)
flags = cpucp_flags;
else
flags = (cpucp_flags << 1) | HWMON_I_ENABLE;
break;
case hwmon_curr:
cpucp_input_val = cpucp_curr_input;
if (cpucp_input_val == hwmon_curr_input)
flags = cpucp_flags;
else
flags = (cpucp_flags << 1) | HWMON_C_ENABLE;
break;
case hwmon_fan:
cpucp_input_val = cpucp_fan_input;
if (cpucp_input_val == hwmon_fan_input)
flags = cpucp_flags;
else
flags = (cpucp_flags << 1) | HWMON_F_ENABLE;
break;
case hwmon_pwm:
/* enable bit was here from day 1, so no need to adjust */
flags = cpucp_flags;
break;
case hwmon_power:
cpucp_input_val = CPUCP_POWER_INPUT;
if (cpucp_input_val == hwmon_power_input)
flags = cpucp_flags;
else
flags = (cpucp_flags << 1) | HWMON_P_ENABLE;
break;
default:
dev_err_ratelimited(hdev->dev, "unsupported h/w sensor type %d\n", type);
flags = cpucp_flags;
break;
}
} else {
flags = fixup_flags_legacy_fw(hdev, type, cpucp_flags);
}
return flags;
}
int hl_build_hwmon_channel_info(struct hl_device *hdev, struct cpucp_sensor *sensors_arr)
{
u32 num_sensors_for_type, flags, num_active_sensor_types = 0, arr_size = 0, *curr_arr;
u32 sensors_by_type_next_index[HWMON_NR_SENSOR_TYPES] = {0};
u32 *sensors_by_type[HWMON_NR_SENSOR_TYPES] = {NULL};
struct hwmon_channel_info **channels_info;
u32 counts[HWMON_NR_SENSOR_TYPES] = {0};
enum hwmon_sensor_types type;
int rc, i, j;
for (i = 0 ; i < CPUCP_MAX_SENSORS ; i++) {
type = le32_to_cpu(sensors_arr[i].type);
if ((type == 0) && (sensors_arr[i].flags == 0))
break;
if (type >= HWMON_NR_SENSOR_TYPES) {
dev_err_ratelimited(hdev->dev,
"Got wrong sensor type %d from device\n", type);
return -EINVAL;
}
counts[type]++;
arr_size++;
}
for (i = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++) {
if (counts[i] == 0)
continue;
num_sensors_for_type = counts[i] + 1;
dev_dbg(hdev->dev, "num_sensors_for_type %d = %d\n", i, num_sensors_for_type);
curr_arr = kcalloc(num_sensors_for_type, sizeof(*curr_arr), GFP_KERNEL);
if (!curr_arr) {
rc = -ENOMEM;
goto sensors_type_err;
}
num_active_sensor_types++;
sensors_by_type[i] = curr_arr;
}
for (i = 0 ; i < arr_size ; i++) {
type = le32_to_cpu(sensors_arr[i].type);
curr_arr = sensors_by_type[type];
flags = adjust_hwmon_flags(hdev, type, le32_to_cpu(sensors_arr[i].flags));
curr_arr[sensors_by_type_next_index[type]++] = flags;
}
channels_info = kzalloc_objs(struct hwmon_channel_info *,
num_active_sensor_types + 1);
if (!channels_info) {
rc = -ENOMEM;
goto channels_info_array_err;
}
for (i = 0 ; i < num_active_sensor_types ; i++) {
channels_info[i] = kzalloc_obj(*channels_info[i]);
if (!channels_info[i]) {
rc = -ENOMEM;
goto channel_info_err;
}
}
for (i = 0, j = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++) {
if (!sensors_by_type[i])
continue;
channels_info[j]->type = i;
channels_info[j]->config = sensors_by_type[i];
j++;
}
hdev->hl_chip_info->info = (const struct hwmon_channel_info **)channels_info;
return 0;
channel_info_err:
for (i = 0 ; i < num_active_sensor_types ; i++) {
if (channels_info[i]) {
kfree(channels_info[i]->config);
kfree(channels_info[i]);
}
}
kfree(channels_info);
channels_info_array_err:
sensors_type_err:
for (i = 0 ; i < HWMON_NR_SENSOR_TYPES ; i++)
kfree(sensors_by_type[i]);
return rc;
}
static int hl_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct hl_device *hdev = dev_get_drvdata(dev);
bool use_cpucp_enum;
u32 cpucp_attr;
int rc;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
use_cpucp_enum = (hdev->asic_prop.fw_app_cpu_boot_dev_sts0 &
CPU_BOOT_DEV_STS0_MAP_HWMON_EN) ? true : false;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
cpucp_attr = cpucp_temp_input;
break;
case hwmon_temp_max:
cpucp_attr = cpucp_temp_max;
break;
case hwmon_temp_crit:
cpucp_attr = cpucp_temp_crit;
break;
case hwmon_temp_max_hyst:
cpucp_attr = cpucp_temp_max_hyst;
break;
case hwmon_temp_crit_hyst:
cpucp_attr = cpucp_temp_crit_hyst;
break;
case hwmon_temp_offset:
cpucp_attr = cpucp_temp_offset;
break;
case hwmon_temp_highest:
cpucp_attr = cpucp_temp_highest;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_temperature(hdev, channel, cpucp_attr, val);
else
rc = hl_get_temperature(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_in:
switch (attr) {
case hwmon_in_input:
cpucp_attr = cpucp_in_input;
break;
case hwmon_in_min:
cpucp_attr = cpucp_in_min;
break;
case hwmon_in_max:
cpucp_attr = cpucp_in_max;
break;
case hwmon_in_highest:
cpucp_attr = cpucp_in_highest;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_voltage(hdev, channel, cpucp_attr, val);
else
rc = hl_get_voltage(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
cpucp_attr = cpucp_curr_input;
break;
case hwmon_curr_min:
cpucp_attr = cpucp_curr_min;
break;
case hwmon_curr_max:
cpucp_attr = cpucp_curr_max;
break;
case hwmon_curr_highest:
cpucp_attr = cpucp_curr_highest;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_current(hdev, channel, cpucp_attr, val);
else
rc = hl_get_current(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_fan:
switch (attr) {
case hwmon_fan_input:
cpucp_attr = cpucp_fan_input;
break;
case hwmon_fan_min:
cpucp_attr = cpucp_fan_min;
break;
case hwmon_fan_max:
cpucp_attr = cpucp_fan_max;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_fan_speed(hdev, channel, cpucp_attr, val);
else
rc = hl_get_fan_speed(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
cpucp_attr = cpucp_pwm_input;
break;
case hwmon_pwm_enable:
cpucp_attr = cpucp_pwm_enable;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_pwm_info(hdev, channel, cpucp_attr, val);
else
/* no need for fixup as pwm was aligned from day 1 */
rc = hl_get_pwm_info(hdev, channel, attr, val);
break;
case hwmon_power:
switch (attr) {
case hwmon_power_input:
cpucp_attr = CPUCP_POWER_INPUT;
break;
case hwmon_power_input_highest:
cpucp_attr = CPUCP_POWER_INPUT_HIGHEST;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
rc = hl_get_power(hdev, channel, cpucp_attr, val);
else
rc = hl_get_power(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
default:
return -EINVAL;
}
return rc;
}
static int hl_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct hl_device *hdev = dev_get_drvdata(dev);
u32 cpucp_attr;
bool use_cpucp_enum = (hdev->asic_prop.fw_app_cpu_boot_dev_sts0 &
CPU_BOOT_DEV_STS0_MAP_HWMON_EN) ? true : false;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_offset:
cpucp_attr = cpucp_temp_offset;
break;
case hwmon_temp_reset_history:
cpucp_attr = cpucp_temp_reset_history;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
hl_set_temperature(hdev, channel, cpucp_attr, val);
else
hl_set_temperature(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
cpucp_attr = cpucp_pwm_input;
break;
case hwmon_pwm_enable:
cpucp_attr = cpucp_pwm_enable;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
hl_set_pwm_info(hdev, channel, cpucp_attr, val);
else
/* no need for fixup as pwm was aligned from day 1 */
hl_set_pwm_info(hdev, channel, attr, val);
break;
case hwmon_in:
switch (attr) {
case hwmon_in_reset_history:
cpucp_attr = cpucp_in_reset_history;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
hl_set_voltage(hdev, channel, cpucp_attr, val);
else
hl_set_voltage(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_curr:
switch (attr) {
case hwmon_curr_reset_history:
cpucp_attr = cpucp_curr_reset_history;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
hl_set_current(hdev, channel, cpucp_attr, val);
else
hl_set_current(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
case hwmon_power:
switch (attr) {
case hwmon_power_reset_history:
cpucp_attr = CPUCP_POWER_RESET_INPUT_HISTORY;
break;
default:
return -EINVAL;
}
if (use_cpucp_enum)
hl_set_power(hdev, channel, cpucp_attr, val);
else
hl_set_power(hdev, channel, fixup_attr_legacy_fw(attr), val);
break;
default:
return -EINVAL;
}
return 0;
}
static umode_t hl_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_max:
case hwmon_temp_max_hyst:
case hwmon_temp_crit:
case hwmon_temp_crit_hyst:
case hwmon_temp_highest:
return 0444;
case hwmon_temp_offset:
return 0644;
case hwmon_temp_reset_history:
return 0200;
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_input:
case hwmon_in_min:
case hwmon_in_max:
case hwmon_in_highest:
return 0444;
case hwmon_in_reset_history:
return 0200;
}
break;
case hwmon_curr:
switch (attr) {
case hwmon_curr_input:
case hwmon_curr_min:
case hwmon_curr_max:
case hwmon_curr_highest:
return 0444;
case hwmon_curr_reset_history:
return 0200;
}
break;
case hwmon_fan:
switch (attr) {
case hwmon_fan_input:
case hwmon_fan_min:
case hwmon_fan_max:
return 0444;
}
break;
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
case hwmon_pwm_enable:
return 0644;
}
break;
case hwmon_power:
switch (attr) {
case hwmon_power_input:
case hwmon_power_input_highest:
return 0444;
case hwmon_power_reset_history:
return 0200;
}
break;
default:
break;
}
return 0;
}
static const struct hwmon_ops hl_hwmon_ops = {
.is_visible = hl_is_visible,
.read = hl_read,
.write = hl_write
};
int hl_get_temperature(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_TEMPERATURE_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get temperature from sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
int hl_set_temperature(struct hl_device *hdev,
int sensor_index, u32 attr, long value)
{
struct cpucp_packet pkt;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_TEMPERATURE_SET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
pkt.value = __cpu_to_le64(value);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, NULL);
if (rc && rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to set temperature of sensor %d, error %d\n",
sensor_index, rc);
return rc;
}
int hl_get_voltage(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_VOLTAGE_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get voltage from sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
int hl_get_current(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_CURRENT_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get current from sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
int hl_get_fan_speed(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_FAN_SPEED_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get fan speed from sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
int hl_get_pwm_info(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_PWM_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get pwm info from sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
void hl_set_pwm_info(struct hl_device *hdev, int sensor_index, u32 attr,
long value)
{
struct cpucp_packet pkt;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_PWM_SET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
pkt.value = cpu_to_le64(value);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, NULL);
if (rc && rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to set pwm info to sensor %d, error %d\n",
sensor_index, rc);
}
int hl_set_voltage(struct hl_device *hdev,
int sensor_index, u32 attr, long value)
{
struct cpucp_packet pkt;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_VOLTAGE_SET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
pkt.value = __cpu_to_le64(value);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, NULL);
if (rc && rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to set voltage of sensor %d, error %d\n",
sensor_index, rc);
return rc;
}
int hl_set_current(struct hl_device *hdev,
int sensor_index, u32 attr, long value)
{
struct cpucp_packet pkt;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_CURRENT_SET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
pkt.value = __cpu_to_le64(value);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt), 0, NULL);
if (rc && rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to set current of sensor %d, error %d\n",
sensor_index, rc);
return rc;
}
int hl_set_power(struct hl_device *hdev,
int sensor_index, u32 attr, long value)
{
struct cpucp_packet pkt;
struct asic_fixed_properties *prop = &hdev->asic_prop;
int rc;
memset(&pkt, 0, sizeof(pkt));
if (prop->use_get_power_for_reset_history)
pkt.ctl = cpu_to_le32(CPUCP_PACKET_POWER_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
else
pkt.ctl = cpu_to_le32(CPUCP_PACKET_POWER_SET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
pkt.value = __cpu_to_le64(value);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, NULL);
if (rc && rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to set power of sensor %d, error %d\n",
sensor_index, rc);
return rc;
}
int hl_get_power(struct hl_device *hdev,
int sensor_index, u32 attr, long *value)
{
struct cpucp_packet pkt;
u64 result;
int rc;
memset(&pkt, 0, sizeof(pkt));
pkt.ctl = cpu_to_le32(CPUCP_PACKET_POWER_GET <<
CPUCP_PKT_CTL_OPCODE_SHIFT);
pkt.sensor_index = __cpu_to_le16(sensor_index);
pkt.type = __cpu_to_le16(attr);
rc = hdev->asic_funcs->send_cpu_message(hdev, (u32 *) &pkt, sizeof(pkt),
0, &result);
*value = (long) result;
if (rc) {
if (rc != -EAGAIN)
dev_err_ratelimited(hdev->dev,
"Failed to get power of sensor %d, error %d\n",
sensor_index, rc);
*value = 0;
}
return rc;
}
int hl_hwmon_init(struct hl_device *hdev)
{
struct device *dev = hdev->pdev ? &hdev->pdev->dev : hdev->dev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
int rc;
if ((hdev->hwmon_initialized) || !(hdev->cpu_queues_enable))
return 0;
if (hdev->hl_chip_info->info) {
hdev->hl_chip_info->ops = &hl_hwmon_ops;
hdev->hwmon_dev = hwmon_device_register_with_info(dev,
prop->cpucp_info.card_name, hdev,
hdev->hl_chip_info, NULL);
if (IS_ERR(hdev->hwmon_dev)) {
rc = PTR_ERR(hdev->hwmon_dev);
dev_err(hdev->dev,
"Unable to register hwmon device: %d\n", rc);
return rc;
}
dev_info(hdev->dev, "%s: add sensors information\n",
dev_name(hdev->hwmon_dev));
hdev->hwmon_initialized = true;
} else {
dev_info(hdev->dev, "no available sensors\n");
}
return 0;
}
void hl_hwmon_fini(struct hl_device *hdev)
{
if (!hdev->hwmon_initialized)
return;
hwmon_device_unregister(hdev->hwmon_dev);
}
void hl_hwmon_release_resources(struct hl_device *hdev)
{
const struct hwmon_channel_info * const *channel_info_arr;
int i = 0;
if (!hdev->hl_chip_info->info)
return;
channel_info_arr = hdev->hl_chip_info->info;
while (channel_info_arr[i]) {
kfree(channel_info_arr[i]->config);
kfree(channel_info_arr[i]);
i++;
}
kfree(channel_info_arr);
hdev->hl_chip_info->info = NULL;
}
@@ -0,0 +1,726 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2022 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include <linux/slab.h>
/**
* struct hl_eqe_work - This structure is used to schedule work of EQ
* entry and cpucp_reset event
*
* @eq_work: workqueue object to run when EQ entry is received
* @hdev: pointer to device structure
* @eq_entry: copy of the EQ entry
*/
struct hl_eqe_work {
struct work_struct eq_work;
struct hl_device *hdev;
struct hl_eq_entry eq_entry;
};
/**
* hl_cq_inc_ptr - increment ci or pi of cq
*
* @ptr: the current ci or pi value of the completion queue
*
* Increment ptr by 1. If it reaches the number of completion queue
* entries, set it to 0
*/
inline u32 hl_cq_inc_ptr(u32 ptr)
{
ptr++;
if (unlikely(ptr == HL_CQ_LENGTH))
ptr = 0;
return ptr;
}
/**
* hl_eq_inc_ptr - increment ci of eq
*
* @ptr: the current ci value of the event queue
*
* Increment ptr by 1. If it reaches the number of event queue
* entries, set it to 0
*/
static inline u32 hl_eq_inc_ptr(u32 ptr)
{
ptr++;
if (unlikely(ptr == HL_EQ_LENGTH))
ptr = 0;
return ptr;
}
static void irq_handle_eqe(struct work_struct *work)
{
struct hl_eqe_work *eqe_work = container_of(work, struct hl_eqe_work,
eq_work);
struct hl_device *hdev = eqe_work->hdev;
hdev->asic_funcs->handle_eqe(hdev, &eqe_work->eq_entry);
kfree(eqe_work);
}
/**
* job_finish - queue job finish work
*
* @hdev: pointer to device structure
* @cs_seq: command submission sequence
* @cq: completion queue
* @timestamp: interrupt timestamp
*
*/
static void job_finish(struct hl_device *hdev, u32 cs_seq, struct hl_cq *cq, ktime_t timestamp)
{
struct hl_hw_queue *queue;
struct hl_cs_job *job;
queue = &hdev->kernel_queues[cq->hw_queue_id];
job = queue->shadow_queue[hl_pi_2_offset(cs_seq)];
job->timestamp = timestamp;
queue_work(hdev->cq_wq[cq->cq_idx], &job->finish_work);
atomic_inc(&queue->ci);
}
/**
* cs_finish - queue all cs jobs finish work
*
* @hdev: pointer to device structure
* @cs_seq: command submission sequence
* @timestamp: interrupt timestamp
*
*/
static void cs_finish(struct hl_device *hdev, u16 cs_seq, ktime_t timestamp)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_hw_queue *queue;
struct hl_cs *cs;
struct hl_cs_job *job;
cs = hdev->shadow_cs_queue[cs_seq & (prop->max_pending_cs - 1)];
if (!cs) {
dev_warn(hdev->dev,
"No pointer to CS in shadow array at index %d\n",
cs_seq);
return;
}
list_for_each_entry(job, &cs->job_list, cs_node) {
queue = &hdev->kernel_queues[job->hw_queue_id];
atomic_inc(&queue->ci);
}
cs->completion_timestamp = timestamp;
queue_work(hdev->cs_cmplt_wq, &cs->finish_work);
}
/**
* hl_irq_handler_cq - irq handler for completion queue
*
* @irq: irq number
* @arg: pointer to completion queue structure
*
*/
irqreturn_t hl_irq_handler_cq(int irq, void *arg)
{
struct hl_cq *cq = arg;
struct hl_device *hdev = cq->hdev;
bool shadow_index_valid, entry_ready;
u16 shadow_index;
struct hl_cq_entry *cq_entry, *cq_base;
ktime_t timestamp = ktime_get();
if (hdev->disabled) {
dev_dbg(hdev->dev,
"Device disabled but received IRQ %d for CQ %d\n",
irq, cq->hw_queue_id);
return IRQ_HANDLED;
}
cq_base = cq->kernel_address;
while (1) {
cq_entry = (struct hl_cq_entry *) &cq_base[cq->ci];
entry_ready = !!FIELD_GET(CQ_ENTRY_READY_MASK,
le32_to_cpu(cq_entry->data));
if (!entry_ready)
break;
/* Make sure we read CQ entry contents after we've
* checked the ownership bit.
*/
dma_rmb();
shadow_index_valid =
!!FIELD_GET(CQ_ENTRY_SHADOW_INDEX_VALID_MASK,
le32_to_cpu(cq_entry->data));
shadow_index = FIELD_GET(CQ_ENTRY_SHADOW_INDEX_MASK,
le32_to_cpu(cq_entry->data));
/*
* CQ interrupt handler has 2 modes of operation:
* 1. Interrupt per CS completion: (Single CQ for all queues)
* CQ entry represents a completed CS
*
* 2. Interrupt per CS job completion in queue: (CQ per queue)
* CQ entry represents a completed job in a certain queue
*/
if (shadow_index_valid && !hdev->disabled) {
if (hdev->asic_prop.completion_mode ==
HL_COMPLETION_MODE_CS)
cs_finish(hdev, shadow_index, timestamp);
else
job_finish(hdev, shadow_index, cq, timestamp);
}
/* Clear CQ entry ready bit */
cq_entry->data = cpu_to_le32(le32_to_cpu(cq_entry->data) &
~CQ_ENTRY_READY_MASK);
cq->ci = hl_cq_inc_ptr(cq->ci);
/* Increment free slots */
atomic_inc(&cq->free_slots_cnt);
}
return IRQ_HANDLED;
}
/*
* hl_ts_free_objects - handler of the free objects workqueue.
* This function should put refcount to objects that the registration node
* took refcount to them.
* @work: workqueue object pointer
*/
static void hl_ts_free_objects(struct work_struct *work)
{
struct timestamp_reg_work_obj *job =
container_of(work, struct timestamp_reg_work_obj, free_obj);
struct list_head *dynamic_alloc_free_list_head = job->dynamic_alloc_free_obj_head;
struct timestamp_reg_free_node *free_obj, *temp_free_obj;
struct list_head *free_list_head = job->free_obj_head;
struct hl_device *hdev = job->hdev;
list_for_each_entry_safe(free_obj, temp_free_obj, free_list_head, free_objects_node) {
dev_dbg(hdev->dev, "About to put refcount to buf (%p) cq_cb(%p)\n",
free_obj->buf,
free_obj->cq_cb);
hl_mmap_mem_buf_put(free_obj->buf);
hl_cb_put(free_obj->cq_cb);
atomic_set(&free_obj->in_use, 0);
}
kfree(free_list_head);
if (dynamic_alloc_free_list_head) {
list_for_each_entry_safe(free_obj, temp_free_obj, dynamic_alloc_free_list_head,
free_objects_node) {
dev_dbg(hdev->dev,
"Dynamic_Alloc list: About to put refcount to buf (%p) cq_cb(%p)\n",
free_obj->buf,
free_obj->cq_cb);
hl_mmap_mem_buf_put(free_obj->buf);
hl_cb_put(free_obj->cq_cb);
list_del(&free_obj->free_objects_node);
kfree(free_obj);
}
kfree(dynamic_alloc_free_list_head);
}
kfree(job);
}
/*
* This function called with spin_lock of wait_list_lock taken
* This function will set timestamp and delete the registration node from the
* wait_list_lock.
* and since we're protected with spin_lock here, so we cannot just put the refcount
* for the objects here, since the release function may be called and it's also a long
* logic (which might sleep also) that cannot be handled in irq context.
* so here we'll be filling a list with nodes of "put" jobs and then will send this
* list to a dedicated workqueue to do the actual put.
*/
static int handle_registration_node(struct hl_device *hdev, struct hl_user_pending_interrupt *pend,
struct list_head **free_list,
struct list_head **dynamic_alloc_list,
struct hl_user_interrupt *intr)
{
struct hl_ts_free_jobs *ts_free_jobs_data;
struct timestamp_reg_free_node *free_node;
u32 free_node_index;
u64 timestamp;
ts_free_jobs_data = &intr->ts_free_jobs_data;
free_node_index = ts_free_jobs_data->next_avail_free_node_idx;
if (!(*free_list)) {
/* Alloc/Init the timestamp registration free objects list */
*free_list = kmalloc_obj(struct list_head, GFP_ATOMIC);
if (!(*free_list))
return -ENOMEM;
INIT_LIST_HEAD(*free_list);
}
free_node = &ts_free_jobs_data->free_nodes_pool[free_node_index];
if (atomic_cmpxchg(&free_node->in_use, 0, 1)) {
dev_dbg(hdev->dev,
"Timestamp free node pool is full, buff: %p, record: %p, irq: %u\n",
pend->ts_reg_info.buf,
pend,
intr->interrupt_id);
if (!(*dynamic_alloc_list)) {
*dynamic_alloc_list = kmalloc_obj(struct list_head,
GFP_ATOMIC);
if (!(*dynamic_alloc_list))
return -ENOMEM;
INIT_LIST_HEAD(*dynamic_alloc_list);
}
free_node = kmalloc_obj(struct timestamp_reg_free_node,
GFP_ATOMIC);
if (!free_node)
return -ENOMEM;
free_node->dynamic_alloc = 1;
}
timestamp = ktime_to_ns(intr->timestamp);
*pend->ts_reg_info.timestamp_kernel_addr = timestamp;
dev_dbg(hdev->dev, "Irq handle: Timestamp record (%p) ts cb address (%p), interrupt_id: %u\n",
pend, pend->ts_reg_info.timestamp_kernel_addr, intr->interrupt_id);
list_del(&pend->list_node);
/* Putting the refcount for ts_buff and cq_cb objects will be handled
* in workqueue context, just add job to free_list.
*/
free_node->buf = pend->ts_reg_info.buf;
free_node->cq_cb = pend->ts_reg_info.cq_cb;
if (free_node->dynamic_alloc) {
list_add(&free_node->free_objects_node, *dynamic_alloc_list);
} else {
ts_free_jobs_data->next_avail_free_node_idx =
(++free_node_index) % ts_free_jobs_data->free_nodes_length;
list_add(&free_node->free_objects_node, *free_list);
}
/* Mark TS record as free */
pend->ts_reg_info.in_use = false;
return 0;
}
static void handle_user_interrupt_ts_list(struct hl_device *hdev, struct hl_user_interrupt *intr)
{
struct list_head *ts_reg_free_list_head = NULL, *dynamic_alloc_list_head = NULL;
struct hl_user_pending_interrupt *pend, *temp_pend;
struct timestamp_reg_work_obj *job;
bool reg_node_handle_fail = false;
unsigned long flags;
int rc;
/* For registration nodes:
* As part of handling the registration nodes, we should put refcount to
* some objects. the problem is that we cannot do that under spinlock
* or in irq handler context at all (since release functions are long and
* might sleep), so we will need to handle that part in workqueue context.
* To avoid handling kmalloc failure which compels us rolling back actions
* and move nodes hanged on the free list back to the interrupt ts list
* we always alloc the job of the WQ at the beginning.
*/
job = kmalloc_obj(*job, GFP_ATOMIC);
if (!job)
return;
spin_lock_irqsave(&intr->ts_list_lock, flags);
list_for_each_entry_safe(pend, temp_pend, &intr->ts_list_head, list_node) {
if ((pend->cq_kernel_addr && *(pend->cq_kernel_addr) >= pend->cq_target_value) ||
!pend->cq_kernel_addr) {
if (!reg_node_handle_fail) {
rc = handle_registration_node(hdev, pend,
&ts_reg_free_list_head,
&dynamic_alloc_list_head, intr);
if (rc)
reg_node_handle_fail = true;
}
}
}
spin_unlock_irqrestore(&intr->ts_list_lock, flags);
if (ts_reg_free_list_head) {
INIT_WORK(&job->free_obj, hl_ts_free_objects);
job->free_obj_head = ts_reg_free_list_head;
job->dynamic_alloc_free_obj_head = dynamic_alloc_list_head;
job->hdev = hdev;
queue_work(hdev->ts_free_obj_wq, &job->free_obj);
} else {
kfree(job);
}
}
static void handle_user_interrupt_wait_list(struct hl_device *hdev, struct hl_user_interrupt *intr)
{
struct hl_user_pending_interrupt *pend, *temp_pend;
unsigned long flags;
spin_lock_irqsave(&intr->wait_list_lock, flags);
list_for_each_entry_safe(pend, temp_pend, &intr->wait_list_head, list_node) {
if ((pend->cq_kernel_addr && *(pend->cq_kernel_addr) >= pend->cq_target_value) ||
!pend->cq_kernel_addr) {
/* Handle wait target value node */
pend->fence.timestamp = intr->timestamp;
complete_all(&pend->fence.completion);
}
}
spin_unlock_irqrestore(&intr->wait_list_lock, flags);
}
static void handle_tpc_interrupt(struct hl_device *hdev)
{
u64 event_mask;
u32 flags;
event_mask = HL_NOTIFIER_EVENT_TPC_ASSERT |
HL_NOTIFIER_EVENT_USER_ENGINE_ERR |
HL_NOTIFIER_EVENT_DEVICE_RESET;
flags = HL_DRV_RESET_DELAY;
dev_err_ratelimited(hdev->dev, "Received TPC assert\n");
hl_device_cond_reset(hdev, flags, event_mask);
}
static void handle_unexpected_user_interrupt(struct hl_device *hdev)
{
dev_err_ratelimited(hdev->dev, "Received unexpected user error interrupt\n");
}
/**
* hl_irq_user_interrupt_handler - irq handler for user interrupts.
*
* @irq: irq number
* @arg: pointer to user interrupt structure
*/
irqreturn_t hl_irq_user_interrupt_handler(int irq, void *arg)
{
struct hl_user_interrupt *user_int = arg;
struct hl_device *hdev = user_int->hdev;
user_int->timestamp = ktime_get();
switch (user_int->type) {
case HL_USR_INTERRUPT_CQ:
/* First handle user waiters threads */
handle_user_interrupt_wait_list(hdev, &hdev->common_user_cq_interrupt);
handle_user_interrupt_wait_list(hdev, user_int);
/* Second handle user timestamp registrations */
handle_user_interrupt_ts_list(hdev, &hdev->common_user_cq_interrupt);
handle_user_interrupt_ts_list(hdev, user_int);
break;
case HL_USR_INTERRUPT_DECODER:
handle_user_interrupt_wait_list(hdev, &hdev->common_decoder_interrupt);
/* Handle decoder interrupt registered on this specific irq */
handle_user_interrupt_wait_list(hdev, user_int);
break;
default:
break;
}
return IRQ_HANDLED;
}
/**
* hl_irq_user_interrupt_thread_handler - irq thread handler for user interrupts.
* This function is invoked by threaded irq mechanism
*
* @irq: irq number
* @arg: pointer to user interrupt structure
*
*/
irqreturn_t hl_irq_user_interrupt_thread_handler(int irq, void *arg)
{
struct hl_user_interrupt *user_int = arg;
struct hl_device *hdev = user_int->hdev;
user_int->timestamp = ktime_get();
switch (user_int->type) {
case HL_USR_INTERRUPT_TPC:
handle_tpc_interrupt(hdev);
break;
case HL_USR_INTERRUPT_UNEXPECTED:
handle_unexpected_user_interrupt(hdev);
break;
default:
break;
}
return IRQ_HANDLED;
}
irqreturn_t hl_irq_eq_error_interrupt_thread_handler(int irq, void *arg)
{
u64 event_mask = HL_NOTIFIER_EVENT_DEVICE_RESET | HL_NOTIFIER_EVENT_DEVICE_UNAVAILABLE;
struct hl_device *hdev = arg;
dev_err(hdev->dev, "EQ error interrupt received\n");
hl_device_cond_reset(hdev, HL_DRV_RESET_HARD, event_mask);
return IRQ_HANDLED;
}
/**
* hl_irq_handler_eq - irq handler for event queue
*
* @irq: irq number
* @arg: pointer to event queue structure
*
*/
irqreturn_t hl_irq_handler_eq(int irq, void *arg)
{
struct hl_eq *eq = arg;
struct hl_device *hdev = eq->hdev;
struct hl_eq_entry *eq_entry;
struct hl_eq_entry *eq_base;
struct hl_eqe_work *handle_eqe_work;
bool entry_ready;
u32 cur_eqe, ctl;
u16 cur_eqe_index, event_type;
eq_base = eq->kernel_address;
while (1) {
cur_eqe = le32_to_cpu(eq_base[eq->ci].hdr.ctl);
entry_ready = !!FIELD_GET(EQ_CTL_READY_MASK, cur_eqe);
if (!entry_ready)
break;
cur_eqe_index = FIELD_GET(EQ_CTL_INDEX_MASK, cur_eqe);
if ((hdev->event_queue.check_eqe_index) &&
(((eq->prev_eqe_index + 1) & EQ_CTL_INDEX_MASK) != cur_eqe_index)) {
dev_err(hdev->dev,
"EQE %#x in queue is ready but index does not match %d!=%d",
cur_eqe,
((eq->prev_eqe_index + 1) & EQ_CTL_INDEX_MASK),
cur_eqe_index);
break;
}
eq->prev_eqe_index++;
eq_entry = &eq_base[eq->ci];
/*
* Make sure we read EQ entry contents after we've
* checked the ownership bit.
*/
dma_rmb();
if (hdev->disabled && !hdev->reset_info.in_compute_reset) {
ctl = le32_to_cpu(eq_entry->hdr.ctl);
event_type = ((ctl & EQ_CTL_EVENT_TYPE_MASK) >> EQ_CTL_EVENT_TYPE_SHIFT);
dev_warn(hdev->dev,
"Device disabled but received an EQ event (%u)\n", event_type);
goto skip_irq;
}
handle_eqe_work = kmalloc_obj(*handle_eqe_work, GFP_ATOMIC);
if (handle_eqe_work) {
INIT_WORK(&handle_eqe_work->eq_work, irq_handle_eqe);
handle_eqe_work->hdev = hdev;
memcpy(&handle_eqe_work->eq_entry, eq_entry,
sizeof(*eq_entry));
queue_work(hdev->eq_wq, &handle_eqe_work->eq_work);
}
skip_irq:
/* Clear EQ entry ready bit */
eq_entry->hdr.ctl =
cpu_to_le32(le32_to_cpu(eq_entry->hdr.ctl) &
~EQ_CTL_READY_MASK);
eq->ci = hl_eq_inc_ptr(eq->ci);
hdev->asic_funcs->update_eq_ci(hdev, eq->ci);
}
return IRQ_HANDLED;
}
/**
* hl_irq_handler_dec_abnrm - Decoder error interrupt handler
* @irq: IRQ number
* @arg: pointer to decoder structure.
*/
irqreturn_t hl_irq_handler_dec_abnrm(int irq, void *arg)
{
struct hl_dec *dec = arg;
schedule_work(&dec->abnrm_intr_work);
return IRQ_HANDLED;
}
/**
* hl_cq_init - main initialization function for an cq object
*
* @hdev: pointer to device structure
* @q: pointer to cq structure
* @hw_queue_id: The H/W queue ID this completion queue belongs to
* HL_INVALID_QUEUE if cq is not attached to any specific queue
*
* Allocate dma-able memory for the completion queue and initialize fields
* Returns 0 on success
*/
int hl_cq_init(struct hl_device *hdev, struct hl_cq *q, u32 hw_queue_id)
{
void *p;
p = hl_asic_dma_alloc_coherent(hdev, HL_CQ_SIZE_IN_BYTES, &q->bus_address,
GFP_KERNEL | __GFP_ZERO);
if (!p)
return -ENOMEM;
q->hdev = hdev;
q->kernel_address = p;
q->hw_queue_id = hw_queue_id;
q->ci = 0;
q->pi = 0;
atomic_set(&q->free_slots_cnt, HL_CQ_LENGTH);
return 0;
}
/**
* hl_cq_fini - destroy completion queue
*
* @hdev: pointer to device structure
* @q: pointer to cq structure
*
* Free the completion queue memory
*/
void hl_cq_fini(struct hl_device *hdev, struct hl_cq *q)
{
hl_asic_dma_free_coherent(hdev, HL_CQ_SIZE_IN_BYTES, q->kernel_address, q->bus_address);
}
void hl_cq_reset(struct hl_device *hdev, struct hl_cq *q)
{
q->ci = 0;
q->pi = 0;
atomic_set(&q->free_slots_cnt, HL_CQ_LENGTH);
/*
* It's not enough to just reset the PI/CI because the H/W may have
* written valid completion entries before it was halted and therefore
* we need to clean the actual queues so we won't process old entries
* when the device is operational again
*/
memset(q->kernel_address, 0, HL_CQ_SIZE_IN_BYTES);
}
/**
* hl_eq_init - main initialization function for an event queue object
*
* @hdev: pointer to device structure
* @q: pointer to eq structure
*
* Allocate dma-able memory for the event queue and initialize fields
* Returns 0 on success
*/
int hl_eq_init(struct hl_device *hdev, struct hl_eq *q)
{
u32 size = hdev->asic_prop.fw_event_queue_size ? : HL_EQ_SIZE_IN_BYTES;
void *p;
p = hl_cpu_accessible_dma_pool_alloc(hdev, size, &q->bus_address);
if (!p)
return -ENOMEM;
q->hdev = hdev;
q->kernel_address = p;
q->size = size;
q->ci = 0;
q->prev_eqe_index = 0;
return 0;
}
/**
* hl_eq_fini - destroy event queue
*
* @hdev: pointer to device structure
* @q: pointer to eq structure
*
* Free the event queue memory
*/
void hl_eq_fini(struct hl_device *hdev, struct hl_eq *q)
{
flush_workqueue(hdev->eq_wq);
hl_cpu_accessible_dma_pool_free(hdev, q->size, q->kernel_address);
}
void hl_eq_reset(struct hl_device *hdev, struct hl_eq *q)
{
q->ci = 0;
q->prev_eqe_index = 0;
/*
* It's not enough to just reset the PI/CI because the H/W may have
* written valid completion entries before it was halted and therefore
* we need to clean the actual queues so we won't process old entries
* when the device is operational again
*/
memset(q->kernel_address, 0, q->size);
}
void hl_eq_dump(struct hl_device *hdev, struct hl_eq *q)
{
u32 eq_length, eqe_size, ctl, ready, mode, type, index;
struct hl_eq_header *hdr;
u8 *ptr;
int i;
eq_length = HL_EQ_LENGTH;
eqe_size = q->size / HL_EQ_LENGTH;
dev_info(hdev->dev, "Contents of EQ entries headers:\n");
for (i = 0, ptr = q->kernel_address ; i < eq_length ; ++i, ptr += eqe_size) {
hdr = (struct hl_eq_header *) ptr;
ctl = le32_to_cpu(hdr->ctl);
ready = FIELD_GET(EQ_CTL_READY_MASK, ctl);
mode = FIELD_GET(EQ_CTL_EVENT_MODE_MASK, ctl);
type = FIELD_GET(EQ_CTL_EVENT_TYPE_MASK, ctl);
index = FIELD_GET(EQ_CTL_INDEX_MASK, ctl);
dev_info(hdev->dev, "%02u: %#010x [ready: %u, mode %u, type %04u, index %05u]\n",
i, ctl, ready, mode, type, index);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2022 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
/**
* hl_mmap_mem_buf_get - increase the buffer refcount and return a pointer to
* the buffer descriptor.
*
* @mmg: parent unified memory manager
* @handle: requested buffer handle
*
* Find the buffer in the store and return a pointer to its descriptor.
* Increase buffer refcount. If not found - return NULL.
*/
struct hl_mmap_mem_buf *hl_mmap_mem_buf_get(struct hl_mem_mgr *mmg, u64 handle)
{
struct hl_mmap_mem_buf *buf;
spin_lock(&mmg->lock);
buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
if (!buf) {
spin_unlock(&mmg->lock);
dev_dbg(mmg->dev, "Buff get failed, no match to handle %#llx\n", handle);
return NULL;
}
kref_get(&buf->refcount);
spin_unlock(&mmg->lock);
return buf;
}
/**
* hl_mmap_mem_buf_destroy - destroy the unused buffer
*
* @buf: memory manager buffer descriptor
*
* Internal function, used as a final step of buffer release. Shall be invoked
* only when the buffer is no longer in use (removed from idr). Will call the
* release callback (if applicable), and free the memory.
*/
static void hl_mmap_mem_buf_destroy(struct hl_mmap_mem_buf *buf)
{
if (buf->behavior->release)
buf->behavior->release(buf);
kfree(buf);
}
/**
* hl_mmap_mem_buf_release - release buffer
*
* @kref: kref that reached 0.
*
* Internal function, used as a kref release callback, when the last user of
* the buffer is released. Shall be called from an interrupt context.
*/
static void hl_mmap_mem_buf_release(struct kref *kref)
{
struct hl_mmap_mem_buf *buf =
container_of(kref, struct hl_mmap_mem_buf, refcount);
spin_lock(&buf->mmg->lock);
idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
spin_unlock(&buf->mmg->lock);
hl_mmap_mem_buf_destroy(buf);
}
/**
* hl_mmap_mem_buf_remove_idr_locked - remove handle from idr
*
* @kref: kref that reached 0.
*
* Internal function, used for kref put by handle. Assumes mmg lock is taken.
* Will remove the buffer from idr, without destroying it.
*/
static void hl_mmap_mem_buf_remove_idr_locked(struct kref *kref)
{
struct hl_mmap_mem_buf *buf =
container_of(kref, struct hl_mmap_mem_buf, refcount);
idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
}
/**
* hl_mmap_mem_buf_put - decrease the reference to the buffer
*
* @buf: memory manager buffer descriptor
*
* Decrease the reference to the buffer, and release it if it was the last one.
* Shall be called from an interrupt context.
*/
int hl_mmap_mem_buf_put(struct hl_mmap_mem_buf *buf)
{
return kref_put(&buf->refcount, hl_mmap_mem_buf_release);
}
/**
* hl_mmap_mem_buf_put_handle - decrease the reference to the buffer with the
* given handle.
*
* @mmg: parent unified memory manager
* @handle: requested buffer handle
*
* Decrease the reference to the buffer, and release it if it was the last one.
* Shall not be called from an interrupt context. Return -EINVAL if handle was
* not found, else return the put outcome (0 or 1).
*/
int hl_mmap_mem_buf_put_handle(struct hl_mem_mgr *mmg, u64 handle)
{
struct hl_mmap_mem_buf *buf;
spin_lock(&mmg->lock);
buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
if (!buf) {
spin_unlock(&mmg->lock);
dev_dbg(mmg->dev,
"Buff put failed, no match to handle %#llx\n", handle);
return -EINVAL;
}
if (kref_put(&buf->refcount, hl_mmap_mem_buf_remove_idr_locked)) {
spin_unlock(&mmg->lock);
hl_mmap_mem_buf_destroy(buf);
return 1;
}
spin_unlock(&mmg->lock);
return 0;
}
/**
* hl_mmap_mem_buf_alloc - allocate a new mappable buffer
*
* @mmg: parent unified memory manager
* @behavior: behavior object describing this buffer polymorphic behavior
* @gfp: gfp flags to use for the memory allocations
* @args: additional args passed to behavior->alloc
*
* Allocate and register a new memory buffer inside the give memory manager.
* Return the pointer to the new buffer on success or NULL on failure.
*/
struct hl_mmap_mem_buf *
hl_mmap_mem_buf_alloc(struct hl_mem_mgr *mmg,
struct hl_mmap_mem_buf_behavior *behavior, gfp_t gfp,
void *args)
{
struct hl_mmap_mem_buf *buf;
int rc;
buf = kzalloc_obj(*buf, gfp);
if (!buf)
return NULL;
spin_lock(&mmg->lock);
rc = idr_alloc(&mmg->handles, buf, 1, 0, GFP_ATOMIC);
spin_unlock(&mmg->lock);
if (rc < 0) {
dev_err(mmg->dev,
"%s: Failed to allocate IDR for a new buffer, rc=%d\n",
behavior->topic, rc);
goto free_buf;
}
buf->mmg = mmg;
buf->behavior = behavior;
buf->handle = (((u64)rc | buf->behavior->mem_id) << PAGE_SHIFT);
kref_init(&buf->refcount);
rc = buf->behavior->alloc(buf, gfp, args);
if (rc) {
dev_err(mmg->dev, "%s: Failure in buffer alloc callback %d\n",
behavior->topic, rc);
goto remove_idr;
}
return buf;
remove_idr:
spin_lock(&mmg->lock);
idr_remove(&mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
spin_unlock(&mmg->lock);
free_buf:
kfree(buf);
return NULL;
}
/**
* hl_mmap_mem_buf_vm_close - handle mmap close
*
* @vma: the vma object for which mmap was closed.
*
* Put the memory buffer if it is no longer mapped.
*/
static void hl_mmap_mem_buf_vm_close(struct vm_area_struct *vma)
{
struct hl_mmap_mem_buf *buf =
(struct hl_mmap_mem_buf *)vma->vm_private_data;
long new_mmap_size;
new_mmap_size = buf->real_mapped_size - (vma->vm_end - vma->vm_start);
if (new_mmap_size > 0) {
buf->real_mapped_size = new_mmap_size;
return;
}
atomic_set(&buf->mmap, 0);
hl_mmap_mem_buf_put(buf);
vma->vm_private_data = NULL;
}
static const struct vm_operations_struct hl_mmap_mem_buf_vm_ops = {
.close = hl_mmap_mem_buf_vm_close
};
/**
* hl_mem_mgr_mmap - map the given buffer to the user
*
* @mmg: unified memory manager
* @vma: the vma object for which mmap was closed.
* @args: additional args passed to behavior->mmap
*
* Map the buffer specified by the vma->vm_pgoff to the given vma.
*/
int hl_mem_mgr_mmap(struct hl_mem_mgr *mmg, struct vm_area_struct *vma,
void *args)
{
struct hl_mmap_mem_buf *buf;
u64 user_mem_size;
u64 handle;
int rc;
/* We use the page offset to hold the idr and thus we need to clear
* it before doing the mmap itself
*/
handle = vma->vm_pgoff << PAGE_SHIFT;
vma->vm_pgoff = 0;
/* Reference was taken here */
buf = hl_mmap_mem_buf_get(mmg, handle);
if (!buf) {
dev_err(mmg->dev,
"Memory mmap failed, no match to handle %#llx\n", handle);
return -EINVAL;
}
/* Validation check */
user_mem_size = vma->vm_end - vma->vm_start;
if (user_mem_size != ALIGN(buf->mappable_size, PAGE_SIZE)) {
dev_err(mmg->dev,
"%s: Memory mmap failed, mmap VM size 0x%llx != 0x%llx allocated physical mem size\n",
buf->behavior->topic, user_mem_size, buf->mappable_size);
rc = -EINVAL;
goto put_mem;
}
if (!access_ok((void __user *)(uintptr_t)vma->vm_start,
user_mem_size)) {
dev_err(mmg->dev, "%s: User pointer is invalid - 0x%lx\n",
buf->behavior->topic, vma->vm_start);
rc = -EINVAL;
goto put_mem;
}
if (atomic_cmpxchg(&buf->mmap, 0, 1)) {
dev_err(mmg->dev,
"%s, Memory mmap failed, already mapped to user\n",
buf->behavior->topic);
rc = -EINVAL;
goto put_mem;
}
vma->vm_ops = &hl_mmap_mem_buf_vm_ops;
/* Note: We're transferring the memory reference to vma->vm_private_data here. */
vma->vm_private_data = buf;
rc = buf->behavior->mmap(buf, vma, args);
if (rc) {
atomic_set(&buf->mmap, 0);
goto put_mem;
}
buf->real_mapped_size = buf->mappable_size;
vma->vm_pgoff = handle >> PAGE_SHIFT;
return 0;
put_mem:
hl_mmap_mem_buf_put(buf);
return rc;
}
/**
* hl_mem_mgr_init - initialize unified memory manager
*
* @dev: owner device pointer
* @mmg: structure to initialize
*
* Initialize an instance of unified memory manager
*/
void hl_mem_mgr_init(struct device *dev, struct hl_mem_mgr *mmg)
{
mmg->dev = dev;
spin_lock_init(&mmg->lock);
idr_init(&mmg->handles);
}
static void hl_mem_mgr_fini_stats_reset(struct hl_mem_mgr_fini_stats *stats)
{
if (!stats)
return;
memset(stats, 0, sizeof(*stats));
}
static void hl_mem_mgr_fini_stats_inc(u64 mem_id, struct hl_mem_mgr_fini_stats *stats)
{
if (!stats)
return;
switch (mem_id) {
case HL_MMAP_TYPE_CB:
++stats->n_busy_cb;
break;
case HL_MMAP_TYPE_TS_BUFF:
++stats->n_busy_ts;
break;
default:
/* we currently store only CB/TS so this shouldn't happen */
++stats->n_busy_other;
}
}
/**
* hl_mem_mgr_fini - release unified memory manager
*
* @mmg: parent unified memory manager
* @stats: if non-NULL, will return some counters for handles that could not be removed.
*
* Release the unified memory manager. Shall be called from an interrupt context.
*/
void hl_mem_mgr_fini(struct hl_mem_mgr *mmg, struct hl_mem_mgr_fini_stats *stats)
{
struct hl_mmap_mem_buf *buf;
struct idr *idp;
const char *topic;
u64 mem_id;
u32 id;
hl_mem_mgr_fini_stats_reset(stats);
idp = &mmg->handles;
idr_for_each_entry(idp, buf, id) {
topic = buf->behavior->topic;
mem_id = buf->behavior->mem_id;
if (hl_mmap_mem_buf_put(buf) != 1) {
dev_err(mmg->dev,
"%s: Buff handle %u for CTX is still alive\n",
topic, id);
hl_mem_mgr_fini_stats_inc(mem_id, stats);
}
}
}
/**
* hl_mem_mgr_idr_destroy() - destroy memory manager IDR.
* @mmg: parent unified memory manager
*
* Destroy the memory manager IDR.
* Shall be called when IDR is empty and no memory buffers are in use.
*/
void hl_mem_mgr_idr_destroy(struct hl_mem_mgr *mmg)
{
if (!idr_is_empty(&mmg->handles))
dev_crit(mmg->dev, "memory manager IDR is destroyed while it is not empty!\n");
idr_destroy(&mmg->handles);
}
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
HL_COMMON_MMU_FILES := common/mmu/mmu.o common/mmu/mmu_v1.o \
common/mmu/mmu_v2.o common/mmu/mmu_v2_hr.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,550 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2019 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "../habanalabs.h"
#include "../../include/hw_ip/mmu/mmu_general.h"
#include <linux/slab.h>
#define MMU_V1_MAX_HOPS (MMU_HOP4 + 1)
static inline u64 get_hop_pte_addr(struct hl_ctx *ctx, struct hl_mmu_properties *mmu_prop,
u64 *hop_addr_arr, u64 virt_addr, enum mmu_hop_num hop_idx)
{
u64 mask, shift;
mask = mmu_prop->hop_masks[hop_idx];
shift = mmu_prop->hop_shifts[hop_idx];
return hop_addr_arr[hop_idx] +
ctx->hdev->asic_prop.mmu_pte_size * ((virt_addr & mask) >> shift);
}
static int dram_default_mapping_init(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
u64 num_of_hop3, total_hops, hop0_addr, hop1_addr, hop2_addr,
hop2_pte_addr, hop3_pte_addr, pte_val;
int rc, i, j, hop3_allocated = 0;
if ((!prop->dram_supports_virtual_memory) ||
(!hdev->dram_default_page_mapping) ||
(ctx->asid == HL_KERNEL_ASID_ID))
return 0;
num_of_hop3 = prop->dram_size_for_default_page_mapping;
do_div(num_of_hop3, prop->dram_page_size);
do_div(num_of_hop3, HOP_PTE_ENTRIES_512);
/* add hop1 and hop2 */
total_hops = num_of_hop3 + 2;
ctx->dram_default_hops = kcalloc(total_hops, HL_PTE_SIZE, GFP_KERNEL);
if (!ctx->dram_default_hops)
return -ENOMEM;
hop0_addr = hl_mmu_dr_get_hop0_addr(ctx);
hop1_addr = hl_mmu_dr_alloc_hop(ctx);
if (hop1_addr == ULLONG_MAX) {
dev_err(hdev->dev, "failed to alloc hop 1\n");
rc = -ENOMEM;
goto hop1_err;
}
ctx->dram_default_hops[total_hops - 1] = hop1_addr;
hop2_addr = hl_mmu_dr_alloc_hop(ctx);
if (hop2_addr == ULLONG_MAX) {
dev_err(hdev->dev, "failed to alloc hop 2\n");
rc = -ENOMEM;
goto hop2_err;
}
ctx->dram_default_hops[total_hops - 2] = hop2_addr;
for (i = 0 ; i < num_of_hop3 ; i++) {
ctx->dram_default_hops[i] = hl_mmu_dr_alloc_hop(ctx);
if (ctx->dram_default_hops[i] == ULLONG_MAX) {
dev_err(hdev->dev, "failed to alloc hop 3, i: %d\n", i);
rc = -ENOMEM;
goto hop3_err;
}
hop3_allocated++;
}
/* need only pte 0 in hops 0 and 1 */
pte_val = (hop1_addr & HOP_PHYS_ADDR_MASK) | PAGE_PRESENT_MASK;
hl_mmu_dr_write_pte(ctx, hop0_addr, pte_val);
pte_val = (hop2_addr & HOP_PHYS_ADDR_MASK) | PAGE_PRESENT_MASK;
hl_mmu_dr_write_pte(ctx, hop1_addr, pte_val);
hl_mmu_dr_get_pte(ctx, hop1_addr);
hop2_pte_addr = hop2_addr;
for (i = 0 ; i < num_of_hop3 ; i++) {
pte_val = (ctx->dram_default_hops[i] & HOP_PHYS_ADDR_MASK) |
PAGE_PRESENT_MASK;
hl_mmu_dr_write_pte(ctx, hop2_pte_addr, pte_val);
hl_mmu_dr_get_pte(ctx, hop2_addr);
hop2_pte_addr += HL_PTE_SIZE;
}
pte_val = (prop->mmu_dram_default_page_addr & HOP_PHYS_ADDR_MASK) |
LAST_MASK | PAGE_PRESENT_MASK;
for (i = 0 ; i < num_of_hop3 ; i++) {
hop3_pte_addr = ctx->dram_default_hops[i];
for (j = 0 ; j < HOP_PTE_ENTRIES_512 ; j++) {
hl_mmu_dr_write_final_pte(ctx, hop3_pte_addr, pte_val);
hl_mmu_dr_get_pte(ctx, ctx->dram_default_hops[i]);
hop3_pte_addr += HL_PTE_SIZE;
}
}
hl_mmu_dr_flush(ctx);
return 0;
hop3_err:
for (i = 0 ; i < hop3_allocated ; i++)
hl_mmu_dr_free_hop(ctx, ctx->dram_default_hops[i]);
hl_mmu_dr_free_hop(ctx, hop2_addr);
hop2_err:
hl_mmu_dr_free_hop(ctx, hop1_addr);
hop1_err:
kfree(ctx->dram_default_hops);
return rc;
}
static void dram_default_mapping_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
u64 num_of_hop3, total_hops, hop0_addr, hop1_addr, hop2_addr,
hop2_pte_addr, hop3_pte_addr;
int i, j;
if ((!prop->dram_supports_virtual_memory) ||
(!hdev->dram_default_page_mapping) ||
(ctx->asid == HL_KERNEL_ASID_ID))
return;
num_of_hop3 = prop->dram_size_for_default_page_mapping;
do_div(num_of_hop3, prop->dram_page_size);
do_div(num_of_hop3, HOP_PTE_ENTRIES_512);
hop0_addr = hl_mmu_dr_get_hop0_addr(ctx);
/* add hop1 and hop2 */
total_hops = num_of_hop3 + 2;
hop1_addr = ctx->dram_default_hops[total_hops - 1];
hop2_addr = ctx->dram_default_hops[total_hops - 2];
for (i = 0 ; i < num_of_hop3 ; i++) {
hop3_pte_addr = ctx->dram_default_hops[i];
for (j = 0 ; j < HOP_PTE_ENTRIES_512 ; j++) {
hl_mmu_dr_clear_pte(ctx, hop3_pte_addr);
hl_mmu_dr_put_pte(ctx, ctx->dram_default_hops[i]);
hop3_pte_addr += HL_PTE_SIZE;
}
}
hop2_pte_addr = hop2_addr;
for (i = 0 ; i < num_of_hop3 ; i++) {
hl_mmu_dr_clear_pte(ctx, hop2_pte_addr);
hl_mmu_dr_put_pte(ctx, hop2_addr);
hop2_pte_addr += HL_PTE_SIZE;
}
hl_mmu_dr_clear_pte(ctx, hop1_addr);
hl_mmu_dr_put_pte(ctx, hop1_addr);
hl_mmu_dr_clear_pte(ctx, hop0_addr);
kfree(ctx->dram_default_hops);
hl_mmu_dr_flush(ctx);
}
/**
* hl_mmu_v1_ctx_init() - initialize a context for using the MMU module.
* @ctx: pointer to the context structure to initialize.
*
* Initialize a mutex to protect the concurrent mapping flow, a hash to hold all
* page tables hops related to this context.
* Return: 0 on success, non-zero otherwise.
*/
static int hl_mmu_v1_ctx_init(struct hl_ctx *ctx)
{
hash_init(ctx->mmu_shadow_hash);
return dram_default_mapping_init(ctx);
}
/*
* hl_mmu_ctx_fini - disable a ctx from using the mmu module
*
* @ctx: pointer to the context structure
*
* This function does the following:
* - Free any pgts which were not freed yet
* - Free the mutex
* - Free DRAM default page mapping hops
*/
static void hl_mmu_v1_ctx_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct pgt_info *pgt_info;
struct hlist_node *tmp;
int i;
dram_default_mapping_fini(ctx);
if (!hash_empty(ctx->mmu_shadow_hash))
dev_err(hdev->dev, "ctx %d is freed while it has pgts in use\n",
ctx->asid);
hash_for_each_safe(ctx->mmu_shadow_hash, i, tmp, pgt_info, node) {
dev_err_ratelimited(hdev->dev,
"pgt_info of addr 0x%llx of asid %d was not destroyed, num_ptes: %d\n",
pgt_info->phys_addr, ctx->asid, pgt_info->num_of_ptes);
hl_mmu_dr_free_pgt_node(ctx, pgt_info);
}
}
static int hl_mmu_v1_unmap(struct hl_ctx *ctx,
u64 virt_addr, bool is_dram_addr)
{
u64 hop_addr[MMU_V1_MAX_HOPS] = {0}, hop_pte_addr[MMU_V1_MAX_HOPS] = {0}, curr_pte = 0;
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_mmu_properties *mmu_prop;
bool is_huge, clear_hop3 = true;
int hop_idx;
/* shifts and masks are the same in PMMU and HPMMU, use one of them */
mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu;
for (hop_idx = MMU_HOP0; hop_idx < MMU_HOP4; hop_idx++) {
if (hop_idx == MMU_HOP0) {
hop_addr[hop_idx] = hl_mmu_dr_get_hop0_addr(ctx);
} else {
hop_addr[hop_idx] = hl_mmu_get_next_hop_addr(ctx, curr_pte);
if (hop_addr[hop_idx] == ULLONG_MAX)
goto not_mapped;
}
hop_pte_addr[hop_idx] =
get_hop_pte_addr(ctx, mmu_prop, hop_addr, virt_addr, hop_idx);
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[hop_idx];
}
is_huge = curr_pte & mmu_prop->last_mask;
if (is_dram_addr && !is_huge) {
dev_err(hdev->dev, "DRAM unmapping should use huge pages only\n");
return -EFAULT;
}
if (!is_huge) {
hop_idx = MMU_HOP4;
hop_addr[hop_idx] = hl_mmu_get_next_hop_addr(ctx, curr_pte);
if (hop_addr[hop_idx] == ULLONG_MAX)
goto not_mapped;
hop_pte_addr[hop_idx] =
get_hop_pte_addr(ctx, mmu_prop, hop_addr, virt_addr, hop_idx);
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[hop_idx];
clear_hop3 = false;
}
if (hdev->dram_default_page_mapping && is_dram_addr) {
u64 default_pte = (prop->mmu_dram_default_page_addr &
HOP_PHYS_ADDR_MASK) | mmu_prop->last_mask |
PAGE_PRESENT_MASK;
if (curr_pte == default_pte) {
dev_err(hdev->dev,
"DRAM: hop3 PTE points to zero page, can't unmap, va: 0x%llx\n",
virt_addr);
goto not_mapped;
}
if (!(curr_pte & PAGE_PRESENT_MASK)) {
dev_err(hdev->dev,
"DRAM: hop3 PTE is cleared! can't unmap, va: 0x%llx\n",
virt_addr);
goto not_mapped;
}
hop_idx = MMU_HOP3;
hl_mmu_dr_write_final_pte(ctx, hop_pte_addr[hop_idx], default_pte);
hl_mmu_dr_put_pte(ctx, hop_addr[hop_idx]);
} else {
if (!(curr_pte & PAGE_PRESENT_MASK))
goto not_mapped;
if (hop_addr[MMU_HOP4])
hl_mmu_dr_clear_pte(ctx, hop_pte_addr[MMU_HOP4]);
else
hl_mmu_dr_clear_pte(ctx, hop_pte_addr[MMU_HOP3]);
if (hop_addr[MMU_HOP4] && !hl_mmu_dr_put_pte(ctx, hop_addr[MMU_HOP4]))
clear_hop3 = true;
if (!clear_hop3)
goto mapped;
for (hop_idx = MMU_HOP3; hop_idx >= 0; hop_idx--) {
hl_mmu_dr_clear_pte(ctx, hop_pte_addr[hop_idx]);
if (hop_idx == MMU_HOP0)
break;
if (hl_mmu_dr_put_pte(ctx, hop_addr[hop_idx]))
goto mapped;
}
}
mapped:
return 0;
not_mapped:
dev_err(hdev->dev, "virt addr 0x%llx is not mapped to phys addr\n",
virt_addr);
return -EINVAL;
}
static int hl_mmu_v1_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr,
u32 page_size, bool is_dram_addr)
{
u64 hop_addr[MMU_V1_MAX_HOPS] = {0}, hop_pte_addr[MMU_V1_MAX_HOPS] = {0}, curr_pte = 0;
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_mmu_properties *mmu_prop;
bool is_huge, hop_new[MMU_V1_MAX_HOPS] = {false};
int num_hops, hop_idx, prev_hop, rc = -ENOMEM;
/*
* This mapping function can map a page or a huge page. For huge page
* there are only 3 hops rather than 4. Currently the DRAM allocation
* uses huge pages only but user memory could have been allocated with
* one of the two page sizes. Since this is a common code for all the
* three cases, we need this hugs page check.
*/
if (is_dram_addr) {
mmu_prop = &prop->dmmu;
is_huge = true;
} else if (page_size == prop->pmmu_huge.page_size) {
mmu_prop = &prop->pmmu_huge;
is_huge = true;
} else {
mmu_prop = &prop->pmmu;
is_huge = false;
}
num_hops = is_huge ? (MMU_V1_MAX_HOPS - 1) : MMU_V1_MAX_HOPS;
for (hop_idx = MMU_HOP0; hop_idx < num_hops; hop_idx++) {
if (hop_idx == MMU_HOP0) {
hop_addr[hop_idx] = hl_mmu_dr_get_hop0_addr(ctx);
} else {
hop_addr[hop_idx] =
hl_mmu_dr_get_alloc_next_hop_addr(ctx, curr_pte, &hop_new[hop_idx]);
if (hop_addr[hop_idx] == ULLONG_MAX)
goto err;
}
hop_pte_addr[hop_idx] =
get_hop_pte_addr(ctx, mmu_prop, hop_addr, virt_addr, hop_idx);
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[hop_idx];
}
if (hdev->dram_default_page_mapping && is_dram_addr) {
u64 default_pte = (prop->mmu_dram_default_page_addr &
HOP_PHYS_ADDR_MASK) | mmu_prop->last_mask |
PAGE_PRESENT_MASK;
if (curr_pte != default_pte) {
dev_err(hdev->dev,
"DRAM: mapping already exists for virt_addr 0x%llx\n",
virt_addr);
rc = -EINVAL;
goto err;
}
for (hop_idx = MMU_HOP1; hop_idx < num_hops; hop_idx++) {
if (hop_new[hop_idx]) {
dev_err(hdev->dev, "DRAM mapping should not allocate more hops\n");
rc = -EFAULT;
goto err;
}
}
} else if (curr_pte & PAGE_PRESENT_MASK) {
dev_err(hdev->dev,
"mapping already exists for virt_addr 0x%llx\n",
virt_addr);
for (hop_idx = MMU_HOP0; hop_idx < num_hops; hop_idx++)
dev_dbg(hdev->dev, "hop%d pte: 0x%llx (0x%llx)\n", hop_idx,
*(u64 *) (uintptr_t) hop_pte_addr[hop_idx],
hop_pte_addr[hop_idx]);
rc = -EINVAL;
goto err;
}
curr_pte = (phys_addr & HOP_PHYS_ADDR_MASK) | mmu_prop->last_mask
| PAGE_PRESENT_MASK;
hl_mmu_dr_write_final_pte(ctx, hop_pte_addr[num_hops - 1], curr_pte);
for (hop_idx = MMU_HOP1; hop_idx < num_hops; hop_idx++) {
prev_hop = hop_idx - 1;
if (hop_new[hop_idx]) {
curr_pte = (hop_addr[hop_idx] & HOP_PHYS_ADDR_MASK) | PAGE_PRESENT_MASK;
hl_mmu_dr_write_pte(ctx, hop_pte_addr[prev_hop], curr_pte);
if (hop_idx != MMU_HOP1)
hl_mmu_dr_get_pte(ctx, hop_addr[prev_hop]);
}
}
hl_mmu_dr_get_pte(ctx, hop_addr[num_hops - 1]);
return 0;
err:
for (hop_idx = num_hops; hop_idx > MMU_HOP0; hop_idx--) {
if (hop_new[hop_idx])
hl_mmu_dr_free_hop(ctx, hop_addr[hop_idx]);
}
return rc;
}
/*
* hl_mmu_v1_swap_out - marks all mapping of the given ctx as swapped out
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v1_swap_out(struct hl_ctx *ctx)
{
}
/*
* hl_mmu_v1_swap_in - marks all mapping of the given ctx as swapped in
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v1_swap_in(struct hl_ctx *ctx)
{
}
static int hl_mmu_v1_get_tlb_info(struct hl_ctx *ctx, u64 virt_addr,
struct hl_mmu_hop_info *hops)
{
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_mmu_properties *mmu_prop;
bool is_dram_addr, is_pmmu_addr, is_pmmu_h_addr, is_huge;
int i, used_hops;
is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size,
prop->dmmu.start_addr,
prop->dmmu.end_addr);
is_pmmu_addr = hl_mem_area_inside_range(virt_addr, prop->pmmu.page_size,
prop->pmmu.start_addr,
prop->pmmu.end_addr);
is_pmmu_h_addr = hl_mem_area_inside_range(virt_addr,
prop->pmmu_huge.page_size,
prop->pmmu_huge.start_addr,
prop->pmmu_huge.end_addr);
if (is_dram_addr) {
mmu_prop = &prop->dmmu;
is_huge = true;
} else if (is_pmmu_addr) {
mmu_prop = &prop->pmmu;
is_huge = false;
} else if (is_pmmu_h_addr) {
mmu_prop = &prop->pmmu_huge;
is_huge = true;
} else {
return -EINVAL;
}
used_hops = mmu_prop->num_hops;
/* huge pages use lesser hops */
if (is_huge)
used_hops--;
hops->hop_info[0].hop_addr = hl_mmu_dr_get_phys_hop0_addr(ctx);
hops->hop_info[0].hop_pte_addr =
hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, 0,
hops->hop_info[0].hop_addr, virt_addr);
hops->hop_info[0].hop_pte_val =
hdev->asic_funcs->read_pte(hdev,
hops->hop_info[0].hop_pte_addr);
for (i = 1 ; i < used_hops ; i++) {
hops->hop_info[i].hop_addr =
hl_mmu_get_next_hop_addr(ctx,
hops->hop_info[i - 1].hop_pte_val);
if (hops->hop_info[i].hop_addr == ULLONG_MAX)
return -EFAULT;
hops->hop_info[i].hop_pte_addr =
hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hops->hop_info[i].hop_addr,
virt_addr);
hops->hop_info[i].hop_pte_val =
hdev->asic_funcs->read_pte(hdev,
hops->hop_info[i].hop_pte_addr);
if (!(hops->hop_info[i].hop_pte_val & PAGE_PRESENT_MASK))
return -EFAULT;
if (hops->hop_info[i].hop_pte_val & mmu_prop->last_mask)
break;
}
/* if passed over all hops then no last hop was found */
if (i == mmu_prop->num_hops)
return -EFAULT;
if (!(hops->hop_info[i].hop_pte_val & PAGE_PRESENT_MASK))
return -EFAULT;
hops->used_hops = i + 1;
return 0;
}
/*
* hl_mmu_v1_prepare - prepare mmu for working with mmu v1
*
* @hdev: pointer to the device structure
*/
void hl_mmu_v1_set_funcs(struct hl_device *hdev, struct hl_mmu_funcs *mmu)
{
mmu->init = hl_mmu_dr_init;
mmu->fini = hl_mmu_dr_fini;
mmu->ctx_init = hl_mmu_v1_ctx_init;
mmu->ctx_fini = hl_mmu_v1_ctx_fini;
mmu->map = hl_mmu_v1_map;
mmu->unmap = hl_mmu_v1_unmap;
mmu->flush = hl_mmu_dr_flush;
mmu->swap_out = hl_mmu_v1_swap_out;
mmu->swap_in = hl_mmu_v1_swap_in;
mmu->get_tlb_info = hl_mmu_v1_get_tlb_info;
}
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2020 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "../habanalabs.h"
#include "../../include/hw_ip/mmu/mmu_general.h"
#include "../../include/hw_ip/mmu/mmu_v2_0.h"
#include <linux/slab.h>
/**
* hl_mmu_v2_ctx_init() - initialize a context for using the MMU module.
* @ctx: pointer to the context structure to initialize.
*
* Initialize a mutex to protect the concurrent mapping flow, a hash to hold all
* page tables hops related to this context.
* Return: 0 on success, non-zero otherwise.
*/
static int hl_mmu_v2_ctx_init(struct hl_ctx *ctx)
{
hash_init(ctx->mmu_shadow_hash);
return 0;
}
/*
* hl_mmu_v2_ctx_fini - disable a ctx from using the mmu module
*
* @ctx: pointer to the context structure
*
* This function does the following:
* - Free any pgts which were not freed yet
* - Free the mutex
* - Free DRAM default page mapping hops
*/
static void hl_mmu_v2_ctx_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct pgt_info *pgt_info;
struct hlist_node *tmp;
int i;
if (!hash_empty(ctx->mmu_shadow_hash))
dev_err(hdev->dev, "ctx %d is freed while it has pgts in use\n",
ctx->asid);
hash_for_each_safe(ctx->mmu_shadow_hash, i, tmp, pgt_info, node) {
dev_err_ratelimited(hdev->dev,
"pgt_info of addr 0x%llx of asid %d was not destroyed, num_ptes: %d\n",
pgt_info->phys_addr, ctx->asid, pgt_info->num_of_ptes);
hl_mmu_dr_free_pgt_node(ctx, pgt_info);
}
}
static int hl_mmu_v2_unmap(struct hl_ctx *ctx, u64 virt_addr, bool is_dram_addr)
{
u64 hop_addr[MMU_ARCH_6_HOPS] = { 0 }, hop_pte_addr[MMU_ARCH_6_HOPS] = { 0 }, curr_pte,
scrambled_virt_addr;
struct asic_fixed_properties *prop = &ctx->hdev->asic_prop;
struct hl_device *hdev = ctx->hdev;
struct hl_mmu_properties *mmu_prop;
bool is_huge = false;
int i, hop_last;
/* device resident in V2 are allowed only for HMMU */
if (!is_dram_addr)
return -EINVAL;
mmu_prop = &prop->dmmu;
hop_last = mmu_prop->num_hops - 1;
scrambled_virt_addr = hdev->asic_funcs->scramble_addr(hdev, virt_addr);
hop_addr[0] = hl_mmu_dr_get_hop0_addr(ctx);
hop_pte_addr[0] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, 0,
hop_addr[0], scrambled_virt_addr);
if (hop_pte_addr[0] == U64_MAX)
return -EFAULT;
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[0];
for (i = 1 ; i < mmu_prop->num_hops ; i++) {
hop_addr[i] = hl_mmu_get_next_hop_addr(ctx, curr_pte);
if (hop_addr[i] == ULLONG_MAX)
goto not_mapped;
hop_pte_addr[i] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hop_addr[i], scrambled_virt_addr);
if (hop_pte_addr[i] == U64_MAX)
return -EFAULT;
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[i];
if ((i <= hop_last) && (curr_pte & mmu_prop->last_mask)) {
hop_last = i;
is_huge = true;
break;
}
}
if (is_dram_addr && !is_huge) {
dev_err(hdev->dev, "DRAM unmapping should use huge pages only\n");
return -EFAULT;
}
if (!(curr_pte & PAGE_PRESENT_MASK))
goto not_mapped;
for (i = hop_last ; i > 0 ; i--) {
hl_mmu_dr_clear_pte(ctx, hop_pte_addr[i]);
if (hl_mmu_dr_put_pte(ctx, hop_addr[i]))
goto mapped;
}
hl_mmu_dr_clear_pte(ctx, hop_pte_addr[0]);
mapped:
return 0;
not_mapped:
dev_err(hdev->dev, "virt addr 0x%llx is not mapped to phys addr\n",
virt_addr);
return -EINVAL;
}
static int hl_mmu_v2_map(struct hl_ctx *ctx, u64 virt_addr, u64 phys_addr,
u32 page_size, bool is_dram_addr)
{
u64 hop_addr[MMU_ARCH_6_HOPS] = { 0 }, hop_pte_addr[MMU_ARCH_6_HOPS] = { 0 },
curr_pte = 0, scrambled_virt_addr, scrambled_phys_addr;
struct asic_fixed_properties *prop = &ctx->hdev->asic_prop;
bool hop_new[MMU_ARCH_6_HOPS] = { false };
struct hl_device *hdev = ctx->hdev;
struct hl_mmu_properties *mmu_prop;
int rc, i, hop_last;
/* device resident in V2 are allowed only for HMMU */
if (!is_dram_addr)
return -EINVAL;
mmu_prop = &prop->dmmu;
hop_last = mmu_prop->num_hops - 1;
scrambled_virt_addr = hdev->asic_funcs->scramble_addr(hdev, virt_addr);
scrambled_phys_addr = hdev->asic_funcs->scramble_addr(hdev, phys_addr);
/* First hop is preallocated therefore it is treated differently */
hop_addr[0] = hl_mmu_dr_get_hop0_addr(ctx);
hop_pte_addr[0] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, 0,
hop_addr[0], scrambled_virt_addr);
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[0];
/* Handle hop1 to hop_last */
for (i = 1 ; i <= hop_last ; i++) {
hop_addr[i] = hl_mmu_dr_get_alloc_next_hop_addr(ctx, curr_pte, &hop_new[i]);
if (hop_addr[i] == ULLONG_MAX) {
rc = -ENOMEM;
goto err;
}
hop_pte_addr[i] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hop_addr[i], scrambled_virt_addr);
if (hop_pte_addr[i] == U64_MAX) {
rc = -EINVAL;
goto err;
}
if (!hop_pte_addr[i]) {
rc = -EINVAL;
goto err;
}
curr_pte = *(u64 *) (uintptr_t) hop_pte_addr[i];
}
if (curr_pte & PAGE_PRESENT_MASK) {
dev_err(hdev->dev,
"mapping already exists for virt_addr 0x%llx\n",
virt_addr);
for (i = 0 ; i <= hop_last ; i++)
dev_dbg(hdev->dev, "hop%d pte: 0x%llx (0x%llx)\n",
i, *(u64 *) (uintptr_t) hop_pte_addr[i],
hop_pte_addr[i]);
rc = -EINVAL;
goto err;
}
curr_pte = (scrambled_phys_addr & HOP_PHYS_ADDR_MASK)
| mmu_prop->last_mask | PAGE_PRESENT_MASK;
/* Write the PTEs */
hl_mmu_dr_write_final_pte(ctx, hop_pte_addr[hop_last], curr_pte);
/* for each new hop, add its address to the table of previous-hop */
for (i = 1 ; i <= hop_last ; i++) {
if (hop_new[i]) {
curr_pte = (hop_addr[i] & HOP_PHYS_ADDR_MASK) | PAGE_PRESENT_MASK;
hl_mmu_dr_write_pte(ctx, hop_pte_addr[i - 1], curr_pte);
if (i - 1)
hl_mmu_dr_get_pte(ctx, hop_addr[i - 1]);
}
}
hl_mmu_dr_get_pte(ctx, hop_addr[hop_last]);
return 0;
err:
for (i = 1 ; i <= hop_last ; i++)
if (hop_new[i] && (hop_addr[i] != U64_MAX))
hl_mmu_dr_free_hop(ctx, hop_addr[i]);
return rc;
}
/*
* hl_mmu_v2_swap_out - marks all mapping of the given ctx as swapped out
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v2_swap_out(struct hl_ctx *ctx)
{
}
/*
* hl_mmu_v2_swap_in - marks all mapping of the given ctx as swapped in
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v2_swap_in(struct hl_ctx *ctx)
{
}
static int hl_mmu_v2_get_tlb_info(struct hl_ctx *ctx, u64 virt_addr, struct hl_mmu_hop_info *hops)
{
struct asic_fixed_properties *prop = &ctx->hdev->asic_prop;
struct hl_device *hdev = ctx->hdev;
struct hl_mmu_properties *mmu_prop;
bool is_dram_addr;
int i;
is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size,
prop->dmmu.start_addr,
prop->dmmu.end_addr);
/* device resident in V2 are allowed only for HMMU */
if (!is_dram_addr)
return -EINVAL;
mmu_prop = &prop->dmmu;
hops->range_type = HL_VA_RANGE_TYPE_DRAM;
hops->scrambled_vaddr = hdev->asic_funcs->scramble_addr(hdev, virt_addr);
hops->hop_info[0].hop_addr = hl_mmu_dr_get_phys_hop0_addr(ctx);
hops->hop_info[0].hop_pte_addr = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, 0,
hops->hop_info[0].hop_addr,
hops->scrambled_vaddr);
if (hops->hop_info[0].hop_pte_addr == U64_MAX)
return -EFAULT;
hops->hop_info[0].hop_pte_val = hdev->asic_funcs->read_pte(hdev,
hops->hop_info[0].hop_pte_addr);
if (hops->hop_info[0].hop_pte_val == U64_MAX)
return -EFAULT;
for (i = 1 ; i < mmu_prop->num_hops ; i++) {
hops->hop_info[i].hop_addr =
hl_mmu_get_next_hop_addr(ctx, hops->hop_info[i - 1].hop_pte_val);
if (hops->hop_info[i].hop_addr == ULLONG_MAX)
return -EFAULT;
hops->hop_info[i].hop_pte_addr =
hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hops->hop_info[i].hop_addr,
hops->scrambled_vaddr);
if (hops->hop_info[i].hop_pte_addr == U64_MAX)
return -EFAULT;
hops->hop_info[i].hop_pte_val =
hdev->asic_funcs->read_pte(hdev,
hops->hop_info[i].hop_pte_addr);
if (!(hops->hop_info[i].hop_pte_val & PAGE_PRESENT_MASK))
return -EFAULT;
if (hops->hop_info[i].hop_pte_val & mmu_prop->last_mask)
break;
}
/* if passed over all hops then no last hop was found */
if (i == mmu_prop->num_hops)
return -EFAULT;
if (!(hops->hop_info[i].hop_pte_val & PAGE_PRESENT_MASK))
return -EFAULT;
if (hops->scrambled_vaddr != virt_addr)
hops->unscrambled_paddr = hdev->asic_funcs->descramble_addr
(hdev, hops->hop_info[i].hop_pte_val);
else
hops->unscrambled_paddr = hops->hop_info[i].hop_pte_val;
hops->used_hops = i + 1;
return 0;
}
/*
* hl_mmu_v2_prepare - prepare mmu_if for working with mmu v2
*
* @hdev: pointer to the device structure
* @mmu_if: pointer to the mmu interface structure
*/
void hl_mmu_v2_set_funcs(struct hl_device *hdev, struct hl_mmu_funcs *mmu)
{
mmu->init = hl_mmu_dr_init;
mmu->fini = hl_mmu_dr_fini;
mmu->ctx_init = hl_mmu_v2_ctx_init;
mmu->ctx_fini = hl_mmu_v2_ctx_fini;
mmu->map = hl_mmu_v2_map;
mmu->unmap = hl_mmu_v2_unmap;
mmu->flush = hl_mmu_dr_flush;
mmu->swap_out = hl_mmu_v2_swap_out;
mmu->swap_in = hl_mmu_v2_swap_in;
mmu->get_tlb_info = hl_mmu_v2_get_tlb_info;
}
@@ -0,0 +1,399 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020-2022 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "../habanalabs.h"
#include "../../include/hw_ip/mmu/mmu_general.h"
#include <linux/slab.h>
static struct pgt_info *hl_mmu_v2_hr_get_pgt_info(struct hl_ctx *ctx, u64 phys_hop_addr)
{
struct pgt_info *pgt_info = NULL;
hash_for_each_possible(ctx->hr_mmu_phys_hash, pgt_info, node,
(unsigned long) phys_hop_addr)
if (phys_hop_addr == pgt_info->phys_addr)
break;
return pgt_info;
}
static void hl_mmu_v2_hr_add_pgt_info(struct hl_ctx *ctx, struct pgt_info *pgt_info,
dma_addr_t phys_addr)
{
hash_add(ctx->hr_mmu_phys_hash, &pgt_info->node, phys_addr);
}
static struct pgt_info *hl_mmu_v2_hr_get_hop0_pgt_info(struct hl_ctx *ctx)
{
return &ctx->hdev->mmu_priv.hr.mmu_asid_hop0[ctx->asid];
}
/**
* hl_mmu_v2_hr_init() - initialize the MMU module.
* @hdev: habanalabs device structure.
*
* This function does the following:
* - Create a pool of pages for pgt_infos.
* - Create a shadow table for pgt
*
* Return: 0 for success, non-zero for failure.
*/
static inline int hl_mmu_v2_hr_init(struct hl_device *hdev)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
return hl_mmu_hr_init(hdev, &hdev->mmu_priv.hr, prop->pmmu.hop_table_size,
prop->mmu_pgt_size);
}
/**
* hl_mmu_v2_hr_fini() - release the MMU module.
* @hdev: habanalabs device structure.
*
* This function does the following:
* - Disable MMU in H/W.
* - Free the pgt_infos pool.
*
* All contexts should be freed before calling this function.
*/
static inline void hl_mmu_v2_hr_fini(struct hl_device *hdev)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
hl_mmu_hr_fini(hdev, &hdev->mmu_priv.hr, prop->pmmu.hop_table_size);
}
/**
* hl_mmu_v2_hr_ctx_init() - initialize a context for using the MMU module.
* @ctx: pointer to the context structure to initialize.
*
* Initialize a mutex to protect the concurrent mapping flow, a hash to hold all
* page tables hops related to this context.
* Return: 0 on success, non-zero otherwise.
*/
static int hl_mmu_v2_hr_ctx_init(struct hl_ctx *ctx)
{
hash_init(ctx->hr_mmu_phys_hash);
return 0;
}
/*
* hl_mmu_v2_hr_ctx_fini - disable a ctx from using the mmu module
*
* @ctx: pointer to the context structure
*
* This function does the following:
* - Free any pgts which were not freed yet
* - Free the mutex
* - Free DRAM default page mapping hops
*/
static void hl_mmu_v2_hr_ctx_fini(struct hl_ctx *ctx)
{
struct hl_device *hdev = ctx->hdev;
struct pgt_info *pgt_info;
struct hlist_node *tmp;
int i;
if (!hash_empty(ctx->hr_mmu_phys_hash))
dev_err(hdev->dev, "ctx %d is freed while it has pgts in use\n",
ctx->asid);
hash_for_each_safe(ctx->hr_mmu_phys_hash, i, tmp, pgt_info, node) {
dev_err_ratelimited(hdev->dev,
"pgt_info of addr 0x%llx of asid %d was not destroyed, num_ptes: %d\n",
pgt_info->phys_addr, ctx->asid, pgt_info->num_of_ptes);
hl_mmu_hr_free_hop_remove_pgt(pgt_info, &ctx->hdev->mmu_priv.hr,
ctx->hdev->asic_prop.pmmu.hop_table_size);
}
}
static int _hl_mmu_v2_hr_unmap(struct hl_ctx *ctx,
u64 virt_addr, bool is_dram_addr)
{
u64 curr_pte, scrambled_virt_addr, hop_pte_phys_addr[MMU_ARCH_6_HOPS] = { 0 };
struct pgt_info *hops_pgt_info[MMU_ARCH_6_HOPS] = { NULL };
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop;
struct hl_mmu_properties *mmu_prop;
bool is_huge = false;
int i, hop_last;
prop = &hdev->asic_prop;
/* shifts and masks are the same in PMMU and HMMU, use one of them */
mmu_prop = is_dram_addr ? &prop->dmmu : &prop->pmmu;
hop_last = mmu_prop->num_hops - 1;
scrambled_virt_addr = hdev->asic_funcs->scramble_addr(hdev, virt_addr);
curr_pte = 0;
for (i = 0 ; i < mmu_prop->num_hops ; i++) {
/* we get HOP0 differently, it doesn't need curr_pte */
if (i == 0)
hops_pgt_info[i] = hl_mmu_v2_hr_get_hop0_pgt_info(ctx);
else
hops_pgt_info[i] = hl_mmu_hr_get_next_hop_pgt_info(ctx,
&ctx->hdev->mmu_func[MMU_HR_PGT].hr_funcs, curr_pte);
if (!hops_pgt_info[i])
goto not_mapped;
hop_pte_phys_addr[i] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hops_pgt_info[i]->phys_addr,
scrambled_virt_addr);
if (hop_pte_phys_addr[i] == U64_MAX)
return -EFAULT;
curr_pte = *(u64 *) (uintptr_t) hl_mmu_hr_pte_phys_to_virt(ctx, hops_pgt_info[i],
hop_pte_phys_addr[i],
ctx->hdev->asic_prop.pmmu.hop_table_size);
if ((i < hop_last) && (curr_pte & mmu_prop->last_mask)) {
hop_last = i;
is_huge = true;
break;
}
}
if (is_dram_addr && !is_huge) {
dev_err(hdev->dev, "DRAM unmapping should use huge pages only\n");
return -EFAULT;
}
if (!(curr_pte & PAGE_PRESENT_MASK))
goto not_mapped;
for (i = hop_last ; i > 0 ; i--) {
hl_mmu_hr_clear_pte(ctx, hops_pgt_info[i], hop_pte_phys_addr[i],
ctx->hdev->asic_prop.pmmu.hop_table_size);
if (hl_mmu_hr_put_pte(ctx, hops_pgt_info[i], &ctx->hdev->mmu_priv.hr,
ctx->hdev->asic_prop.pmmu.hop_table_size))
goto mapped;
}
hl_mmu_hr_clear_pte(ctx, hops_pgt_info[0], hop_pte_phys_addr[0],
ctx->hdev->asic_prop.pmmu.hop_table_size);
mapped:
return 0;
not_mapped:
dev_err(hdev->dev, "virt addr 0x%llx is not mapped to phys addr\n", virt_addr);
return -EINVAL;
}
static int hl_mmu_v2_get_last_hop(struct hl_mmu_properties *mmu_prop, u32 page_size)
{
int hop;
for (hop = (mmu_prop->num_hops - 1); hop; hop--) {
if (mmu_prop->hop_shifts[hop] == 0)
continue;
if (page_size <= (1 << mmu_prop->hop_shifts[hop]))
break;
}
return hop;
}
static int _hl_mmu_v2_hr_map(struct hl_ctx *ctx,
u64 virt_addr, u64 phys_addr,
u32 page_size, bool is_dram_addr)
{
u64 hop_pte_phys_addr[MMU_ARCH_6_HOPS] = { 0 },
curr_pte = 0, scrambled_virt_addr, scrambled_phys_addr;
struct pgt_info *hops_pgt_info[MMU_ARCH_6_HOPS] = { NULL };
bool hop_new[MMU_ARCH_6_HOPS] = { false };
struct hl_device *hdev = ctx->hdev;
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_mmu_properties *mmu_prop;
int i, hop_last, rc = -ENOMEM;
/*
* This mapping function can map a page or a huge page. For huge page
* there are only 4 hops rather than 5. Currently the DRAM allocation
* uses huge pages only but user memory could have been allocated with
* one of the two page sizes. Since this is a common code for all the
* three cases, we need this hugs page check.
*/
if (is_dram_addr)
mmu_prop = &prop->dmmu;
else if (page_size == prop->pmmu_huge.page_size)
mmu_prop = &prop->pmmu_huge;
else
mmu_prop = &prop->pmmu;
hop_last = hl_mmu_v2_get_last_hop(mmu_prop, page_size);
if (hop_last <= 0) {
dev_err(ctx->hdev->dev, "Invalid last HOP %d\n", hop_last);
return -EFAULT;
}
scrambled_virt_addr = hdev->asic_funcs->scramble_addr(hdev, virt_addr);
scrambled_phys_addr = hdev->asic_funcs->scramble_addr(hdev, phys_addr);
for (i = 0 ; i <= hop_last ; i++) {
if (i == 0)
hops_pgt_info[i] = hl_mmu_v2_hr_get_hop0_pgt_info(ctx);
else
hops_pgt_info[i] = hl_mmu_hr_get_alloc_next_hop(ctx,
&ctx->hdev->mmu_priv.hr,
&ctx->hdev->mmu_func[MMU_HR_PGT].hr_funcs,
mmu_prop, curr_pte, &hop_new[i]);
if (!hops_pgt_info[i])
goto err;
hop_pte_phys_addr[i] = hl_mmu_get_hop_pte_phys_addr(ctx, mmu_prop, i,
hops_pgt_info[i]->phys_addr,
scrambled_virt_addr);
curr_pte = *(u64 *) (uintptr_t) hl_mmu_hr_pte_phys_to_virt(ctx, hops_pgt_info[i],
hop_pte_phys_addr[i],
ctx->hdev->asic_prop.pmmu.hop_table_size);
}
if (curr_pte & PAGE_PRESENT_MASK) {
dev_err(hdev->dev, "mapping already exists for virt_addr 0x%llx\n",
scrambled_virt_addr);
for (i = 0 ; i <= hop_last ; i++)
dev_dbg(hdev->dev, "hop%d pte: 0x%llx (0x%llx)\n",
i,
*(u64 *) (uintptr_t)
hl_mmu_hr_pte_phys_to_virt(ctx, hops_pgt_info[i],
hop_pte_phys_addr[i],
ctx->hdev->asic_prop.pmmu.hop_table_size),
hop_pte_phys_addr[i]);
rc = -EINVAL;
goto err;
}
curr_pte = (scrambled_phys_addr & HOP_PHYS_ADDR_MASK) | mmu_prop->last_mask
| PAGE_PRESENT_MASK;
/* Write the PTEs */
hl_mmu_hr_write_pte(ctx, hops_pgt_info[hop_last], hop_pte_phys_addr[hop_last], curr_pte,
ctx->hdev->asic_prop.pmmu.hop_table_size);
/* for each new hop, add its address to the table of previous-hop */
for (i = 1 ; i <= hop_last ; i++) {
if (hop_new[i]) {
curr_pte = (hops_pgt_info[i]->phys_addr & HOP_PHYS_ADDR_MASK) |
PAGE_PRESENT_MASK;
hl_mmu_hr_write_pte(ctx, hops_pgt_info[i - 1], hop_pte_phys_addr[i - 1],
curr_pte, ctx->hdev->asic_prop.pmmu.hop_table_size);
if (i - 1)
hl_mmu_hr_get_pte(ctx, &ctx->hdev->mmu_func[MMU_HR_PGT].hr_funcs,
hops_pgt_info[i - 1]->phys_addr);
}
}
hl_mmu_hr_get_pte(ctx, &ctx->hdev->mmu_func[MMU_HR_PGT].hr_funcs,
hops_pgt_info[hop_last]->phys_addr);
return 0;
err:
for (i = 1 ; i <= hop_last ; i++)
if (hop_new[i] && hops_pgt_info[i])
hl_mmu_hr_free_hop_remove_pgt(hops_pgt_info[i], &ctx->hdev->mmu_priv.hr,
ctx->hdev->asic_prop.pmmu.hop_table_size);
return rc;
}
/*
* hl_mmu_v2_swap_out - marks all mapping of the given ctx as swapped out
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v2_hr_swap_out(struct hl_ctx *ctx)
{
}
/*
* hl_mmu_v2_swap_in - marks all mapping of the given ctx as swapped in
*
* @ctx: pointer to the context structure
*
*/
static void hl_mmu_v2_hr_swap_in(struct hl_ctx *ctx)
{
}
static int hl_mmu_v2_hr_get_tlb_mapping_params(struct hl_device *hdev,
struct hl_mmu_properties **mmu_prop,
struct hl_mmu_hop_info *hops,
u64 virt_addr, bool *is_huge)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
bool is_dram_addr, is_pmmu_addr, is_pmmu_h_addr;
is_dram_addr = hl_mem_area_inside_range(virt_addr, prop->dmmu.page_size,
prop->dmmu.start_addr,
prop->dmmu.end_addr);
is_pmmu_addr = hl_mem_area_inside_range(virt_addr, prop->pmmu.page_size,
prop->pmmu.start_addr,
prop->pmmu.end_addr);
is_pmmu_h_addr = hl_mem_area_inside_range(virt_addr,
prop->pmmu_huge.page_size,
prop->pmmu_huge.start_addr,
prop->pmmu_huge.end_addr);
if (is_dram_addr) {
*mmu_prop = &prop->dmmu;
*is_huge = true;
hops->range_type = HL_VA_RANGE_TYPE_DRAM;
} else if (is_pmmu_addr) {
*mmu_prop = &prop->pmmu;
*is_huge = false;
hops->range_type = HL_VA_RANGE_TYPE_HOST;
} else if (is_pmmu_h_addr) {
*mmu_prop = &prop->pmmu_huge;
*is_huge = true;
hops->range_type = HL_VA_RANGE_TYPE_HOST_HUGE;
} else {
return -EINVAL;
}
return 0;
}
static int hl_mmu_v2_hr_get_tlb_info(struct hl_ctx *ctx, u64 virt_addr,
struct hl_mmu_hop_info *hops)
{
return hl_mmu_hr_get_tlb_info(ctx, virt_addr, hops,
&ctx->hdev->mmu_func[MMU_HR_PGT].hr_funcs);
}
/*
* hl_mmu_v2_prepare - prepare mmu_if for working with mmu v2
*
* @hdev: pointer to the device structure
* @mmu_if: pointer to the mmu interface structure
*/
void hl_mmu_v2_hr_set_funcs(struct hl_device *hdev, struct hl_mmu_funcs *mmu)
{
mmu->init = hl_mmu_v2_hr_init;
mmu->fini = hl_mmu_v2_hr_fini;
mmu->ctx_init = hl_mmu_v2_hr_ctx_init;
mmu->ctx_fini = hl_mmu_v2_hr_ctx_fini;
mmu->map = _hl_mmu_v2_hr_map;
mmu->unmap = _hl_mmu_v2_hr_unmap;
mmu->flush = hl_mmu_hr_flush;
mmu->swap_out = hl_mmu_v2_hr_swap_out;
mmu->swap_in = hl_mmu_v2_hr_swap_in;
mmu->get_tlb_info = hl_mmu_v2_hr_get_tlb_info;
mmu->hr_funcs.get_hop0_pgt_info = hl_mmu_v2_hr_get_hop0_pgt_info;
mmu->hr_funcs.get_pgt_info = hl_mmu_v2_hr_get_pgt_info;
mmu->hr_funcs.add_pgt_info = hl_mmu_v2_hr_add_pgt_info;
mmu->hr_funcs.get_tlb_mapping_params = hl_mmu_v2_hr_get_tlb_mapping_params;
}
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
HL_COMMON_PCI_FILES := common/pci/pci.o
@@ -0,0 +1,439 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2019 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "../habanalabs.h"
#include "../../include/hw_ip/pci/pci_general.h"
#include <linux/pci.h>
#include <trace/events/habanalabs.h>
#define HL_PLDM_PCI_ELBI_TIMEOUT_MSEC (HL_PCI_ELBI_TIMEOUT_MSEC * 100)
#define IATU_REGION_CTRL_REGION_EN_MASK BIT(31)
#define IATU_REGION_CTRL_MATCH_MODE_MASK BIT(30)
#define IATU_REGION_CTRL_NUM_MATCH_EN_MASK BIT(19)
#define IATU_REGION_CTRL_BAR_NUM_MASK GENMASK(10, 8)
/**
* hl_pci_bars_map() - Map PCI BARs.
* @hdev: Pointer to hl_device structure.
* @name: Array of BAR names.
* @is_wc: Array with flag per BAR whether a write-combined mapping is needed.
*
* Request PCI regions and map them to kernel virtual addresses.
*
* Return: 0 on success, non-zero for failure.
*/
int hl_pci_bars_map(struct hl_device *hdev, const char * const name[3],
bool is_wc[3])
{
struct pci_dev *pdev = hdev->pdev;
int rc, i, bar;
rc = pci_request_regions(pdev, HL_NAME);
if (rc) {
dev_err(hdev->dev, "Cannot obtain PCI resources\n");
return rc;
}
for (i = 0 ; i < 3 ; i++) {
bar = i * 2; /* 64-bit BARs */
hdev->pcie_bar[bar] = is_wc[i] ?
pci_ioremap_wc_bar(pdev, bar) :
pci_ioremap_bar(pdev, bar);
if (!hdev->pcie_bar[bar]) {
dev_err(hdev->dev, "pci_ioremap%s_bar failed for %s\n",
is_wc[i] ? "_wc" : "", name[i]);
rc = -ENODEV;
goto err;
}
}
return 0;
err:
for (i = 2 ; i >= 0 ; i--) {
bar = i * 2; /* 64-bit BARs */
if (hdev->pcie_bar[bar])
iounmap(hdev->pcie_bar[bar]);
}
pci_release_regions(pdev);
return rc;
}
/**
* hl_pci_bars_unmap() - Unmap PCI BARS.
* @hdev: Pointer to hl_device structure.
*
* Release all PCI BARs and unmap their virtual addresses.
*/
static void hl_pci_bars_unmap(struct hl_device *hdev)
{
struct pci_dev *pdev = hdev->pdev;
int i, bar;
for (i = 2 ; i >= 0 ; i--) {
bar = i * 2; /* 64-bit BARs */
iounmap(hdev->pcie_bar[bar]);
}
pci_release_regions(pdev);
}
int hl_pci_elbi_read(struct hl_device *hdev, u64 addr, u32 *data)
{
struct pci_dev *pdev = hdev->pdev;
ktime_t timeout;
u64 msec;
u32 val;
if (hdev->pldm)
msec = HL_PLDM_PCI_ELBI_TIMEOUT_MSEC;
else
msec = HL_PCI_ELBI_TIMEOUT_MSEC;
/* Clear previous status */
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, 0);
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_ADDR, (u32) addr);
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_CTRL, 0);
timeout = ktime_add_ms(ktime_get(), msec);
for (;;) {
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, &val);
if (val & PCI_CONFIG_ELBI_STS_MASK)
break;
if (ktime_compare(ktime_get(), timeout) > 0) {
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS,
&val);
break;
}
usleep_range(300, 500);
}
if ((val & PCI_CONFIG_ELBI_STS_MASK) == PCI_CONFIG_ELBI_STS_DONE) {
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_DATA, data);
if (unlikely(trace_habanalabs_elbi_read_enabled()))
trace_habanalabs_elbi_read(&hdev->pdev->dev, (u32) addr, val);
return 0;
}
if (val & PCI_CONFIG_ELBI_STS_ERR) {
dev_err(hdev->dev, "Error reading from ELBI\n");
return -EIO;
}
if (!(val & PCI_CONFIG_ELBI_STS_MASK)) {
dev_err(hdev->dev, "ELBI read didn't finish in time\n");
return -EIO;
}
dev_err(hdev->dev, "ELBI read has undefined bits in status\n");
return -EIO;
}
/**
* hl_pci_elbi_write() - Write through the ELBI interface.
* @hdev: Pointer to hl_device structure.
* @addr: Address to write to
* @data: Data to write
*
* Return: 0 on success, negative value for failure.
*/
static int hl_pci_elbi_write(struct hl_device *hdev, u64 addr, u32 data)
{
struct pci_dev *pdev = hdev->pdev;
ktime_t timeout;
u64 msec;
u32 val;
if (hdev->pldm)
msec = HL_PLDM_PCI_ELBI_TIMEOUT_MSEC;
else
msec = HL_PCI_ELBI_TIMEOUT_MSEC;
/* Clear previous status */
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, 0);
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_ADDR, (u32) addr);
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_DATA, data);
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_CTRL,
PCI_CONFIG_ELBI_CTRL_WRITE);
timeout = ktime_add_ms(ktime_get(), msec);
for (;;) {
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, &val);
if (val & PCI_CONFIG_ELBI_STS_MASK)
break;
if (ktime_compare(ktime_get(), timeout) > 0) {
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS,
&val);
break;
}
usleep_range(300, 500);
}
if ((val & PCI_CONFIG_ELBI_STS_MASK) == PCI_CONFIG_ELBI_STS_DONE) {
if (unlikely(trace_habanalabs_elbi_write_enabled()))
trace_habanalabs_elbi_write(&hdev->pdev->dev, (u32) addr, val);
return 0;
}
if (val & PCI_CONFIG_ELBI_STS_ERR)
return -EIO;
if (!(val & PCI_CONFIG_ELBI_STS_MASK)) {
dev_err(hdev->dev, "ELBI write didn't finish in time\n");
return -EIO;
}
dev_err(hdev->dev, "ELBI write has undefined bits in status\n");
return -EIO;
}
/**
* hl_pci_iatu_write() - iatu write routine.
* @hdev: Pointer to hl_device structure.
* @addr: Address to write to
* @data: Data to write
*
* Return: 0 on success, negative value for failure.
*/
int hl_pci_iatu_write(struct hl_device *hdev, u32 addr, u32 data)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
u32 dbi_offset;
int rc;
dbi_offset = addr & 0xFFF;
/* Ignore result of writing to pcie_aux_dbi_reg_addr as it could fail
* in case the firmware security is enabled
*/
hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0x00300000);
rc = hl_pci_elbi_write(hdev, prop->pcie_dbi_base_address + dbi_offset,
data);
if (rc)
return -EIO;
return 0;
}
/**
* hl_pci_set_inbound_region() - Configure inbound region
* @hdev: Pointer to hl_device structure.
* @region: Inbound region number.
* @pci_region: Inbound region parameters.
*
* Configure the iATU inbound region.
*
* Return: 0 on success, negative value for failure.
*/
int hl_pci_set_inbound_region(struct hl_device *hdev, u8 region,
struct hl_inbound_pci_region *pci_region)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
u64 bar_phys_base, region_base, region_end_address;
u32 offset, ctrl_reg_val;
int rc = 0;
/* region offset */
offset = (0x200 * region) + 0x100;
if (pci_region->mode == PCI_ADDRESS_MATCH_MODE) {
bar_phys_base = hdev->pcie_bar_phys[pci_region->bar];
region_base = bar_phys_base + pci_region->offset_in_bar;
region_end_address = region_base + pci_region->size - 1;
rc |= hl_pci_iatu_write(hdev, offset + 0x8,
lower_32_bits(region_base));
rc |= hl_pci_iatu_write(hdev, offset + 0xC,
upper_32_bits(region_base));
rc |= hl_pci_iatu_write(hdev, offset + 0x10,
lower_32_bits(region_end_address));
}
/* Point to the specified address */
rc |= hl_pci_iatu_write(hdev, offset + 0x14, lower_32_bits(pci_region->addr));
rc |= hl_pci_iatu_write(hdev, offset + 0x18, upper_32_bits(pci_region->addr));
/* Set bar type as memory */
rc |= hl_pci_iatu_write(hdev, offset + 0x0, 0);
/* Enable + bar/address match + match enable + bar number */
ctrl_reg_val = FIELD_PREP(IATU_REGION_CTRL_REGION_EN_MASK, 1);
ctrl_reg_val |= FIELD_PREP(IATU_REGION_CTRL_MATCH_MODE_MASK, pci_region->mode);
ctrl_reg_val |= FIELD_PREP(IATU_REGION_CTRL_NUM_MATCH_EN_MASK, 1);
if (pci_region->mode == PCI_BAR_MATCH_MODE)
ctrl_reg_val |= FIELD_PREP(IATU_REGION_CTRL_BAR_NUM_MASK, pci_region->bar);
rc |= hl_pci_iatu_write(hdev, offset + 0x4, ctrl_reg_val);
/* Return the DBI window to the default location
* Ignore result of writing to pcie_aux_dbi_reg_addr as it could fail
* in case the firmware security is enabled
*/
hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0);
if (rc)
dev_err(hdev->dev, "failed to map bar %u to 0x%08llx\n",
pci_region->bar, pci_region->addr);
return rc;
}
/**
* hl_pci_set_outbound_region() - Configure outbound region 0
* @hdev: Pointer to hl_device structure.
* @pci_region: Outbound region parameters.
*
* Configure the iATU outbound region 0.
*
* Return: 0 on success, negative value for failure.
*/
int hl_pci_set_outbound_region(struct hl_device *hdev,
struct hl_outbound_pci_region *pci_region)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
u64 outbound_region_end_address;
int rc = 0;
/* Outbound Region 0 */
outbound_region_end_address =
pci_region->addr + pci_region->size - 1;
rc |= hl_pci_iatu_write(hdev, 0x008,
lower_32_bits(pci_region->addr));
rc |= hl_pci_iatu_write(hdev, 0x00C,
upper_32_bits(pci_region->addr));
rc |= hl_pci_iatu_write(hdev, 0x010,
lower_32_bits(outbound_region_end_address));
rc |= hl_pci_iatu_write(hdev, 0x014, 0);
rc |= hl_pci_iatu_write(hdev, 0x018, 0);
rc |= hl_pci_iatu_write(hdev, 0x020,
upper_32_bits(outbound_region_end_address));
/* Increase region size */
rc |= hl_pci_iatu_write(hdev, 0x000, 0x00002000);
/* Enable */
rc |= hl_pci_iatu_write(hdev, 0x004, 0x80000000);
/* Return the DBI window to the default location
* Ignore result of writing to pcie_aux_dbi_reg_addr as it could fail
* in case the firmware security is enabled
*/
hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0);
return rc;
}
/**
* hl_get_pci_memory_region() - get PCI region for given address
* @hdev: Pointer to hl_device structure.
* @addr: device address
*
* @return region index on success, otherwise PCI_REGION_NUMBER (invalid
* region index)
*/
enum pci_region hl_get_pci_memory_region(struct hl_device *hdev, u64 addr)
{
int i;
for (i = 0 ; i < PCI_REGION_NUMBER ; i++) {
struct pci_mem_region *region = &hdev->pci_mem_region[i];
if (!region->used)
continue;
if ((addr >= region->region_base) &&
(addr < region->region_base + region->region_size))
return i;
}
return PCI_REGION_NUMBER;
}
/**
* hl_pci_init() - PCI initialization code.
* @hdev: Pointer to hl_device structure.
*
* Set DMA masks, initialize the PCI controller and map the PCI BARs.
*
* Return: 0 on success, non-zero for failure.
*/
int hl_pci_init(struct hl_device *hdev)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct pci_dev *pdev = hdev->pdev;
int rc;
rc = pci_enable_device_mem(pdev);
if (rc) {
dev_err(hdev->dev, "can't enable PCI device\n");
return rc;
}
pci_set_master(pdev);
rc = hdev->asic_funcs->pci_bars_map(hdev);
if (rc) {
dev_err(hdev->dev, "Failed to map PCI BAR addresses\n");
goto disable_device;
}
rc = hdev->asic_funcs->init_iatu(hdev);
if (rc) {
dev_err(hdev->dev, "PCI controller was not initialized successfully\n");
goto unmap_pci_bars;
}
/* Driver must sleep in order for FW to finish the iATU configuration */
if (hdev->asic_prop.iatu_done_by_fw)
usleep_range(2000, 3000);
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(prop->dma_mask));
if (rc) {
dev_err(hdev->dev,
"Failed to set dma mask to %d bits, error %d\n",
prop->dma_mask, rc);
goto unmap_pci_bars;
}
dma_set_max_seg_size(&pdev->dev, U32_MAX);
return 0;
unmap_pci_bars:
hl_pci_bars_unmap(hdev);
disable_device:
pci_disable_device(pdev);
return rc;
}
/**
* hl_pci_fini() - PCI finalization code.
* @hdev: Pointer to hl_device structure
*
* Unmap PCI bars and disable PCI device.
*/
void hl_pci_fini(struct hl_device *hdev)
{
hl_pci_bars_unmap(hdev);
pci_disable_device(hdev->pdev);
}
@@ -0,0 +1,784 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
static const char * const hl_glbl_error_cause[] = {
"Error due to un-priv read",
"Error due to un-secure read",
"Error due to read from unmapped reg",
"Error due to un-priv write",
"Error due to un-secure write",
"Error due to write to unmapped reg",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"External I/F write sec violation",
"External I/F write to un-mapped reg",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"N/A",
"Read to write only",
"Write to read only"
};
/**
* hl_get_pb_block - return the relevant block within the block array
*
* @hdev: pointer to hl_device structure
* @mm_reg_addr: register address in the desired block
* @pb_blocks: blocks array
* @array_size: blocks array size
*
*/
static int hl_get_pb_block(struct hl_device *hdev, u32 mm_reg_addr,
const u32 pb_blocks[], int array_size)
{
int i;
u32 start_addr, end_addr;
for (i = 0 ; i < array_size ; i++) {
start_addr = pb_blocks[i];
end_addr = start_addr + HL_BLOCK_SIZE;
if ((mm_reg_addr >= start_addr) && (mm_reg_addr < end_addr))
return i;
}
dev_err(hdev->dev, "No protection domain was found for 0x%x\n",
mm_reg_addr);
return -EDOM;
}
/**
* hl_unset_pb_in_block - clear a specific protection bit in a block
*
* @hdev: pointer to hl_device structure
* @reg_offset: register offset will be converted to bit offset in pb block
* @sgs_entry: pb array
*
*/
static int hl_unset_pb_in_block(struct hl_device *hdev, u32 reg_offset,
struct hl_block_glbl_sec *sgs_entry)
{
if ((reg_offset >= HL_BLOCK_SIZE) || (reg_offset & 0x3)) {
dev_err(hdev->dev,
"Register offset(%d) is out of range(%d) or invalid\n",
reg_offset, HL_BLOCK_SIZE);
return -EINVAL;
}
UNSET_GLBL_SEC_BIT(sgs_entry->sec_array,
(reg_offset & (HL_BLOCK_SIZE - 1)) >> 2);
return 0;
}
/**
* hl_unsecure_register - locate the relevant block for this register and
* remove corresponding protection bit
*
* @hdev: pointer to hl_device structure
* @mm_reg_addr: register address to unsecure
* @offset: additional offset to the register address
* @pb_blocks: blocks array
* @sgs_array: pb array
* @array_size: blocks array size
*
*/
int hl_unsecure_register(struct hl_device *hdev, u32 mm_reg_addr, int offset,
const u32 pb_blocks[], struct hl_block_glbl_sec sgs_array[],
int array_size)
{
u32 reg_offset;
int block_num;
block_num = hl_get_pb_block(hdev, mm_reg_addr + offset, pb_blocks,
array_size);
if (block_num < 0)
return block_num;
reg_offset = (mm_reg_addr + offset) - pb_blocks[block_num];
return hl_unset_pb_in_block(hdev, reg_offset, &sgs_array[block_num]);
}
/**
* hl_unsecure_register_range - locate the relevant block for this register
* range and remove corresponding protection bit
*
* @hdev: pointer to hl_device structure
* @mm_reg_range: register address range to unsecure
* @offset: additional offset to the register address
* @pb_blocks: blocks array
* @sgs_array: pb array
* @array_size: blocks array size
*
*/
static int hl_unsecure_register_range(struct hl_device *hdev,
struct range mm_reg_range, int offset, const u32 pb_blocks[],
struct hl_block_glbl_sec sgs_array[],
int array_size)
{
u32 reg_offset;
int i, block_num, rc = 0;
block_num = hl_get_pb_block(hdev,
mm_reg_range.start + offset, pb_blocks,
array_size);
if (block_num < 0)
return block_num;
for (i = mm_reg_range.start ; i <= mm_reg_range.end ; i += 4) {
reg_offset = (i + offset) - pb_blocks[block_num];
rc |= hl_unset_pb_in_block(hdev, reg_offset,
&sgs_array[block_num]);
}
return rc;
}
/**
* hl_unsecure_registers - locate the relevant block for all registers and
* remove corresponding protection bit
*
* @hdev: pointer to hl_device structure
* @mm_reg_array: register address array to unsecure
* @mm_array_size: register array size
* @offset: additional offset to the register address
* @pb_blocks: blocks array
* @sgs_array: pb array
* @blocks_array_size: blocks array size
*
*/
int hl_unsecure_registers(struct hl_device *hdev, const u32 mm_reg_array[],
int mm_array_size, int offset, const u32 pb_blocks[],
struct hl_block_glbl_sec sgs_array[], int blocks_array_size)
{
int i, rc = 0;
for (i = 0 ; i < mm_array_size ; i++) {
rc = hl_unsecure_register(hdev, mm_reg_array[i], offset,
pb_blocks, sgs_array, blocks_array_size);
if (rc)
return rc;
}
return rc;
}
/**
* hl_unsecure_registers_range - locate the relevant block for all register
* ranges and remove corresponding protection bit
*
* @hdev: pointer to hl_device structure
* @mm_reg_range_array: register address range array to unsecure
* @mm_array_size: register array size
* @offset: additional offset to the register address
* @pb_blocks: blocks array
* @sgs_array: pb array
* @blocks_array_size: blocks array size
*
*/
static int hl_unsecure_registers_range(struct hl_device *hdev,
const struct range mm_reg_range_array[], int mm_array_size,
int offset, const u32 pb_blocks[],
struct hl_block_glbl_sec sgs_array[], int blocks_array_size)
{
int i, rc = 0;
for (i = 0 ; i < mm_array_size ; i++) {
rc = hl_unsecure_register_range(hdev, mm_reg_range_array[i],
offset, pb_blocks, sgs_array, blocks_array_size);
if (rc)
return rc;
}
return rc;
}
/**
* hl_ack_pb_security_violations - Ack security violation
*
* @hdev: pointer to hl_device structure
* @pb_blocks: blocks array
* @block_offset: additional offset to the block
* @array_size: blocks array size
*
*/
static void hl_ack_pb_security_violations(struct hl_device *hdev,
const u32 pb_blocks[], u32 block_offset, int array_size)
{
int i;
u32 cause, addr, block_base;
for (i = 0 ; i < array_size ; i++) {
block_base = pb_blocks[i] + block_offset;
cause = RREG32(block_base + HL_BLOCK_GLBL_ERR_CAUSE);
if (cause) {
addr = RREG32(block_base + HL_BLOCK_GLBL_ERR_ADDR);
hdev->asic_funcs->pb_print_security_errors(hdev,
block_base, cause, addr);
WREG32(block_base + HL_BLOCK_GLBL_ERR_CAUSE, cause);
}
}
}
/**
* hl_config_glbl_sec - set pb in HW according to given pb array
*
* @hdev: pointer to hl_device structure
* @pb_blocks: blocks array
* @sgs_array: pb array
* @block_offset: additional offset to the block
* @array_size: blocks array size
*
*/
void hl_config_glbl_sec(struct hl_device *hdev, const u32 pb_blocks[],
struct hl_block_glbl_sec sgs_array[], u32 block_offset,
int array_size)
{
int i, j;
u32 sgs_base;
if (hdev->pldm)
usleep_range(100, 1000);
for (i = 0 ; i < array_size ; i++) {
sgs_base = block_offset + pb_blocks[i] +
HL_BLOCK_GLBL_SEC_OFFS;
for (j = 0 ; j < HL_BLOCK_GLBL_SEC_LEN ; j++)
WREG32(sgs_base + j * sizeof(u32),
sgs_array[i].sec_array[j]);
}
}
/**
* hl_secure_block - locally memsets a block to 0
*
* @hdev: pointer to hl_device structure
* @sgs_array: pb array to clear
* @array_size: blocks array size
*
*/
void hl_secure_block(struct hl_device *hdev,
struct hl_block_glbl_sec sgs_array[], int array_size)
{
int i;
for (i = 0 ; i < array_size ; i++)
memset((char *)(sgs_array[i].sec_array), 0,
HL_BLOCK_GLBL_SEC_SIZE);
}
/**
* hl_init_pb_with_mask - set selected pb instances with mask in HW according
* to given configuration
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_array: unsecured register array
* @user_regs_array_size: unsecured register array size
* @mask: enabled instances mask: 1- enabled, 0- disabled
*/
int hl_init_pb_with_mask(struct hl_device *hdev, u32 num_dcores,
u32 dcore_offset, u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const u32 *user_regs_array, u32 user_regs_array_size, u64 mask)
{
int i, j;
struct hl_block_glbl_sec *glbl_sec;
glbl_sec = kzalloc_objs(struct hl_block_glbl_sec, blocks_array_size);
if (!glbl_sec)
return -ENOMEM;
hl_secure_block(hdev, glbl_sec, blocks_array_size);
hl_unsecure_registers(hdev, user_regs_array, user_regs_array_size, 0,
pb_blocks, glbl_sec, blocks_array_size);
/* Fill all blocks with the same configuration */
for (i = 0 ; i < num_dcores ; i++) {
for (j = 0 ; j < num_instances ; j++) {
int seq = i * num_instances + j;
if (!(mask & BIT_ULL(seq)))
continue;
hl_config_glbl_sec(hdev, pb_blocks, glbl_sec,
i * dcore_offset + j * instance_offset,
blocks_array_size);
}
}
kfree(glbl_sec);
return 0;
}
/**
* hl_init_pb - set pb in HW according to given configuration
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_array: unsecured register array
* @user_regs_array_size: unsecured register array size
*
*/
int hl_init_pb(struct hl_device *hdev, u32 num_dcores, u32 dcore_offset,
u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const u32 *user_regs_array, u32 user_regs_array_size)
{
return hl_init_pb_with_mask(hdev, num_dcores, dcore_offset,
num_instances, instance_offset, pb_blocks,
blocks_array_size, user_regs_array,
user_regs_array_size, ULLONG_MAX);
}
/**
* hl_init_pb_ranges_with_mask - set pb instances using mask in HW according to
* given configuration unsecurring registers
* ranges instead of specific registers
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_range_array: unsecured register range array
* @user_regs_range_array_size: unsecured register range array size
* @mask: enabled instances mask: 1- enabled, 0- disabled
*/
int hl_init_pb_ranges_with_mask(struct hl_device *hdev, u32 num_dcores,
u32 dcore_offset, u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const struct range *user_regs_range_array,
u32 user_regs_range_array_size, u64 mask)
{
int i, j, rc = 0;
struct hl_block_glbl_sec *glbl_sec;
glbl_sec = kzalloc_objs(struct hl_block_glbl_sec, blocks_array_size);
if (!glbl_sec)
return -ENOMEM;
hl_secure_block(hdev, glbl_sec, blocks_array_size);
rc = hl_unsecure_registers_range(hdev, user_regs_range_array,
user_regs_range_array_size, 0, pb_blocks, glbl_sec,
blocks_array_size);
if (rc)
goto free_glbl_sec;
/* Fill all blocks with the same configuration */
for (i = 0 ; i < num_dcores ; i++) {
for (j = 0 ; j < num_instances ; j++) {
int seq = i * num_instances + j;
if (!(mask & BIT_ULL(seq)))
continue;
hl_config_glbl_sec(hdev, pb_blocks, glbl_sec,
i * dcore_offset + j * instance_offset,
blocks_array_size);
}
}
free_glbl_sec:
kfree(glbl_sec);
return rc;
}
/**
* hl_init_pb_ranges - set pb in HW according to given configuration unsecurring
* registers ranges instead of specific registers
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_range_array: unsecured register range array
* @user_regs_range_array_size: unsecured register range array size
*
*/
int hl_init_pb_ranges(struct hl_device *hdev, u32 num_dcores,
u32 dcore_offset, u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const struct range *user_regs_range_array,
u32 user_regs_range_array_size)
{
return hl_init_pb_ranges_with_mask(hdev, num_dcores, dcore_offset,
num_instances, instance_offset, pb_blocks,
blocks_array_size, user_regs_range_array,
user_regs_range_array_size, ULLONG_MAX);
}
/**
* hl_init_pb_single_dcore - set pb for a single docre in HW
* according to given configuration
*
* @hdev: pointer to hl_device structure
* @dcore_offset: offset from the dcore0
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_array: unsecured register array
* @user_regs_array_size: unsecured register array size
*
*/
int hl_init_pb_single_dcore(struct hl_device *hdev, u32 dcore_offset,
u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const u32 *user_regs_array, u32 user_regs_array_size)
{
int i, rc = 0;
struct hl_block_glbl_sec *glbl_sec;
glbl_sec = kzalloc_objs(struct hl_block_glbl_sec, blocks_array_size);
if (!glbl_sec)
return -ENOMEM;
hl_secure_block(hdev, glbl_sec, blocks_array_size);
rc = hl_unsecure_registers(hdev, user_regs_array, user_regs_array_size,
0, pb_blocks, glbl_sec, blocks_array_size);
if (rc)
goto free_glbl_sec;
/* Fill all blocks with the same configuration */
for (i = 0 ; i < num_instances ; i++)
hl_config_glbl_sec(hdev, pb_blocks, glbl_sec,
dcore_offset + i * instance_offset,
blocks_array_size);
free_glbl_sec:
kfree(glbl_sec);
return rc;
}
/**
* hl_init_pb_ranges_single_dcore - set pb for a single docre in HW according
* to given configuration unsecurring
* registers ranges instead of specific
* registers
*
* @hdev: pointer to hl_device structure
* @dcore_offset: offset from the dcore0
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @user_regs_range_array: unsecured register range array
* @user_regs_range_array_size: unsecured register range array size
*
*/
int hl_init_pb_ranges_single_dcore(struct hl_device *hdev, u32 dcore_offset,
u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size,
const struct range *user_regs_range_array, u32 user_regs_range_array_size)
{
int i;
struct hl_block_glbl_sec *glbl_sec;
glbl_sec = kzalloc_objs(struct hl_block_glbl_sec, blocks_array_size);
if (!glbl_sec)
return -ENOMEM;
hl_secure_block(hdev, glbl_sec, blocks_array_size);
hl_unsecure_registers_range(hdev, user_regs_range_array,
user_regs_range_array_size, 0, pb_blocks, glbl_sec,
blocks_array_size);
/* Fill all blocks with the same configuration */
for (i = 0 ; i < num_instances ; i++)
hl_config_glbl_sec(hdev, pb_blocks, glbl_sec,
dcore_offset + i * instance_offset,
blocks_array_size);
kfree(glbl_sec);
return 0;
}
/**
* hl_ack_pb_with_mask - ack pb with mask in HW according to given configuration
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
* @mask: enabled instances mask: 1- enabled, 0- disabled
*
*/
void hl_ack_pb_with_mask(struct hl_device *hdev, u32 num_dcores,
u32 dcore_offset, u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size, u64 mask)
{
int i, j;
/* ack all blocks */
for (i = 0 ; i < num_dcores ; i++) {
for (j = 0 ; j < num_instances ; j++) {
int seq = i * num_instances + j;
if (!(mask & BIT_ULL(seq)))
continue;
hl_ack_pb_security_violations(hdev, pb_blocks,
i * dcore_offset + j * instance_offset,
blocks_array_size);
}
}
}
/**
* hl_ack_pb - ack pb in HW according to given configuration
*
* @hdev: pointer to hl_device structure
* @num_dcores: number of decores to apply configuration to
* set to HL_PB_SHARED if need to apply only once
* @dcore_offset: offset between dcores
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
*
*/
void hl_ack_pb(struct hl_device *hdev, u32 num_dcores, u32 dcore_offset,
u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size)
{
hl_ack_pb_with_mask(hdev, num_dcores, dcore_offset, num_instances,
instance_offset, pb_blocks, blocks_array_size,
ULLONG_MAX);
}
/**
* hl_ack_pb_single_dcore - ack pb for single docre in HW
* according to given configuration
*
* @hdev: pointer to hl_device structure
* @dcore_offset: offset from dcore0
* @num_instances: number of instances to apply configuration to
* @instance_offset: offset between instances
* @pb_blocks: blocks array
* @blocks_array_size: blocks array size
*
*/
void hl_ack_pb_single_dcore(struct hl_device *hdev, u32 dcore_offset,
u32 num_instances, u32 instance_offset,
const u32 pb_blocks[], u32 blocks_array_size)
{
int i;
/* ack all blocks */
for (i = 0 ; i < num_instances ; i++)
hl_ack_pb_security_violations(hdev, pb_blocks,
dcore_offset + i * instance_offset,
blocks_array_size);
}
static u32 hl_automated_get_block_base_addr(struct hl_device *hdev,
struct hl_special_block_info *block_info,
u32 major, u32 minor, u32 sub_minor)
{
u32 fw_block_base_address = block_info->base_addr +
major * block_info->major_offset +
minor * block_info->minor_offset +
sub_minor * block_info->sub_minor_offset;
struct asic_fixed_properties *prop = &hdev->asic_prop;
/* Calculation above returns an address for FW use, and therefore should
* be casted for driver use.
*/
return (fw_block_base_address - lower_32_bits(prop->cfg_base_address));
}
static bool hl_check_block_type_exclusion(struct hl_skip_blocks_cfg *skip_blocks_cfg,
int block_type)
{
int i;
/* Check if block type is listed in the exclusion list of block types */
for (i = 0 ; i < skip_blocks_cfg->block_types_len ; i++)
if (block_type == skip_blocks_cfg->block_types[i])
return true;
return false;
}
static bool hl_check_block_range_exclusion(struct hl_device *hdev,
struct hl_skip_blocks_cfg *skip_blocks_cfg,
struct hl_special_block_info *block_info,
u32 major, u32 minor, u32 sub_minor)
{
u32 blocks_in_range, block_base_addr_in_range, block_base_addr;
int i, j;
block_base_addr = hl_automated_get_block_base_addr(hdev, block_info,
major, minor, sub_minor);
for (i = 0 ; i < skip_blocks_cfg->block_ranges_len ; i++) {
blocks_in_range = (skip_blocks_cfg->block_ranges[i].end -
skip_blocks_cfg->block_ranges[i].start) /
HL_BLOCK_SIZE + 1;
for (j = 0 ; j < blocks_in_range ; j++) {
block_base_addr_in_range = skip_blocks_cfg->block_ranges[i].start +
j * HL_BLOCK_SIZE;
if (block_base_addr == block_base_addr_in_range)
return true;
}
}
return false;
}
static int hl_read_glbl_errors(struct hl_device *hdev,
u32 blk_idx, u32 major, u32 minor, u32 sub_minor, void *data)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_special_block_info *special_blocks = prop->special_blocks;
struct hl_special_block_info *current_block = &special_blocks[blk_idx];
u32 glbl_err_addr, glbl_err_cause, addr_val, cause_val, block_base,
base = current_block->base_addr - lower_32_bits(prop->cfg_base_address);
int i;
block_base = base + major * current_block->major_offset +
minor * current_block->minor_offset +
sub_minor * current_block->sub_minor_offset;
glbl_err_cause = block_base + HL_GLBL_ERR_CAUSE_OFFSET;
cause_val = RREG32(glbl_err_cause);
if (!cause_val)
return 0;
glbl_err_addr = block_base + HL_GLBL_ERR_ADDR_OFFSET;
addr_val = RREG32(glbl_err_addr);
for (i = 0 ; i <= prop->glbl_err_max_cause_num ; i++) {
if (cause_val & BIT(i))
dev_err_ratelimited(hdev->dev,
"%s, addr %#llx\n",
hl_glbl_error_cause[i],
prop->cfg_base_address + block_base +
FIELD_GET(HL_GLBL_ERR_ADDRESS_MASK, addr_val));
}
WREG32(glbl_err_cause, cause_val);
return 0;
}
void hl_check_for_glbl_errors(struct hl_device *hdev)
{
struct asic_fixed_properties *prop = &hdev->asic_prop;
struct hl_special_blocks_cfg special_blocks_cfg;
struct iterate_special_ctx glbl_err_iter;
int rc;
memset(&special_blocks_cfg, 0, sizeof(special_blocks_cfg));
special_blocks_cfg.skip_blocks_cfg = &prop->skip_special_blocks_cfg;
glbl_err_iter.fn = &hl_read_glbl_errors;
glbl_err_iter.data = &special_blocks_cfg;
rc = hl_iterate_special_blocks(hdev, &glbl_err_iter);
if (rc)
dev_err_ratelimited(hdev->dev,
"Could not iterate special blocks, glbl error check failed\n");
}
int hl_iterate_special_blocks(struct hl_device *hdev, struct iterate_special_ctx *ctx)
{
struct hl_special_blocks_cfg *special_blocks_cfg =
(struct hl_special_blocks_cfg *)ctx->data;
struct hl_skip_blocks_cfg *skip_blocks_cfg =
special_blocks_cfg->skip_blocks_cfg;
u32 major, minor, sub_minor, blk_idx, num_blocks;
struct hl_special_block_info *block_info_arr;
int rc;
block_info_arr = hdev->asic_prop.special_blocks;
if (!block_info_arr)
return -EINVAL;
num_blocks = hdev->asic_prop.num_of_special_blocks;
for (blk_idx = 0 ; blk_idx < num_blocks ; blk_idx++, block_info_arr++) {
if (hl_check_block_type_exclusion(skip_blocks_cfg, block_info_arr->block_type))
continue;
for (major = 0 ; major < block_info_arr->major ; major++) {
minor = 0;
do {
sub_minor = 0;
do {
if ((hl_check_block_range_exclusion(hdev,
skip_blocks_cfg, block_info_arr,
major, minor, sub_minor)) ||
(skip_blocks_cfg->skip_block_hook &&
skip_blocks_cfg->skip_block_hook(hdev,
special_blocks_cfg,
blk_idx, major, minor, sub_minor))) {
sub_minor++;
continue;
}
rc = ctx->fn(hdev, blk_idx, major, minor,
sub_minor, ctx->data);
if (rc)
return rc;
sub_minor++;
} while (sub_minor < block_info_arr->sub_minor);
minor++;
} while (minor < block_info_arr->minor);
}
}
return 0;
}
@@ -0,0 +1,162 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright 2016-2022 HabanaLabs, Ltd.
* All Rights Reserved.
*
*/
#ifndef SECURITY_H_
#define SECURITY_H_
#include <linux/io-64-nonatomic-lo-hi.h>
struct hl_device;
/* special blocks */
#define HL_GLBL_ERR_ADDRESS_MASK GENMASK(11, 0)
/* GLBL_ERR_ADDR register offset from the start of the block */
#define HL_GLBL_ERR_ADDR_OFFSET 0xF44
/* GLBL_ERR_CAUSE register offset from the start of the block */
#define HL_GLBL_ERR_CAUSE_OFFSET 0xF48
/*
* struct hl_special_block_info - stores address details of a particular type of
* IP block which has a SPECIAL part.
*
* @block_type: block type as described in every ASIC's block_types enum.
* @base_addr: base address of the first block of particular type,
* e.g., address of NIC0_UMR0_0 of 'NIC_UMR' block.
* @major: number of major blocks of particular type.
* @minor: number of minor blocks of particular type.
* @sub_minor: number of sub minor blocks of particular type.
* @major_offset: address gap between 2 consecutive major blocks of particular type,
* e.g., offset between NIC0_UMR0_0 and NIC1_UMR0_0 is 0x80000.
* @minor_offset: address gap between 2 consecutive minor blocks of particular type,
* e.g., offset between NIC0_UMR0_0 and NIC0_UMR1_0 is 0x20000.
* @sub_minor_offset: address gap between 2 consecutive sub_minor blocks of particular
* type, e.g., offset between NIC0_UMR0_0 and NIC0_UMR0_1 is 0x1000.
*
* e.g., in Gaudi2, NIC_UMR blocks can be interpreted as:
* NIC<major>_UMR<minor>_<sub_minor> where major=12, minor=2, sub_minor=15.
* In other words, for each of 12 major numbers (i.e 0 to 11) there are
* 2 blocks with different minor numbers (i.e. 0 to 1). Again, for each minor
* number there are 15 blocks with different sub_minor numbers (i.e. 0 to 14).
* So different blocks are NIC0_UMR0_0, NIC0_UMR0_1, ..., NIC0_UMR1_0, ....,
* NIC11_UMR1_14.
*
* Struct's formatted data is located in the SOL-based auto-generated protbits headers.
*/
struct hl_special_block_info {
int block_type;
u32 base_addr;
u32 major;
u32 minor;
u32 sub_minor;
u32 major_offset;
u32 minor_offset;
u32 sub_minor_offset;
};
/*
* struct hl_automated_pb_cfg - represents configurations of a particular type
* of IP block which has protection bits.
*
* @addr: address details as described in hl_automation_pb_addr struct.
* @prot_map: each bit corresponds to one among 32 protection configuration regs
* (e.g., SPECIAL_GLBL_PRIV). '1' means 0xffffffff and '0' means 0x0
* to be written into the corresponding protection configuration reg.
* This bit is meaningful if same bit in data_map is 0, otherwise ignored.
* @data_map: each bit corresponds to one among 32 protection configuration regs
* (e.g., SPECIAL_GLBL_PRIV). '1' means corresponding protection
* configuration reg is to be written with a value in array pointed
* by 'data', otherwise the value is decided by 'prot_map'.
* @data: pointer to data array which stores the config value(s) to be written
* to corresponding protection configuration reg(s).
* @data_size: size of the data array.
*
* Each bit of 'data_map' and 'prot_map' fields corresponds to one among 32
* protection configuration registers e.g., SPECIAL GLBL PRIV regs (starting at
* offset 0xE80). '1' in 'data_map' means protection configuration to be done
* using configuration in data array. '0' in 'data_map" means protection
* configuration to be done as per the value of corresponding bit in 'prot_map'.
* '1' in 'prot_map' means the register to be programmed with 0xFFFFFFFF
* (all non-protected). '0' in 'prot_map' means the register to be programmed
* with 0x0 (all protected).
*
* e.g., prot_map = 0x00000001, data_map = 0xC0000000 , data = {0xff, 0x12}
* SPECIAL_GLBL_PRIV[0] = 0xFFFFFFFF
* SPECIAL_GLBL_PRIV[1..29] = 0x0
* SPECIAL_GLBL_PRIV[30] = 0xFF
* SPECIAL_GLBL_PRIV[31] = 0x12
*/
struct hl_automated_pb_cfg {
struct hl_special_block_info addr;
u32 prot_map;
u32 data_map;
const u32 *data;
u8 data_size;
};
/* struct hl_special_blocks_cfg - holds special blocks cfg data.
*
* @priv_automated_pb_cfg: points to the main privileged PB array.
* @sec_automated_pb_cfg: points to the main secured PB array.
* @skip_blocks_cfg: holds arrays of block types & block ranges to be excluded.
* @priv_cfg_size: size of the main privileged PB array.
* @sec_cfg_size: size of the main secured PB array.
* @prot_lvl_priv: indication if it's a privileged/secured PB configurations.
*/
struct hl_special_blocks_cfg {
struct hl_automated_pb_cfg *priv_automated_pb_cfg;
struct hl_automated_pb_cfg *sec_automated_pb_cfg;
struct hl_skip_blocks_cfg *skip_blocks_cfg;
u32 priv_cfg_size;
u32 sec_cfg_size;
u8 prot_lvl_priv;
};
/* Automated security */
/* struct hl_skip_blocks_cfg - holds arrays of block types & block ranges to be
* excluded from special blocks configurations.
*
* @block_types: an array of block types NOT to be configured.
* @block_types_len: len of an array of block types not to be configured.
* @block_ranges: an array of block ranges not to be configured.
* @block_ranges_len: len of an array of block ranges not to be configured.
* @skip_block_hook: hook that will be called before initializing special blocks.
*/
struct hl_skip_blocks_cfg {
int *block_types;
size_t block_types_len;
struct range *block_ranges;
size_t block_ranges_len;
bool (*skip_block_hook)(struct hl_device *hdev,
struct hl_special_blocks_cfg *special_blocks_cfg,
u32 blk_idx, u32 major, u32 minor, u32 sub_minor);
};
/**
* struct iterate_special_ctx - HW module special block iterator
* @fn: function to apply to each HW module special block instance
* @data: optional internal data to the function iterator
*/
struct iterate_special_ctx {
/*
* callback for the HW module special block iterator
* @hdev: pointer to the habanalabs device structure
* @block_id: block (ASIC specific definition can be dcore/hdcore)
* @major: major block index within block_id
* @minor: minor block index within the major block
* @sub_minor: sub_minor block index within the minor block
* @data: function specific data
*/
int (*fn)(struct hl_device *hdev, u32 block_id, u32 major, u32 minor,
u32 sub_minor, void *data);
void *data;
};
int hl_iterate_special_blocks(struct hl_device *hdev, struct iterate_special_ctx *ctx);
void hl_check_for_glbl_errors(struct hl_device *hdev);
#endif /* SECURITY_H_ */
@@ -0,0 +1,718 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2021 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include <linux/vmalloc.h>
#include <uapi/drm/habanalabs_accel.h>
#include "habanalabs.h"
/**
* hl_format_as_binary - helper function, format an integer as binary
* using supplied scratch buffer
* @buf: the buffer to use
* @buf_len: buffer capacity
* @n: number to format
*
* Returns pointer to buffer
*/
char *hl_format_as_binary(char *buf, size_t buf_len, u32 n)
{
int i;
u32 bit;
bool leading0 = true;
char *wrptr = buf;
if (buf_len > 0 && buf_len < 3) {
*wrptr = '\0';
return buf;
}
wrptr[0] = '0';
wrptr[1] = 'b';
wrptr += 2;
/* Remove 3 characters from length for '0b' and '\0' termination */
buf_len -= 3;
for (i = 0; i < sizeof(n) * BITS_PER_BYTE && buf_len; ++i, n <<= 1) {
/* Writing bit calculation in one line would cause a false
* positive static code analysis error, so splitting.
*/
bit = n & (1 << (sizeof(n) * BITS_PER_BYTE - 1));
bit = !!bit;
leading0 &= !bit;
if (!leading0) {
*wrptr = '0' + bit;
++wrptr;
}
}
*wrptr = '\0';
return buf;
}
/**
* resize_to_fit - helper function, resize buffer to fit given amount of data
* @buf: destination buffer double pointer
* @size: pointer to the size container
* @desired_size: size the buffer must contain
*
* Returns 0 on success or error code on failure.
* On success, the size of buffer is at least desired_size. Buffer is allocated
* via vmalloc and must be freed with vfree.
*/
static int resize_to_fit(char **buf, size_t *size, size_t desired_size)
{
char *resized_buf;
size_t new_size;
if (*size >= desired_size)
return 0;
/* Not enough space to print all, have to resize */
new_size = max_t(size_t, PAGE_SIZE, round_up(desired_size, PAGE_SIZE));
resized_buf = vmalloc(new_size);
if (!resized_buf)
return -ENOMEM;
memcpy(resized_buf, *buf, *size);
vfree(*buf);
*buf = resized_buf;
*size = new_size;
return 1;
}
/**
* hl_snprintf_resize() - print formatted data to buffer, resize as needed
* @buf: buffer double pointer, to be written to and resized, must be either
* NULL or allocated with vmalloc.
* @size: current size of the buffer
* @offset: current offset to write to
* @format: format of the data
*
* This function will write formatted data into the buffer. If buffer is not
* large enough, it will be resized using vmalloc. Size may be modified if the
* buffer was resized, offset will be advanced by the number of bytes written
* not including the terminating character
*
* Returns 0 on success or error code on failure
*
* Note that the buffer has to be manually released using vfree.
*/
int hl_snprintf_resize(char **buf, size_t *size, size_t *offset,
const char *format, ...)
{
va_list args;
size_t length;
int rc;
if (*buf == NULL && (*size != 0 || *offset != 0))
return -EINVAL;
va_start(args, format);
length = vsnprintf(*buf + *offset, *size - *offset, format, args);
va_end(args);
rc = resize_to_fit(buf, size, *offset + length + 1);
if (rc < 0)
return rc;
else if (rc > 0) {
/* Resize was needed, write again */
va_start(args, format);
length = vsnprintf(*buf + *offset, *size - *offset, format,
args);
va_end(args);
}
*offset += length;
return 0;
}
/**
* hl_sync_engine_to_string - convert engine type enum to string literal
* @engine_type: engine type (TPC/MME/DMA)
*
* Return the resolved string literal
*/
const char *hl_sync_engine_to_string(enum hl_sync_engine_type engine_type)
{
switch (engine_type) {
case ENGINE_DMA:
return "DMA";
case ENGINE_MME:
return "MME";
case ENGINE_TPC:
return "TPC";
}
return "Invalid Engine Type";
}
/**
* hl_print_resize_sync_engine - helper function, format engine name and ID
* using hl_snprintf_resize
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
* @engine_type: engine type (TPC/MME/DMA)
* @engine_id: engine numerical id
*
* Returns 0 on success or error code on failure
*/
static int hl_print_resize_sync_engine(char **buf, size_t *size, size_t *offset,
enum hl_sync_engine_type engine_type,
u32 engine_id)
{
return hl_snprintf_resize(buf, size, offset, "%s%u",
hl_sync_engine_to_string(engine_type), engine_id);
}
/**
* hl_state_dump_get_sync_name - transform sync object id to name if available
* @hdev: pointer to the device
* @sync_id: sync object id
*
* Returns a name literal or NULL if not resolved.
* Note: returning NULL shall not be considered as a failure, as not all
* sync objects are named.
*/
const char *hl_state_dump_get_sync_name(struct hl_device *hdev, u32 sync_id)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
struct hl_hw_obj_name_entry *entry;
hash_for_each_possible(sds->so_id_to_str_tb, entry,
node, sync_id)
if (sync_id == entry->id)
return entry->name;
return NULL;
}
/**
* hl_state_dump_get_monitor_name - transform monitor object dump to monitor
* name if available
* @hdev: pointer to the device
* @mon: monitor state dump
*
* Returns a name literal or NULL if not resolved.
* Note: returning NULL shall not be considered as a failure, as not all
* monitors are named.
*/
const char *hl_state_dump_get_monitor_name(struct hl_device *hdev,
struct hl_mon_state_dump *mon)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
struct hl_hw_obj_name_entry *entry;
hash_for_each_possible(sds->monitor_id_to_str_tb,
entry, node, mon->id)
if (mon->id == entry->id)
return entry->name;
return NULL;
}
/**
* hl_state_dump_free_sync_to_engine_map - free sync object to engine map
* @map: sync object to engine map
*
* Note: generic free implementation, the allocation is implemented per ASIC.
*/
void hl_state_dump_free_sync_to_engine_map(struct hl_sync_to_engine_map *map)
{
struct hl_sync_to_engine_map_entry *entry;
struct hlist_node *tmp_node;
int i;
hash_for_each_safe(map->tb, i, tmp_node, entry, node) {
hash_del(&entry->node);
kfree(entry);
}
}
/**
* hl_state_dump_get_sync_to_engine - transform sync_id to
* hl_sync_to_engine_map_entry if available for current id
* @map: sync object to engine map
* @sync_id: sync object id
*
* Returns the translation entry if found or NULL if not.
* Note, returned NULL shall not be considered as a failure as the map
* does not cover all possible, it is a best effort sync ids.
*/
static struct hl_sync_to_engine_map_entry *
hl_state_dump_get_sync_to_engine(struct hl_sync_to_engine_map *map, u32 sync_id)
{
struct hl_sync_to_engine_map_entry *entry;
hash_for_each_possible(map->tb, entry, node, sync_id)
if (entry->sync_id == sync_id)
return entry;
return NULL;
}
/**
* hl_state_dump_read_sync_objects - read sync objects array
* @hdev: pointer to the device
* @index: sync manager block index starting with E_N
*
* Returns array of size SP_SYNC_OBJ_AMOUNT on success or NULL on failure
*/
static u32 *hl_state_dump_read_sync_objects(struct hl_device *hdev, u32 index)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
u32 *sync_objects;
s64 base_addr; /* Base addr can be negative */
int i;
base_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;
sync_objects = vmalloc(sds->props[SP_SYNC_OBJ_AMOUNT] * sizeof(u32));
if (!sync_objects)
return NULL;
for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i)
sync_objects[i] = RREG32(base_addr + i * sizeof(u32));
return sync_objects;
}
/**
* hl_state_dump_free_sync_objects - free sync objects array allocated by
* hl_state_dump_read_sync_objects
* @sync_objects: sync objects array
*/
static void hl_state_dump_free_sync_objects(u32 *sync_objects)
{
vfree(sync_objects);
}
/**
* hl_state_dump_print_syncs_single_block - print active sync objects on a
* single block
* @hdev: pointer to the device
* @index: sync manager block index starting with E_N
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
* @map: sync engines names map
*
* Returns 0 on success or error code on failure
*/
static int
hl_state_dump_print_syncs_single_block(struct hl_device *hdev, u32 index,
char **buf, size_t *size, size_t *offset,
struct hl_sync_to_engine_map *map)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
const char *sync_name;
u32 *sync_objects = NULL;
int rc = 0, i;
if (sds->sync_namager_names) {
rc = hl_snprintf_resize(
buf, size, offset, "%s\n",
sds->sync_namager_names[index]);
if (rc)
goto out;
}
sync_objects = hl_state_dump_read_sync_objects(hdev, index);
if (!sync_objects) {
rc = -ENOMEM;
goto out;
}
for (i = 0; i < sds->props[SP_SYNC_OBJ_AMOUNT]; ++i) {
struct hl_sync_to_engine_map_entry *entry;
u64 sync_object_addr;
if (!sync_objects[i])
continue;
sync_object_addr = sds->props[SP_SYNC_OBJ_BASE_ADDR] +
sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index +
i * sizeof(u32);
rc = hl_snprintf_resize(buf, size, offset, "sync id: %u", i);
if (rc)
goto free_sync_objects;
sync_name = hl_state_dump_get_sync_name(hdev, i);
if (sync_name) {
rc = hl_snprintf_resize(buf, size, offset, " %s",
sync_name);
if (rc)
goto free_sync_objects;
}
rc = hl_snprintf_resize(buf, size, offset, ", value: %u",
sync_objects[i]);
if (rc)
goto free_sync_objects;
/* Append engine string */
entry = hl_state_dump_get_sync_to_engine(map,
(u32)sync_object_addr);
if (entry) {
rc = hl_snprintf_resize(buf, size, offset,
", Engine: ");
if (rc)
goto free_sync_objects;
rc = hl_print_resize_sync_engine(buf, size, offset,
entry->engine_type,
entry->engine_id);
if (rc)
goto free_sync_objects;
}
rc = hl_snprintf_resize(buf, size, offset, "\n");
if (rc)
goto free_sync_objects;
}
free_sync_objects:
hl_state_dump_free_sync_objects(sync_objects);
out:
return rc;
}
/**
* hl_state_dump_print_syncs - print active sync objects
* @hdev: pointer to the device
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
*
* Returns 0 on success or error code on failure
*/
static int hl_state_dump_print_syncs(struct hl_device *hdev,
char **buf, size_t *size,
size_t *offset)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
struct hl_sync_to_engine_map *map;
u32 index;
int rc = 0;
map = kzalloc_obj(*map);
if (!map)
return -ENOMEM;
rc = sds->funcs.gen_sync_to_engine_map(hdev, map);
if (rc)
goto free_map_mem;
rc = hl_snprintf_resize(buf, size, offset, "Non zero sync objects:\n");
if (rc)
goto out;
if (sds->sync_namager_names) {
for (index = 0; sds->sync_namager_names[index]; ++index) {
rc = hl_state_dump_print_syncs_single_block(
hdev, index, buf, size, offset, map);
if (rc)
goto out;
}
} else {
for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
rc = hl_state_dump_print_syncs_single_block(
hdev, index, buf, size, offset, map);
if (rc)
goto out;
}
}
out:
hl_state_dump_free_sync_to_engine_map(map);
free_map_mem:
kfree(map);
return rc;
}
/**
* hl_state_dump_alloc_read_sm_block_monitors - read monitors for a specific
* block
* @hdev: pointer to the device
* @index: sync manager block index starting with E_N
*
* Returns an array of monitor data of size SP_MONITORS_AMOUNT or NULL
* on error
*/
static struct hl_mon_state_dump *
hl_state_dump_alloc_read_sm_block_monitors(struct hl_device *hdev, u32 index)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
struct hl_mon_state_dump *monitors;
s64 base_addr; /* Base addr can be negative */
int i;
monitors = vmalloc(sds->props[SP_MONITORS_AMOUNT] *
sizeof(struct hl_mon_state_dump));
if (!monitors)
return NULL;
base_addr = sds->props[SP_NEXT_SYNC_OBJ_ADDR] * index;
for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
monitors[i].id = i;
monitors[i].wr_addr_low =
RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_LOW] +
i * sizeof(u32));
monitors[i].wr_addr_high =
RREG32(base_addr + sds->props[SP_MON_OBJ_WR_ADDR_HIGH] +
i * sizeof(u32));
monitors[i].wr_data =
RREG32(base_addr + sds->props[SP_MON_OBJ_WR_DATA] +
i * sizeof(u32));
monitors[i].arm_data =
RREG32(base_addr + sds->props[SP_MON_OBJ_ARM_DATA] +
i * sizeof(u32));
monitors[i].status =
RREG32(base_addr + sds->props[SP_MON_OBJ_STATUS] +
i * sizeof(u32));
}
return monitors;
}
/**
* hl_state_dump_free_monitors - free the monitors structure
* @monitors: monitors array created with
* hl_state_dump_alloc_read_sm_block_monitors
*/
static void hl_state_dump_free_monitors(struct hl_mon_state_dump *monitors)
{
vfree(monitors);
}
/**
* hl_state_dump_print_monitors_single_block - print active monitors on a
* single block
* @hdev: pointer to the device
* @index: sync manager block index starting with E_N
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
*
* Returns 0 on success or error code on failure
*/
static int hl_state_dump_print_monitors_single_block(struct hl_device *hdev,
u32 index,
char **buf, size_t *size,
size_t *offset)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
struct hl_mon_state_dump *monitors = NULL;
int rc = 0, i;
if (sds->sync_namager_names) {
rc = hl_snprintf_resize(
buf, size, offset, "%s\n",
sds->sync_namager_names[index]);
if (rc)
goto out;
}
monitors = hl_state_dump_alloc_read_sm_block_monitors(hdev, index);
if (!monitors) {
rc = -ENOMEM;
goto out;
}
for (i = 0; i < sds->props[SP_MONITORS_AMOUNT]; ++i) {
if (!(sds->funcs.monitor_valid(&monitors[i])))
continue;
/* Monitor is valid, dump it */
rc = sds->funcs.print_single_monitor(buf, size, offset, hdev,
&monitors[i]);
if (rc)
goto free_monitors;
hl_snprintf_resize(buf, size, offset, "\n");
}
free_monitors:
hl_state_dump_free_monitors(monitors);
out:
return rc;
}
/**
* hl_state_dump_print_monitors - print active monitors
* @hdev: pointer to the device
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
*
* Returns 0 on success or error code on failure
*/
static int hl_state_dump_print_monitors(struct hl_device *hdev,
char **buf, size_t *size,
size_t *offset)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
u32 index;
int rc = 0;
rc = hl_snprintf_resize(buf, size, offset,
"Valid (armed) monitor objects:\n");
if (rc)
goto out;
if (sds->sync_namager_names) {
for (index = 0; sds->sync_namager_names[index]; ++index) {
rc = hl_state_dump_print_monitors_single_block(
hdev, index, buf, size, offset);
if (rc)
goto out;
}
} else {
for (index = 0; index < sds->props[SP_NUM_CORES]; ++index) {
rc = hl_state_dump_print_monitors_single_block(
hdev, index, buf, size, offset);
if (rc)
goto out;
}
}
out:
return rc;
}
/**
* hl_state_dump_print_engine_fences - print active fences for a specific
* engine
* @hdev: pointer to the device
* @engine_type: engine type to use
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
*/
static int
hl_state_dump_print_engine_fences(struct hl_device *hdev,
enum hl_sync_engine_type engine_type,
char **buf, size_t *size, size_t *offset)
{
struct hl_state_dump_specs *sds = &hdev->state_dump_specs;
int rc = 0, i, n_fences;
u64 base_addr, next_fence;
switch (engine_type) {
case ENGINE_TPC:
n_fences = sds->props[SP_NUM_OF_TPC_ENGINES];
base_addr = sds->props[SP_TPC0_CMDQ];
next_fence = sds->props[SP_NEXT_TPC];
break;
case ENGINE_MME:
n_fences = sds->props[SP_NUM_OF_MME_ENGINES];
base_addr = sds->props[SP_MME_CMDQ];
next_fence = sds->props[SP_NEXT_MME];
break;
case ENGINE_DMA:
n_fences = sds->props[SP_NUM_OF_DMA_ENGINES];
base_addr = sds->props[SP_DMA_CMDQ];
next_fence = sds->props[SP_DMA_QUEUES_OFFSET];
break;
default:
return -EINVAL;
}
for (i = 0; i < n_fences; ++i) {
rc = sds->funcs.print_fences_single_engine(
hdev,
base_addr + next_fence * i +
sds->props[SP_FENCE0_CNT_OFFSET],
base_addr + next_fence * i +
sds->props[SP_CP_STS_OFFSET],
engine_type, i, buf, size, offset);
if (rc)
goto out;
}
out:
return rc;
}
/**
* hl_state_dump_print_fences - print active fences
* @hdev: pointer to the device
* @buf: destination buffer double pointer to be used with hl_snprintf_resize
* @size: pointer to the size container
* @offset: pointer to the offset container
*/
static int hl_state_dump_print_fences(struct hl_device *hdev, char **buf,
size_t *size, size_t *offset)
{
int rc = 0;
rc = hl_snprintf_resize(buf, size, offset, "Valid (armed) fences:\n");
if (rc)
goto out;
rc = hl_state_dump_print_engine_fences(hdev, ENGINE_TPC, buf, size, offset);
if (rc)
goto out;
rc = hl_state_dump_print_engine_fences(hdev, ENGINE_MME, buf, size, offset);
if (rc)
goto out;
rc = hl_state_dump_print_engine_fences(hdev, ENGINE_DMA, buf, size, offset);
if (rc)
goto out;
out:
return rc;
}
/**
* hl_state_dump() - dump system state
* @hdev: pointer to device structure
*/
int hl_state_dump(struct hl_device *hdev)
{
char *buf = NULL;
size_t offset = 0, size = 0;
int rc;
rc = hl_snprintf_resize(&buf, &size, &offset,
"Timestamp taken on: %llu\n\n",
ktime_to_ns(ktime_get()));
if (rc)
goto err;
rc = hl_state_dump_print_syncs(hdev, &buf, &size, &offset);
if (rc)
goto err;
hl_snprintf_resize(&buf, &size, &offset, "\n");
rc = hl_state_dump_print_monitors(hdev, &buf, &size, &offset);
if (rc)
goto err;
hl_snprintf_resize(&buf, &size, &offset, "\n");
rc = hl_state_dump_print_fences(hdev, &buf, &size, &offset);
if (rc)
goto err;
hl_snprintf_resize(&buf, &size, &offset, "\n");
hl_debugfs_set_state_dump(hdev, buf, size);
return 0;
err:
vfree(buf);
return rc;
}
@@ -0,0 +1,567 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2016-2022 HabanaLabs, Ltd.
* All Rights Reserved.
*/
#include "habanalabs.h"
#include <linux/pci.h>
#include <linux/types.h>
static ssize_t clk_max_freq_mhz_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
long value;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
value = hl_fw_get_frequency(hdev, hdev->asic_prop.clk_pll_index, false);
if (value < 0)
return value;
hdev->asic_prop.max_freq_value = value;
return sprintf(buf, "%lu\n", (value / 1000 / 1000));
}
static ssize_t clk_max_freq_mhz_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct hl_device *hdev = dev_get_drvdata(dev);
int rc;
u64 value;
if (!hl_device_operational(hdev, NULL)) {
count = -ENODEV;
goto fail;
}
rc = kstrtoull(buf, 0, &value);
if (rc) {
count = -EINVAL;
goto fail;
}
hdev->asic_prop.max_freq_value = value * 1000 * 1000;
hl_fw_set_frequency(hdev, hdev->asic_prop.clk_pll_index, hdev->asic_prop.max_freq_value);
fail:
return count;
}
static ssize_t clk_cur_freq_mhz_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
long value;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
value = hl_fw_get_frequency(hdev, hdev->asic_prop.clk_pll_index, true);
if (value < 0)
return value;
return sprintf(buf, "%lu\n", (value / 1000 / 1000));
}
static DEVICE_ATTR_RW(clk_max_freq_mhz);
static DEVICE_ATTR_RO(clk_cur_freq_mhz);
static struct attribute *hl_dev_clk_attrs[] = {
&dev_attr_clk_max_freq_mhz.attr,
&dev_attr_clk_cur_freq_mhz.attr,
NULL,
};
static ssize_t vrm_ver_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
struct cpucp_info *cpucp_info;
u32 infineon_second_stage_version;
u32 infineon_second_stage_first_instance;
u32 infineon_second_stage_second_instance;
u32 infineon_second_stage_third_instance;
u32 mask = 0xff;
cpucp_info = &hdev->asic_prop.cpucp_info;
infineon_second_stage_version = le32_to_cpu(cpucp_info->infineon_second_stage_version);
infineon_second_stage_first_instance = infineon_second_stage_version & mask;
infineon_second_stage_second_instance =
(infineon_second_stage_version >> 8) & mask;
infineon_second_stage_third_instance =
(infineon_second_stage_version >> 16) & mask;
if (cpucp_info->infineon_version && cpucp_info->infineon_second_stage_version)
return sprintf(buf, "%#04x %#04x:%#04x:%#04x\n",
le32_to_cpu(cpucp_info->infineon_version),
infineon_second_stage_first_instance,
infineon_second_stage_second_instance,
infineon_second_stage_third_instance);
else if (cpucp_info->infineon_second_stage_version)
return sprintf(buf, "%#04x:%#04x:%#04x\n",
infineon_second_stage_first_instance,
infineon_second_stage_second_instance,
infineon_second_stage_third_instance);
else if (cpucp_info->infineon_version)
return sprintf(buf, "%#04x\n", le32_to_cpu(cpucp_info->infineon_version));
return 0;
}
static DEVICE_ATTR_RO(vrm_ver);
static struct attribute *hl_dev_vrm_attrs[] = {
&dev_attr_vrm_ver.attr,
NULL,
};
static ssize_t uboot_ver_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", hdev->asic_prop.uboot_ver);
}
static ssize_t armcp_kernel_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s", hdev->asic_prop.cpucp_info.kernel_version);
}
static ssize_t armcp_ver_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", hdev->asic_prop.cpucp_info.cpucp_version);
}
static ssize_t cpld_ver_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "0x%08x%08x\n",
le32_to_cpu(hdev->asic_prop.cpucp_info.cpld_timestamp),
le32_to_cpu(hdev->asic_prop.cpucp_info.cpld_version));
}
static ssize_t cpucp_kernel_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s", hdev->asic_prop.cpucp_info.kernel_version);
}
static ssize_t cpucp_ver_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", hdev->asic_prop.cpucp_info.cpucp_version);
}
static ssize_t fuse_ver_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", hdev->asic_prop.cpucp_info.fuse_version);
}
static ssize_t thermal_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s", hdev->asic_prop.cpucp_info.thermal_version);
}
static ssize_t fw_os_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s", hdev->asic_prop.cpucp_info.fw_os_version);
}
static ssize_t preboot_btl_ver_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", hdev->asic_prop.preboot_ver);
}
static ssize_t soft_reset_store(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct hl_device *hdev = dev_get_drvdata(dev);
long value;
int rc;
rc = kstrtoul(buf, 0, &value);
if (rc) {
count = -EINVAL;
goto out;
}
if (!hdev->asic_prop.allow_inference_soft_reset) {
dev_err(hdev->dev, "Device does not support inference soft-reset\n");
goto out;
}
dev_warn(hdev->dev, "Inference Soft-Reset requested through sysfs\n");
hl_device_reset(hdev, 0);
out:
return count;
}
static ssize_t hard_reset_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct hl_device *hdev = dev_get_drvdata(dev);
long value;
int rc;
rc = kstrtoul(buf, 0, &value);
if (rc) {
count = -EINVAL;
goto out;
}
dev_warn(hdev->dev, "Hard-Reset requested through sysfs\n");
hl_device_reset(hdev, HL_DRV_RESET_HARD);
out:
return count;
}
static ssize_t device_type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
char *str;
switch (hdev->asic_type) {
case ASIC_GOYA:
str = "GOYA";
break;
case ASIC_GAUDI:
str = "GAUDI";
break;
case ASIC_GAUDI_SEC:
str = "GAUDI SEC";
break;
case ASIC_GAUDI2:
str = "GAUDI2";
break;
case ASIC_GAUDI2B:
str = "GAUDI2B";
break;
case ASIC_GAUDI2C:
str = "GAUDI2C";
break;
case ASIC_GAUDI2D:
str = "GAUDI2D";
break;
default:
dev_err(hdev->dev, "Unrecognized ASIC type %d\n",
hdev->asic_type);
return -EINVAL;
}
return sprintf(buf, "%s\n", str);
}
static ssize_t pci_addr_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%04x:%02x:%02x.%x\n",
pci_domain_nr(hdev->pdev->bus),
hdev->pdev->bus->number,
PCI_SLOT(hdev->pdev->devfn),
PCI_FUNC(hdev->pdev->devfn));
}
static ssize_t status_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
char str[HL_STR_MAX];
strscpy(str, hdev->status[hl_device_status(hdev)], HL_STR_MAX);
/* use uppercase for backward compatibility */
str[0] = 'A' + (str[0] - 'a');
return sprintf(buf, "%s\n", str);
}
static ssize_t soft_reset_cnt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->reset_info.compute_reset_cnt);
}
static ssize_t hard_reset_cnt_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->reset_info.hard_reset_cnt);
}
static ssize_t max_power_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
long val;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
val = hl_fw_get_max_power(hdev);
if (val < 0)
return val;
return sprintf(buf, "%lu\n", val);
}
static ssize_t max_power_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct hl_device *hdev = dev_get_drvdata(dev);
unsigned long value;
int rc;
if (!hl_device_operational(hdev, NULL)) {
count = -ENODEV;
goto out;
}
rc = kstrtoul(buf, 0, &value);
if (rc) {
count = -EINVAL;
goto out;
}
hdev->max_power = value;
hl_fw_set_max_power(hdev);
out:
return count;
}
static ssize_t eeprom_read_handler(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buf, loff_t offset,
size_t max_size)
{
struct device *dev = kobj_to_dev(kobj);
struct hl_device *hdev = dev_get_drvdata(dev);
char *data;
int rc;
if (!hl_device_operational(hdev, NULL))
return -ENODEV;
if (!max_size)
return -EINVAL;
data = kzalloc(max_size, GFP_KERNEL);
if (!data)
return -ENOMEM;
rc = hdev->asic_funcs->get_eeprom_data(hdev, data, max_size);
if (rc)
goto out;
memcpy(buf, data, max_size);
out:
kfree(data);
return max_size;
}
static ssize_t security_enabled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", hdev->asic_prop.fw_security_enabled);
}
static ssize_t module_id_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", le32_to_cpu(hdev->asic_prop.cpucp_info.card_location));
}
static ssize_t parent_device_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct hl_device *hdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", HL_DEV_NAME(hdev));
}
static DEVICE_ATTR_RO(armcp_kernel_ver);
static DEVICE_ATTR_RO(armcp_ver);
static DEVICE_ATTR_RO(cpld_ver);
static DEVICE_ATTR_RO(cpucp_kernel_ver);
static DEVICE_ATTR_RO(cpucp_ver);
static DEVICE_ATTR_RO(device_type);
static DEVICE_ATTR_RO(fuse_ver);
static DEVICE_ATTR_WO(hard_reset);
static DEVICE_ATTR_RO(hard_reset_cnt);
static DEVICE_ATTR_RW(max_power);
static DEVICE_ATTR_RO(pci_addr);
static DEVICE_ATTR_RO(preboot_btl_ver);
static DEVICE_ATTR_WO(soft_reset);
static DEVICE_ATTR_RO(soft_reset_cnt);
static DEVICE_ATTR_RO(status);
static DEVICE_ATTR_RO(thermal_ver);
static DEVICE_ATTR_RO(uboot_ver);
static DEVICE_ATTR_RO(fw_os_ver);
static DEVICE_ATTR_RO(security_enabled);
static DEVICE_ATTR_RO(module_id);
static DEVICE_ATTR_RO(parent_device);
static const struct bin_attribute bin_attr_eeprom = {
.attr = {.name = "eeprom", .mode = (0444)},
.size = PAGE_SIZE,
.read = eeprom_read_handler
};
static struct attribute *hl_dev_attrs[] = {
&dev_attr_armcp_kernel_ver.attr,
&dev_attr_armcp_ver.attr,
&dev_attr_cpld_ver.attr,
&dev_attr_cpucp_kernel_ver.attr,
&dev_attr_cpucp_ver.attr,
&dev_attr_device_type.attr,
&dev_attr_fuse_ver.attr,
&dev_attr_hard_reset.attr,
&dev_attr_hard_reset_cnt.attr,
&dev_attr_max_power.attr,
&dev_attr_pci_addr.attr,
&dev_attr_preboot_btl_ver.attr,
&dev_attr_status.attr,
&dev_attr_thermal_ver.attr,
&dev_attr_uboot_ver.attr,
&dev_attr_fw_os_ver.attr,
&dev_attr_security_enabled.attr,
&dev_attr_module_id.attr,
&dev_attr_parent_device.attr,
NULL,
};
static const struct bin_attribute *const hl_dev_bin_attrs[] = {
&bin_attr_eeprom,
NULL
};
static struct attribute_group hl_dev_attr_group = {
.attrs = hl_dev_attrs,
.bin_attrs = hl_dev_bin_attrs,
};
static struct attribute_group hl_dev_clks_attr_group;
static struct attribute_group hl_dev_vrm_attr_group;
static const struct attribute_group *hl_dev_attr_groups[] = {
&hl_dev_attr_group,
&hl_dev_clks_attr_group,
&hl_dev_vrm_attr_group,
NULL,
};
static struct attribute *hl_dev_inference_attrs[] = {
&dev_attr_soft_reset.attr,
&dev_attr_soft_reset_cnt.attr,
NULL,
};
static struct attribute_group hl_dev_inference_attr_group = {
.attrs = hl_dev_inference_attrs,
};
static const struct attribute_group *hl_dev_inference_attr_groups[] = {
&hl_dev_inference_attr_group,
NULL,
};
void hl_sysfs_add_dev_clk_attr(struct hl_device *hdev, struct attribute_group *dev_clk_attr_grp)
{
dev_clk_attr_grp->attrs = hl_dev_clk_attrs;
}
void hl_sysfs_add_dev_vrm_attr(struct hl_device *hdev, struct attribute_group *dev_vrm_attr_grp)
{
dev_vrm_attr_grp->attrs = hl_dev_vrm_attrs;
}
int hl_sysfs_init(struct hl_device *hdev)
{
int rc;
hdev->max_power = hdev->asic_prop.max_power_default;
hdev->asic_funcs->add_device_attr(hdev, &hl_dev_clks_attr_group, &hl_dev_vrm_attr_group);
rc = device_add_groups(hdev->dev, hl_dev_attr_groups);
if (rc) {
dev_err(hdev->dev,
"Failed to add groups to device, error %d\n", rc);
return rc;
}
if (!hdev->asic_prop.allow_inference_soft_reset)
return 0;
rc = device_add_groups(hdev->dev, hl_dev_inference_attr_groups);
if (rc) {
dev_err(hdev->dev,
"Failed to add groups to device, error %d\n", rc);
goto remove_groups;
}
return 0;
remove_groups:
device_remove_groups(hdev->dev, hl_dev_attr_groups);
return rc;
}
void hl_sysfs_fini(struct hl_device *hdev)
{
device_remove_groups(hdev->dev, hl_dev_attr_groups);
if (!hdev->asic_prop.allow_inference_soft_reset)
return;
device_remove_groups(hdev->dev, hl_dev_inference_attr_groups);
}