diff --git a/drivers/gpu/nvgpu/include/nvgpu/posix/io.h b/drivers/gpu/nvgpu/include/nvgpu/posix/io.h new file mode 100644 index 000000000..98be4d00f --- /dev/null +++ b/drivers/gpu/nvgpu/include/nvgpu/posix/io.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef NVGPU_POSIX_IO_H +#define NVGPU_POSIX_IO_H + +#include +#include + +struct gk20a; + +/** + * Here lies the interface for a unit test module to interact with the nvgpu IO + * accessors. This interface provides the ability for a module to react to nvgpu + * calling nvgpu IO accessors so that nvgpu can handle various HW sequences even + * when run in unit testing mode. + * + * The primary interface is simply callbacks to the unit test module which the + * module can handle how ever it wishes. + */ + +struct nvgpu_reg_access { + /* + * Address of the register write relative to the base of the register + * space. I.e you can compare this against values in the HW headers + * directly to check what register is being read/written to/from. + */ + u32 addr; + + /* + * Writes: this is the value being written. + * Reads: populate with the value to return. + */ + u32 value; +}; + +struct nvgpu_posix_io_callbacks { + void (*writel)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*writel_check)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*__readl)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*readl)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*bar1_writel)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*bar1_readl)(struct gk20a *g, struct nvgpu_reg_access *access); + void (*usermode_writel)(struct gk20a *g, + struct nvgpu_reg_access *access); +}; + +struct nvgpu_posix_io_callbacks *nvgpu_posix_register_io( + struct gk20a *g, + struct nvgpu_posix_io_callbacks *io_callbacks); + +struct nvgpu_posix_io_reg_space { + u32 base; + u32 size; + u32 *data; + struct nvgpu_list_node link; +}; + +static inline struct nvgpu_posix_io_reg_space * +nvgpu_posix_io_reg_space_from_link(struct nvgpu_list_node *node) +{ + return (struct nvgpu_posix_io_reg_space *) + ((uintptr_t)node - offsetof(struct nvgpu_posix_io_reg_space, link)); +}; + +void nvgpu_posix_io_init_reg_space(struct gk20a *g); +int nvgpu_posix_io_get_error_code(struct gk20a *g); +void nvgpu_posix_io_reset_error_code(struct gk20a *g); +int nvgpu_posix_io_add_reg_space(struct gk20a *g, u32 base, u32 size); +struct nvgpu_posix_io_reg_space *nvgpu_posix_io_get_reg_space(struct gk20a *g, + u32 addr); +void nvgpu_posix_io_delete_reg_space(struct gk20a *g, u32 base); +void nvgpu_posix_io_writel_reg_space(struct gk20a *g, u32 addr, u32 data); +u32 nvgpu_posix_io_readl_reg_space(struct gk20a *g, u32 addr); + +struct nvgpu_posix_io_reg_access { + struct nvgpu_reg_access access; + struct nvgpu_list_node link; +}; + +static inline struct nvgpu_posix_io_reg_access * +nvgpu_posix_io_reg_access_from_link(struct nvgpu_list_node *node) +{ + return (struct nvgpu_posix_io_reg_access *) + ((uintptr_t)node - offsetof(struct nvgpu_posix_io_reg_access, link)); +}; + +void nvgpu_posix_io_start_recorder(struct gk20a *g); +void nvgpu_posix_io_reset_recorder(struct gk20a *g); +void nvgpu_posix_io_record_access(struct gk20a *g, + struct nvgpu_reg_access *access); +bool nvgpu_posix_io_check_sequence(struct gk20a *g, + struct nvgpu_reg_access *sequence, u32 size, bool strict); + +#endif diff --git a/drivers/gpu/nvgpu/os/posix/io.c b/drivers/gpu/nvgpu/os/posix/io.c index 7bab8af6f..8369e73f6 100644 --- a/drivers/gpu/nvgpu/os/posix/io.c +++ b/drivers/gpu/nvgpu/os/posix/io.c @@ -24,15 +24,45 @@ #include #include +#include + +#include "os_posix.h" + + /* - * For now none of these make sense to execute in userspace. Eventually we - * may want to use these to verify certain register read/write sequences - * but for now, just hang. + * This function sets the IO callbacks to the passed set of callbacks. It + * returns the value of the old IO callback struct pointer. This function + * cannot fail. + * + * This is expected to be called from modules to set up their IO interaction. */ +struct nvgpu_posix_io_callbacks *nvgpu_posix_register_io( + struct gk20a *g, + struct nvgpu_posix_io_callbacks *io_callbacks) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + struct nvgpu_posix_io_callbacks *old_io = p->callbacks; + + p->callbacks = io_callbacks; + + return old_io; +} void nvgpu_writel(struct gk20a *g, u32 r, u32 v) { - BUG(); + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; + + struct nvgpu_reg_access access = { + .addr = r, + .value = v + }; + + if (callbacks == NULL || callbacks->writel == NULL) { + BUG(); + } + + callbacks->writel(g, &access); } void nvgpu_writel_relaxed(struct gk20a *g, u32 r, u32 v) @@ -42,16 +72,21 @@ void nvgpu_writel_relaxed(struct gk20a *g, u32 r, u32 v) u32 nvgpu_readl(struct gk20a *g, u32 r) { - BUG(); + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; - return 0; -} + struct nvgpu_reg_access access = { + .addr = r, + .value = 0L + }; -u32 __nvgpu_readl(struct gk20a *g, u32 r) -{ - BUG(); + if (callbacks == NULL || callbacks->readl == NULL) { + BUG(); + } - return 0; + callbacks->readl(g, &access); + + return access.value; } void nvgpu_writel_loop(struct gk20a *g, u32 r, u32 v) @@ -59,16 +94,76 @@ void nvgpu_writel_loop(struct gk20a *g, u32 r, u32 v) BUG(); } +u32 __nvgpu_readl(struct gk20a *g, u32 r) +{ + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; + + struct nvgpu_reg_access access = { + .addr = r, + .value = 0L + }; + + if (callbacks == NULL || callbacks->__readl == NULL) { + BUG(); + } + + callbacks->__readl(g, &access); + + return access.value; +} + void nvgpu_bar1_writel(struct gk20a *g, u32 b, u32 v) { - BUG(); + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; + + struct nvgpu_reg_access access = { + .addr = b, + .value = v + }; + + if (callbacks == NULL || callbacks->bar1_writel == NULL) { + BUG(); + } + + callbacks->bar1_writel(g, &access); } u32 nvgpu_bar1_readl(struct gk20a *g, u32 b) { - BUG(); + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; - return 0; + struct nvgpu_reg_access access = { + .addr = b, + .value = 0L + }; + + if (callbacks == NULL || callbacks->bar1_readl == NULL) { + BUG(); + } + + callbacks->bar1_readl(g, &access); + + return access.value; +} + +void nvgpu_usermode_writel(struct gk20a *g, u32 r, u32 v) +{ + struct nvgpu_posix_io_callbacks *callbacks = + nvgpu_os_posix_from_gk20a(g)->callbacks; + + struct nvgpu_reg_access access = { + .addr = r, + .value = v + }; + + if (callbacks == NULL || callbacks->usermode_writel == NULL) { + BUG(); + } + + callbacks->usermode_writel(g, &access); } bool nvgpu_io_exists(struct gk20a *g) @@ -81,7 +176,196 @@ bool nvgpu_io_valid_reg(struct gk20a *g, u32 r) return false; } -void nvgpu_usermode_writel(struct gk20a *g, u32 r, u32 v) +void nvgpu_posix_io_init_reg_space(struct gk20a *g) { - BUG(); + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + + p->recording = false; + p->error_code = 0; + nvgpu_init_list_node(&p->reg_space_head); + nvgpu_init_list_node(&p->recorder_head); +} + +int nvgpu_posix_io_get_error_code(struct gk20a *g) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + + return p->error_code; +} + +void nvgpu_posix_io_reset_error_code(struct gk20a *g) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + + p->error_code = 0; +} + +/* + * Add a new register space to the list of spaces, defined by a base + * address and a size. + */ +int nvgpu_posix_io_add_reg_space(struct gk20a *g, u32 base, u32 size) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + struct nvgpu_posix_io_reg_space *new_reg_space = + nvgpu_kzalloc(g, sizeof(struct nvgpu_posix_io_reg_space)); + + if (new_reg_space == NULL) { + return -ENOMEM; + } + + new_reg_space->base = base; + new_reg_space->size = size; + + new_reg_space->data = nvgpu_vzalloc(g, size); + if (new_reg_space->data == NULL) { + return -ENOMEM; + } + + nvgpu_list_add_tail(&new_reg_space->link, &p->reg_space_head); + return 0; +} + +void nvgpu_posix_io_delete_reg_space(struct gk20a *g, u32 base) +{ + struct nvgpu_posix_io_reg_space *reg_space = + nvgpu_posix_io_get_reg_space(g, base); + if (reg_space == NULL) { + /* Invalid space, or already de-allocated */ + return; + } + nvgpu_list_del(®_space->link); + nvgpu_vfree(g, reg_space->data); + nvgpu_kfree(g, reg_space); +} + +/* + * Lookup a register space from a given address. If no register space is found + * this is a bug similar to a translation fault. + */ +struct nvgpu_posix_io_reg_space *nvgpu_posix_io_get_reg_space(struct gk20a *g, + u32 addr) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + struct nvgpu_posix_io_reg_space *reg_space; + + nvgpu_list_for_each_entry(reg_space, &p->reg_space_head, + nvgpu_posix_io_reg_space, link) { + u32 offset = addr - reg_space->base; + + if ((addr >= reg_space->base) && (offset <= reg_space->size)) { + return reg_space; + } + } + p->error_code = -EFAULT; + nvgpu_err(g, "ABORT for address 0x%x", addr); + return NULL; +} + +void nvgpu_posix_io_writel_reg_space(struct gk20a *g, u32 addr, u32 data) +{ + struct nvgpu_posix_io_reg_space *space = + nvgpu_posix_io_get_reg_space(g, addr); + + if (space != NULL) { + u32 offset = (addr - space->base) / ((u32) sizeof(u32)); + + *(space->data + offset) = data; + } +} + +u32 nvgpu_posix_io_readl_reg_space(struct gk20a *g, u32 addr) +{ + struct nvgpu_posix_io_reg_space *space = + nvgpu_posix_io_get_reg_space(g, addr); + + if (space != NULL) { + u32 offset = (addr - space->base) / ((u32) sizeof(u32)); + + return *(space->data + offset); + } else { + return 0; + } +} + +/* + * Start recording register writes. If this function is called again, + * it will free all previously recorded events. + */ +void nvgpu_posix_io_start_recorder(struct gk20a *g) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + struct nvgpu_posix_io_reg_access *ptr; + + /* If list already has events, delete them all */ + if (p->recording == true) { + while (!nvgpu_list_empty(&p->recorder_head)) { + ptr = nvgpu_list_first_entry(&p->recorder_head, + nvgpu_posix_io_reg_access, link); + nvgpu_list_del(&ptr->link); + nvgpu_kfree(g, ptr); + } + } + p->recording = true; +} + +void nvgpu_posix_io_record_access(struct gk20a *g, + struct nvgpu_reg_access *access) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + + if (p->recording == true) { + struct nvgpu_posix_io_reg_access *new_event = nvgpu_kzalloc(g, + sizeof(struct nvgpu_posix_io_reg_access)); + (void) memcpy(&(new_event->access), access, + sizeof(struct nvgpu_reg_access)); + nvgpu_list_add_tail(&new_event->link, &p->recorder_head); + } +} + +/* + * Take an array of accesses and compare to the recorded sequence. Returns true + * if the array matches the recorded sequence. + * If strict mode is false, this function allows extra accesses to be present + * in the recording. + */ +bool nvgpu_posix_io_check_sequence(struct gk20a *g, + struct nvgpu_reg_access *sequence, u32 size, bool strict) +{ + struct nvgpu_os_posix *p = nvgpu_os_posix_from_gk20a(g); + struct nvgpu_posix_io_reg_access *ptr; + u32 i = 0; + + if (p->recording == false) { + return false; + } + + nvgpu_list_for_each_entry(ptr, &p->recorder_head, + nvgpu_posix_io_reg_access, link) { + if ((sequence[i].addr == ptr->access.addr) && + (sequence[i].value == ptr->access.value)) { + i++; + } else { + if (strict == true) { + return false; + } + } + } + + if (i != size) { + /* Either missing or too many accesses */ + return false; + } + + if (&ptr->link == &p->recorder_head) { + /* Identical match */ + return true; + } + + /* Not an identical match */ + if (strict) { + return false; + } else { + return true; + } } diff --git a/drivers/gpu/nvgpu/os/posix/os_posix.h b/drivers/gpu/nvgpu/os/posix/os_posix.h index 955186ef6..ff8c98173 100644 --- a/drivers/gpu/nvgpu/os/posix/os_posix.h +++ b/drivers/gpu/nvgpu/os/posix/os_posix.h @@ -25,8 +25,34 @@ #include "gk20a/gk20a.h" +struct nvgpu_posix_io_callbacks; + struct nvgpu_os_posix { struct gk20a g; + + + /* + * IO callbacks for handling the nvgpu IO accessors. + */ + struct nvgpu_posix_io_callbacks *callbacks; + + /* + * Memory-mapped register space for unit tests. + */ + struct nvgpu_list_node reg_space_head; + int error_code; + + + /* + * List to record sequence of register writes. + */ + struct nvgpu_list_node recorder_head; + bool recording; }; +static inline struct nvgpu_os_posix *nvgpu_os_posix_from_gk20a(struct gk20a *g) +{ + return container_of(g, struct nvgpu_os_posix, g); +} + #endif