2020-02-20 00:49:22

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 0/7] firmware: add partial read support in request_firmware_into_buf

This patch series adds partial read support in request_firmware_into_buf.
In order to accept the enhanced API it has been requested that kernel
selftests and upstreamed driver utilize the API enhancement and so
are included in this patch series.

Also in this patch series is the addition of a new Broadcom VK driver
utilizing the new request_firmware_into_buf enhanced API.

Scott Branden (7):
fs: introduce kernel_pread_file* support
firmware: add offset to request_firmware_into_buf
test_firmware: add partial read support for request_firmware_into_buf
firmware: test partial file reads of request_firmware_into_buf
bcm-vk: add bcm_vk UAPI
misc: bcm-vk: add Broadcom VK driver
MAINTAINERS: bcm-vk: add maintainer for Broadcom VK Driver

MAINTAINERS | 7 +
drivers/base/firmware_loader/firmware.h | 5 +
drivers/base/firmware_loader/main.c | 49 +-
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/bcm-vk/Kconfig | 42 +
drivers/misc/bcm-vk/Makefile | 11 +
drivers/misc/bcm-vk/bcm_vk.h | 357 +++++
drivers/misc/bcm-vk/bcm_vk_dev.c | 1197 +++++++++++++++
drivers/misc/bcm-vk/bcm_vk_msg.c | 1359 +++++++++++++++++
drivers/misc/bcm-vk/bcm_vk_msg.h | 210 +++
drivers/misc/bcm-vk/bcm_vk_sg.c | 273 ++++
drivers/misc/bcm-vk/bcm_vk_sg.h | 60 +
drivers/misc/bcm-vk/bcm_vk_tty.c | 327 ++++
drivers/soc/qcom/mdt_loader.c | 7 +-
fs/exec.c | 77 +-
include/linux/firmware.h | 8 +-
include/linux/fs.h | 15 +
include/uapi/linux/misc/bcm_vk.h | 117 ++
lib/test_firmware.c | 139 +-
.../selftests/firmware/fw_filesystem.sh | 80 +
21 files changed, 4305 insertions(+), 37 deletions(-)
create mode 100644 drivers/misc/bcm-vk/Kconfig
create mode 100644 drivers/misc/bcm-vk/Makefile
create mode 100644 drivers/misc/bcm-vk/bcm_vk.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_dev.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_tty.c
create mode 100644 include/uapi/linux/misc/bcm_vk.h

--
2.17.1


2020-02-20 00:49:23

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 2/7] firmware: add offset to request_firmware_into_buf

Add offset to request_firmware_into_buf to allow for portions
of firmware file to be read into a buffer. Necessary where firmware
needs to be loaded in portions from file in memory constrained systems.

Signed-off-by: Scott Branden <[email protected]>
---
drivers/base/firmware_loader/firmware.h | 5 +++
drivers/base/firmware_loader/main.c | 49 +++++++++++++++++--------
drivers/soc/qcom/mdt_loader.c | 7 +++-
include/linux/firmware.h | 8 +++-
lib/test_firmware.c | 4 +-
5 files changed, 53 insertions(+), 20 deletions(-)

diff --git a/drivers/base/firmware_loader/firmware.h b/drivers/base/firmware_loader/firmware.h
index 8656e5239a80..9f4fddcb1ab7 100644
--- a/drivers/base/firmware_loader/firmware.h
+++ b/drivers/base/firmware_loader/firmware.h
@@ -29,6 +29,8 @@
* firmware caching mechanism.
* @FW_OPT_NOFALLBACK_SYSFS: Disable the sysfs fallback mechanism. Takes
* precedence over &FW_OPT_UEVENT and &FW_OPT_USERHELPER.
+ * @FW_OPT_PARTIAL: Allow partial read of firmware instead of needing to read
+ * entire file.
*/
enum fw_opt {
FW_OPT_UEVENT = BIT(0),
@@ -37,6 +39,7 @@ enum fw_opt {
FW_OPT_NO_WARN = BIT(3),
FW_OPT_NOCACHE = BIT(4),
FW_OPT_NOFALLBACK_SYSFS = BIT(5),
+ FW_OPT_PARTIAL = BIT(6),
};

enum fw_status {
@@ -64,6 +67,8 @@ struct fw_priv {
void *data;
size_t size;
size_t allocated_size;
+ size_t offset;
+ unsigned int flags;
#ifdef CONFIG_FW_LOADER_PAGED_BUF
bool is_paged_buf;
struct page **pages;
diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index 57133a9dad09..28312309aab4 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -167,7 +167,8 @@ static int fw_cache_piggyback_on_request(const char *name);

static struct fw_priv *__allocate_fw_priv(const char *fw_name,
struct firmware_cache *fwc,
- void *dbuf, size_t size)
+ void *dbuf, size_t size,
+ size_t offset, unsigned int flags)
{
struct fw_priv *fw_priv;

@@ -185,6 +186,8 @@ static struct fw_priv *__allocate_fw_priv(const char *fw_name,
fw_priv->fwc = fwc;
fw_priv->data = dbuf;
fw_priv->allocated_size = size;
+ fw_priv->offset = offset;
+ fw_priv->flags = flags;
fw_state_init(fw_priv);
#ifdef CONFIG_FW_LOADER_USER_HELPER
INIT_LIST_HEAD(&fw_priv->pending_list);
@@ -210,9 +213,11 @@ static struct fw_priv *__lookup_fw_priv(const char *fw_name)
static int alloc_lookup_fw_priv(const char *fw_name,
struct firmware_cache *fwc,
struct fw_priv **fw_priv, void *dbuf,
- size_t size, enum fw_opt opt_flags)
+ size_t size, enum fw_opt opt_flags,
+ size_t offset)
{
struct fw_priv *tmp;
+ unsigned int pread_flags;

spin_lock(&fwc->lock);
if (!(opt_flags & FW_OPT_NOCACHE)) {
@@ -226,7 +231,12 @@ static int alloc_lookup_fw_priv(const char *fw_name,
}
}

- tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size);
+ if (opt_flags & FW_OPT_PARTIAL)
+ pread_flags = KERNEL_PREAD_FLAG_PART;
+ else
+ pread_flags = KERNEL_PREAD_FLAG_WHOLE;
+
+ tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, pread_flags);
if (tmp) {
INIT_LIST_HEAD(&tmp->list);
if (!(opt_flags & FW_OPT_NOCACHE))
@@ -493,8 +503,9 @@ fw_get_filesystem_firmware(struct device *device, struct fw_priv *fw_priv,
}

fw_priv->size = 0;
- rc = kernel_read_file_from_path(path, &buffer, &size,
- msize, id);
+ rc = kernel_pread_file_from_path(path, &buffer, &size,
+ fw_priv->offset, msize,
+ fw_priv->flags, id);
if (rc) {
if (rc != -ENOENT)
dev_warn(device, "loading %s failed with error %d\n",
@@ -685,7 +696,7 @@ int assign_fw(struct firmware *fw, struct device *device,
static int
_request_firmware_prepare(struct firmware **firmware_p, const char *name,
struct device *device, void *dbuf, size_t size,
- enum fw_opt opt_flags)
+ enum fw_opt opt_flags, size_t offset)
{
struct firmware *firmware;
struct fw_priv *fw_priv;
@@ -704,7 +715,7 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
}

ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size,
- opt_flags);
+ opt_flags, offset);

/*
* bind with 'priv' now to avoid warning in failure path
@@ -751,7 +762,7 @@ static void fw_abort_batch_reqs(struct firmware *fw)
static int
_request_firmware(const struct firmware **firmware_p, const char *name,
struct device *device, void *buf, size_t size,
- enum fw_opt opt_flags)
+ enum fw_opt opt_flags, size_t offset)
{
struct firmware *fw = NULL;
int ret;
@@ -765,7 +776,7 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
}

ret = _request_firmware_prepare(&fw, name, device, buf, size,
- opt_flags);
+ opt_flags, offset);
if (ret <= 0) /* error or already assigned */
goto out;

@@ -825,7 +836,7 @@ request_firmware(const struct firmware **firmware_p, const char *name,
/* Need to pin this module until return */
__module_get(THIS_MODULE);
ret = _request_firmware(firmware_p, name, device, NULL, 0,
- FW_OPT_UEVENT);
+ FW_OPT_UEVENT, 0);
module_put(THIS_MODULE);
return ret;
}
@@ -852,7 +863,7 @@ int firmware_request_nowarn(const struct firmware **firmware, const char *name,
/* Need to pin this module until return */
__module_get(THIS_MODULE);
ret = _request_firmware(firmware, name, device, NULL, 0,
- FW_OPT_UEVENT | FW_OPT_NO_WARN);
+ FW_OPT_UEVENT | FW_OPT_NO_WARN, 0);
module_put(THIS_MODULE);
return ret;
}
@@ -877,7 +888,7 @@ int request_firmware_direct(const struct firmware **firmware_p,
__module_get(THIS_MODULE);
ret = _request_firmware(firmware_p, name, device, NULL, 0,
FW_OPT_UEVENT | FW_OPT_NO_WARN |
- FW_OPT_NOFALLBACK_SYSFS);
+ FW_OPT_NOFALLBACK_SYSFS, 0);
module_put(THIS_MODULE);
return ret;
}
@@ -914,6 +925,8 @@ EXPORT_SYMBOL_GPL(firmware_request_cache);
* @device: device for which firmware is being loaded and DMA region allocated
* @buf: address of buffer to load firmware into
* @size: size of buffer
+ * @offset: offset into file to read
+ * @pread_flags: KERNEL_PREAD_FLAG_PART to allow partial file read
*
* This function works pretty much like request_firmware(), but it doesn't
* allocate a buffer to hold the firmware data. Instead, the firmware
@@ -924,16 +937,22 @@ EXPORT_SYMBOL_GPL(firmware_request_cache);
*/
int
request_firmware_into_buf(const struct firmware **firmware_p, const char *name,
- struct device *device, void *buf, size_t size)
+ struct device *device, void *buf, size_t size,
+ size_t offset, unsigned int pread_flags)
{
int ret;
+ enum fw_opt opt_flags;

if (fw_cache_is_setup(device, name))
return -EOPNOTSUPP;

__module_get(THIS_MODULE);
+ opt_flags = FW_OPT_UEVENT | FW_OPT_NOCACHE;
+ if (pread_flags & KERNEL_PREAD_FLAG_PART)
+ opt_flags |= FW_OPT_PARTIAL;
+
ret = _request_firmware(firmware_p, name, device, buf, size,
- FW_OPT_UEVENT | FW_OPT_NOCACHE);
+ opt_flags, offset);
module_put(THIS_MODULE);
return ret;
}
@@ -972,7 +991,7 @@ static void request_firmware_work_func(struct work_struct *work)
fw_work = container_of(work, struct firmware_work, work);

_request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
- fw_work->opt_flags);
+ fw_work->opt_flags, 0);
fw_work->cont(fw, fw_work->context);
put_device(fw_work->device); /* taken in request_firmware_nowait() */

diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c
index 24cd193dec55..00f3359f4f61 100644
--- a/drivers/soc/qcom/mdt_loader.c
+++ b/drivers/soc/qcom/mdt_loader.c
@@ -246,8 +246,11 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
} else if (phdr->p_filesz) {
/* Firmware not large enough, load split-out segments */
sprintf(fw_name + fw_name_len - 3, "b%02d", i);
- ret = request_firmware_into_buf(&seg_fw, fw_name, dev,
- ptr, phdr->p_filesz);
+ ret = request_firmware_into_buf
+ (&seg_fw, fw_name, dev,
+ ptr, phdr->p_filesz,
+ 0,
+ KERNEL_PREAD_FLAG_WHOLE);
if (ret) {
dev_err(dev, "failed to load %s\n", fw_name);
break;
diff --git a/include/linux/firmware.h b/include/linux/firmware.h
index 2dd566c91d44..c81162a8d709 100644
--- a/include/linux/firmware.h
+++ b/include/linux/firmware.h
@@ -4,6 +4,7 @@

#include <linux/types.h>
#include <linux/compiler.h>
+#include <linux/fs.h>
#include <linux/gfp.h>

#define FW_ACTION_NOHOTPLUG 0
@@ -51,7 +52,9 @@ int request_firmware_nowait(
int request_firmware_direct(const struct firmware **fw, const char *name,
struct device *device);
int request_firmware_into_buf(const struct firmware **firmware_p,
- const char *name, struct device *device, void *buf, size_t size);
+ const char *name, struct device *device,
+ void *buf, size_t size,
+ size_t offset, unsigned int pread_flags);

void release_firmware(const struct firmware *fw);
#else
@@ -89,7 +92,8 @@ static inline int request_firmware_direct(const struct firmware **fw,
}

static inline int request_firmware_into_buf(const struct firmware **firmware_p,
- const char *name, struct device *device, void *buf, size_t size)
+ const char *name, struct device *device, void *buf, size_t size,
+ size_t offset, unsigned int pread_flags);
{
return -EINVAL;
}
diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index 251213c872b5..7d1d97fa9a23 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -622,7 +622,9 @@ static int test_fw_run_batch_request(void *data)
req->name,
req->dev,
test_buf,
- TEST_FIRMWARE_BUF_SIZE);
+ TEST_FIRMWARE_BUF_SIZE,
+ 0,
+ KERNEL_PREAD_FLAG_WHOLE);
if (!req->fw)
kfree(test_buf);
} else {
--
2.17.1

2020-02-20 00:49:23

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 1/7] fs: introduce kernel_pread_file* support

Add kernel_pread_file* support to kernel to allow for partial read
of files with an offset into the file. Existing kernel_read_file
functions call new kernel_pread_file functions with offset=0 and
flags=KERNEL_PREAD_FLAG_WHOLE.

Signed-off-by: Scott Branden <[email protected]>
---
fs/exec.c | 77 ++++++++++++++++++++++++++++++++++++----------
include/linux/fs.h | 15 +++++++++
2 files changed, 75 insertions(+), 17 deletions(-)

diff --git a/fs/exec.c b/fs/exec.c
index db17be51b112..a38f8fb432fa 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -896,10 +896,14 @@ struct file *open_exec(const char *name)
}
EXPORT_SYMBOL(open_exec);

-int kernel_read_file(struct file *file, void **buf, loff_t *size,
- loff_t max_size, enum kernel_read_file_id id)
-{
- loff_t i_size, pos;
+int kernel_pread_file(struct file *file, void **buf, loff_t *size,
+ loff_t pos, loff_t max_size, unsigned int flags,
+ enum kernel_read_file_id id)
+{
+ loff_t alloc_size;
+ loff_t buf_pos;
+ loff_t read_end;
+ loff_t i_size;
ssize_t bytes = 0;
int ret;

@@ -919,21 +923,31 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size,
ret = -EINVAL;
goto out;
}
- if (i_size > SIZE_MAX || (max_size > 0 && i_size > max_size)) {
+
+ /* Default read to end of file */
+ read_end = i_size;
+
+ /* Allow reading partial portion of file */
+ if ((flags & KERNEL_PREAD_FLAG_PART) &&
+ (i_size > (pos + max_size)))
+ read_end = pos + max_size;
+
+ alloc_size = read_end - pos;
+ if (i_size > SIZE_MAX || (max_size > 0 && alloc_size > max_size)) {
ret = -EFBIG;
goto out;
}

if (id != READING_FIRMWARE_PREALLOC_BUFFER)
- *buf = vmalloc(i_size);
+ *buf = vmalloc(alloc_size);
if (!*buf) {
ret = -ENOMEM;
goto out;
}

- pos = 0;
- while (pos < i_size) {
- bytes = kernel_read(file, *buf + pos, i_size - pos, &pos);
+ buf_pos = 0;
+ while (pos < read_end) {
+ bytes = kernel_read(file, *buf + buf_pos, read_end - pos, &pos);
if (bytes < 0) {
ret = bytes;
goto out_free;
@@ -941,14 +955,16 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size,

if (bytes == 0)
break;
+
+ buf_pos += bytes;
}

- if (pos != i_size) {
+ if (pos != read_end) {
ret = -EIO;
goto out_free;
}

- ret = security_kernel_post_read_file(file, *buf, i_size, id);
+ ret = security_kernel_post_read_file(file, *buf, alloc_size, id);
if (!ret)
*size = pos;

@@ -964,10 +980,20 @@ int kernel_read_file(struct file *file, void **buf, loff_t *size,
allow_write_access(file);
return ret;
}
+EXPORT_SYMBOL_GPL(kernel_pread_file);
+
+int kernel_read_file(struct file *file, void **buf, loff_t *size,
+ loff_t max_size, enum kernel_read_file_id id)
+{
+ return kernel_pread_file(file, buf, size, 0, max_size,
+ KERNEL_PREAD_FLAG_WHOLE, id);
+}
EXPORT_SYMBOL_GPL(kernel_read_file);

-int kernel_read_file_from_path(const char *path, void **buf, loff_t *size,
- loff_t max_size, enum kernel_read_file_id id)
+int kernel_pread_file_from_path(const char *path, void **buf,
+ loff_t *size, loff_t pos,
+ loff_t max_size, unsigned int flags,
+ enum kernel_read_file_id id)
{
struct file *file;
int ret;
@@ -979,14 +1005,23 @@ int kernel_read_file_from_path(const char *path, void **buf, loff_t *size,
if (IS_ERR(file))
return PTR_ERR(file);

- ret = kernel_read_file(file, buf, size, max_size, id);
+ ret = kernel_pread_file(file, buf, size, pos, max_size, flags, id);
fput(file);
return ret;
}
+EXPORT_SYMBOL_GPL(kernel_pread_file_from_path);
+
+int kernel_read_file_from_path(const char *path, void **buf, loff_t *size,
+ loff_t max_size, enum kernel_read_file_id id)
+{
+ return kernel_pread_file_from_path(path, buf, size, 0, max_size,
+ KERNEL_PREAD_FLAG_WHOLE, id);
+}
EXPORT_SYMBOL_GPL(kernel_read_file_from_path);

-int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size,
- enum kernel_read_file_id id)
+int kernel_pread_file_from_fd(int fd, void **buf, loff_t *size, loff_t pos,
+ loff_t max_size, unsigned int flags,
+ enum kernel_read_file_id id)
{
struct fd f = fdget(fd);
int ret = -EBADF;
@@ -994,11 +1029,19 @@ int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size,
if (!f.file)
goto out;

- ret = kernel_read_file(f.file, buf, size, max_size, id);
+ ret = kernel_pread_file(f.file, buf, size, pos, max_size, flags, id);
out:
fdput(f);
return ret;
}
+EXPORT_SYMBOL_GPL(kernel_pread_file_from_fd);
+
+int kernel_read_file_from_fd(int fd, void **buf, loff_t *size, loff_t max_size,
+ enum kernel_read_file_id id)
+{
+ return kernel_pread_file_from_fd(fd, buf, size, 0, max_size,
+ KERNEL_PREAD_FLAG_WHOLE, id);
+}
EXPORT_SYMBOL_GPL(kernel_read_file_from_fd);

ssize_t read_code(struct file *file, unsigned long addr, loff_t pos, size_t len)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3cd4fe6b845e..8f39530cfcc2 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3008,10 +3008,25 @@ static inline const char *kernel_read_file_id_str(enum kernel_read_file_id id)
return kernel_read_file_str[id];
}

+/* Flags used by kernel_pread_file functions */
+#define KERNEL_PREAD_FLAG_WHOLE 0x0000 /* Only Allow reading of whole file */
+#define KERNEL_PREAD_FLAG_PART 0x0001 /* Allow reading part of file */
+
+extern int kernel_pread_file(struct file *file, void **buf, loff_t *size,
+ loff_t pos, loff_t max_size, unsigned int flags,
+ enum kernel_read_file_id id);
extern int kernel_read_file(struct file *, void **, loff_t *, loff_t,
enum kernel_read_file_id);
+extern int kernel_pread_file_from_path(const char *path, void **buf,
+ loff_t *size, loff_t pos,
+ loff_t max_size, unsigned int flags,
+ enum kernel_read_file_id id);
extern int kernel_read_file_from_path(const char *, void **, loff_t *, loff_t,
enum kernel_read_file_id);
+extern int kernel_pread_file_from_fd(int fd, void **buf, loff_t *size,
+ loff_t pos, loff_t max_size,
+ unsigned int flags,
+ enum kernel_read_file_id id);
extern int kernel_read_file_from_fd(int, void **, loff_t *, loff_t,
enum kernel_read_file_id);
extern ssize_t kernel_read(struct file *, void *, size_t, loff_t *);
--
2.17.1

2020-02-20 00:49:28

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 4/7] firmware: test partial file reads of request_firmware_into_buf

Add firmware tests for partial file reads of request_firmware_into_buf.

Signed-off-by: Scott Branden <[email protected]>
---
.../selftests/firmware/fw_filesystem.sh | 80 +++++++++++++++++++
1 file changed, 80 insertions(+)

diff --git a/tools/testing/selftests/firmware/fw_filesystem.sh b/tools/testing/selftests/firmware/fw_filesystem.sh
index 56894477c8bd..e973c658fe1a 100755
--- a/tools/testing/selftests/firmware/fw_filesystem.sh
+++ b/tools/testing/selftests/firmware/fw_filesystem.sh
@@ -126,6 +126,26 @@ config_unset_into_buf()
echo 0 > $DIR/config_into_buf
}

+config_set_buf_size()
+{
+ echo $1 > $DIR/config_buf_size
+}
+
+config_set_file_offset()
+{
+ echo $1 > $DIR/config_file_offset
+}
+
+config_set_partial()
+{
+ echo 1 > $DIR/config_partial
+}
+
+config_unset_partial()
+{
+ echo 0 > $DIR/config_partial
+}
+
config_set_sync_direct()
{
echo 1 > $DIR/config_sync_direct
@@ -184,6 +204,35 @@ read_firmwares()
done
}

+read_firmwares_partial()
+{
+ if [ "$(cat $DIR/config_into_buf)" == "1" ]; then
+ fwfile="${FW_INTO_BUF}"
+ else
+ fwfile="${FW}"
+ fi
+
+ if [ "$1" = "xzonly" ]; then
+ fwfile="${fwfile}-orig"
+ fi
+
+ # Strip fwfile down to match partial offset and length
+ partial_data="$(cat $fwfile)"
+ partial_data="${partial_data:$2:$3}"
+
+ for i in $(seq 0 3); do
+ config_set_read_fw_idx $i
+
+ read_firmware="$(cat $DIR/read_firmware)"
+
+ # Verify the contents are what we expect.
+ if [ $read_firmware != $partial_data ]; then
+ echo "request #$i: partial firmware was not loaded" >&2
+ exit 1
+ fi
+ done
+}
+
read_firmwares_expect_nofile()
{
for i in $(seq 0 3); do
@@ -296,6 +345,21 @@ test_batched_request_firmware_into_buf()
echo "OK"
}

+test_batched_request_firmware_into_buf_partial()
+{
+ echo -n "Batched request_firmware_into_buf_partial() $2 off=$3 size=$4 try #$1: "
+ config_reset
+ config_set_name $TEST_FIRMWARE_INTO_BUF_FILENAME
+ config_set_into_buf
+ config_set_partial
+ config_set_buf_size $4
+ config_set_file_offset $3
+ config_trigger_sync
+ read_firmwares_partial $2 $3 $4
+ release_all_firmware
+ echo "OK"
+}
+
test_batched_request_firmware_direct()
{
echo -n "Batched request_firmware_direct() $2 try #$1: "
@@ -348,6 +412,22 @@ for i in $(seq 1 5); do
test_batched_request_firmware_into_buf $i normal
done

+for i in $(seq 1 5); do
+ test_batched_request_firmware_into_buf_partial $i normal 0 10
+done
+
+for i in $(seq 1 5); do
+ test_batched_request_firmware_into_buf_partial $i normal 0 5
+done
+
+for i in $(seq 1 5); do
+ test_batched_request_firmware_into_buf_partial $i normal 1 6
+done
+
+for i in $(seq 1 5); do
+ test_batched_request_firmware_into_buf_partial $i normal 2 10
+done
+
for i in $(seq 1 5); do
test_batched_request_firmware_direct $i normal
done
--
2.17.1

2020-02-20 00:49:33

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Add Broadcom VK driver offload engine.
This driver interfaces to the VK PCIe offload engine to perform
should offload functions as video transcoding on multiple streams
in parallel. VK device is booted from files loaded using
request_firmware_into_buf mechanism. After booted card status is updated
and messages can then be sent to the card.
Such messages contain scatter gather list of addresses
to pull data from the host to perform operations on.

Signed-off-by: Scott Branden <[email protected]>
Signed-off-by: Desmond Yan <[email protected]>
Signed-off-by: James Hu <[email protected]>
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/bcm-vk/Kconfig | 42 +
drivers/misc/bcm-vk/Makefile | 11 +
drivers/misc/bcm-vk/bcm_vk.h | 357 ++++++++
drivers/misc/bcm-vk/bcm_vk_dev.c | 1197 ++++++++++++++++++++++++++
drivers/misc/bcm-vk/bcm_vk_msg.c | 1359 ++++++++++++++++++++++++++++++
drivers/misc/bcm-vk/bcm_vk_msg.h | 210 +++++
drivers/misc/bcm-vk/bcm_vk_sg.c | 273 ++++++
drivers/misc/bcm-vk/bcm_vk_sg.h | 60 ++
drivers/misc/bcm-vk/bcm_vk_tty.c | 327 +++++++
11 files changed, 3838 insertions(+)
create mode 100644 drivers/misc/bcm-vk/Kconfig
create mode 100644 drivers/misc/bcm-vk/Makefile
create mode 100644 drivers/misc/bcm-vk/bcm_vk.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_dev.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.c
create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.h
create mode 100644 drivers/misc/bcm-vk/bcm_vk_tty.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7f0d48f406e3..39c74ed6a846 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -478,6 +478,7 @@ source "drivers/misc/genwqe/Kconfig"
source "drivers/misc/echo/Kconfig"
source "drivers/misc/cxl/Kconfig"
source "drivers/misc/ocxl/Kconfig"
+source "drivers/misc/bcm-vk/Kconfig"
source "drivers/misc/cardreader/Kconfig"
source "drivers/misc/habanalabs/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c1860d35dc7e..d9a3b2791560 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
obj-$(CONFIG_OCXL) += ocxl/
+obj-$(CONFIG_BCM_VK) += bcm-vk/
obj-y += cardreader/
obj-$(CONFIG_PVPANIC) += pvpanic.o
obj-$(CONFIG_HABANA_AI) += habanalabs/
diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
new file mode 100644
index 000000000000..c75dfb89a38d
--- /dev/null
+++ b/drivers/misc/bcm-vk/Kconfig
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Broadcom VK device
+#
+config BCM_VK
+ tristate "Support for Broadcom VK Accelerators"
+ depends on PCI_MSI
+ default m
+ help
+ Select this option to enable support for Broadcom
+ VK Accelerators. VK is used for performing
+ specific video offload processing. This driver enables
+ userspace programs to access these accelerators via /dev/bcm-vk.N
+ devices.
+
+ If unsure, say N.
+
+if BCM_VK
+
+config BCM_VK_H2VK_VERIFY_AND_RETRY
+ bool "Host To VK Verifiy Data and Retry"
+ help
+ Turn on to verify the data passed down to VK is good,
+ and if not, do a retry until it succeeds.
+ This is a debug/workaround on FPGA PCIe timing issues
+ but may be found useful for debugging other PCIe hardware issues.
+ Small performance loss by enabling this debug config.
+ For properly operating PCIe hardware no need to enable this.
+
+ If unsure, say N.
+
+config BCM_VK_QSTATS
+ bool "VK Queue Statistics"
+ help
+ Turn on to enable Queue Statistics.
+ These are useful for debugging purposes.
+ Some performance loss by enabling this debug config.
+ For properly operating PCIe hardware no need to enable this.
+
+ If unsure, say N.
+
+endif
diff --git a/drivers/misc/bcm-vk/Makefile b/drivers/misc/bcm-vk/Makefile
new file mode 100644
index 000000000000..05cb213ee826
--- /dev/null
+++ b/drivers/misc/bcm-vk/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for Broadcom VK driver
+#
+
+obj-$(CONFIG_BCM_VK) += bcm_vk.o
+bcm_vk-objs := \
+ bcm_vk_dev.o \
+ bcm_vk_msg.o \
+ bcm_vk_sg.o \
+ bcm_vk_tty.o
diff --git a/drivers/misc/bcm-vk/bcm_vk.h b/drivers/misc/bcm-vk/bcm_vk.h
new file mode 100644
index 000000000000..203ca741bf88
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk.h
@@ -0,0 +1,357 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#ifndef BCM_VK_H
+#define BCM_VK_H
+
+#include <linux/atomic.h>
+#include <linux/pci.h>
+#include <linux/irq.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/sched/signal.h>
+#include <linux/tty.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
+#include <uapi/linux/misc/bcm_vk.h>
+
+#include "bcm_vk_msg.h"
+
+#define DRV_MODULE_NAME "bcm-vk"
+
+/*
+ * Load Image is completed in two stages:
+ *
+ * 1) When the VK device boot-up, M7 CPU runs and executes the BootROM.
+ * The Secure Boot Loader (SBL) as part of the BootROM will run
+ * to open up ITCM for host to push BOOT1 image.
+ * SBL will authenticate the image before jumping to BOOT1 image.
+ *
+ * 2) Because BOOT1 image is a secured image, we also called it the
+ * Secure Boot Image (SBI). At second stage, SBI will initialize DDR
+ * and wait for host to push BOOT2 image to DDR.
+ * SBI will authenticate the image before jumping to BOOT2 image.
+ *
+ */
+/* Location of registers of interest in BAR0 */
+
+/* Request register for Secure Boot Loader (SBL) download */
+#define BAR_CODEPUSH_SBL 0x400
+/* Start of ITCM */
+#define CODEPUSH_BOOT1_ENTRY 0x00400000
+#define CODEPUSH_MASK 0xFFFFF000
+#define CODEPUSH_BOOTSTART BIT(0)
+
+/* Boot Status register */
+#define BAR_BOOT_STATUS 0x404
+
+#define SRAM_OPEN BIT(16)
+#define DDR_OPEN BIT(17)
+
+/* Firmware loader progress status definitions */
+#define FW_LOADER_ACK_SEND_MORE_DATA BIT(18)
+#define FW_LOADER_ACK_IN_PROGRESS BIT(19)
+#define FW_LOADER_ACK_RCVD_ALL_DATA BIT(20)
+
+/* definitions for boot status register */
+#define BOOT_STATE_MASK 0xFFF3FFFF
+#define BROM_STATUS_NOT_RUN 0x2
+#define BROM_NOT_RUN (SRAM_OPEN | BROM_STATUS_NOT_RUN)
+#define BROM_STATUS_COMPLETE 0x6
+#define BROM_RUNNING (SRAM_OPEN | BROM_STATUS_COMPLETE)
+#define BOOT1_STATUS_COMPLETE 0x6
+#define BOOT1_RUNNING (DDR_OPEN | BOOT1_STATUS_COMPLETE)
+#define BOOT2_STATUS_COMPLETE 0x6
+#define BOOT2_RUNNING (FW_LOADER_ACK_RCVD_ALL_DATA | \
+ BOOT2_STATUS_COMPLETE)
+
+/* Boot request for Secure Boot Image (SBI) */
+#define BAR_CODEPUSH_SBI 0x408
+/* 64M mapped to BAR2 */
+#define CODEPUSH_BOOT2_ENTRY 0x60000000
+
+#define BAR_CARD_STATUS 0x410
+/* CARD_STATUS definitions */
+#define CARD_STATUS_TTYVK0_READY BIT(0)
+#define CARD_STATUS_TTYVK1_READY BIT(1)
+
+#define BAR_METADATA_VERSION 0x440
+#define BAR_OS_UPTIME 0x444
+#define BAR_CHIP_ID 0x448
+
+#define BAR_CARD_TEMPERATURE 0x45C
+/* defines for all temperature sensor */
+#define BCM_VK_TEMP_FIELD_MASK 0xFF
+#define BCM_VK_CPU_TEMP_SHIFT 0
+#define BCM_VK_DDR0_TEMP_SHIFT 8
+#define BCM_VK_DDR1_TEMP_SHIFT 16
+
+#define BAR_CARD_VOLTAGE 0x460
+/* defines for voltage rail conversion */
+#define BCM_VK_VOLT_RAIL_MASK 0xFFFF
+#define BCM_VK_3P3_VOLT_REG_SHIFT 16
+
+#define BAR_CARD_ERR_LOG 0x464
+/* Error log register bit definition - register for error alerts */
+#define ERR_LOG_UECC BIT(0)
+#define ERR_LOG_SSIM_BUSY BIT(1)
+#define ERR_LOG_AFBC_BUSY BIT(2)
+#define ERR_LOG_HIGH_TEMP_ERR BIT(3)
+#define ERR_LOG_WDOG_TIMEOUT BIT(4)
+#define ERR_LOG_SYS_FAULT BIT(5)
+#define ERR_LOG_MEM_ALLOC_FAIL BIT(8)
+#define ERR_LOG_LOW_TEMP_WARN BIT(9)
+#define ERR_LOG_ECC BIT(10)
+/* Alert bit definitions detectd on host */
+#define ERR_LOG_HOST_HB_FAIL BIT(14)
+#define ERR_LOG_HOST_PCIE_DWN BIT(15)
+
+#define BAR_CARD_ERR_MEM 0x468
+/* defines for mem err, all fields have same width */
+#define BCM_VK_MEM_ERR_FIELD_MASK 0xFF
+#define BCM_VK_ECC_MEM_ERR_SHIFT 0
+#define BCM_VK_UECC_MEM_ERR_SHIFT 8
+/* threshold of event occurrence and logs start to come out */
+#define BCM_VK_ECC_THRESHOLD 10
+#define BCM_VK_UECC_THRESHOLD 1
+
+#define BAR_CARD_PWR_AND_THRE 0x46C
+/* defines for power and temp threshold, all fields have same width */
+#define BCM_VK_PWR_AND_THRE_FIELD_MASK 0xFF
+#define BCM_VK_LOW_TEMP_THRE_SHIFT 0
+#define BCM_VK_HIGH_TEMP_THRE_SHIFT 8
+#define BCM_VK_PWR_STATE_SHIFT 16
+
+#define BAR_CARD_STATIC_INFO 0x470
+
+#define BAR_BOOTSRC_SELECT 0xC78
+/* BOOTSRC definitions */
+#define BOOTSRC_SOFT_ENABLE BIT(14)
+
+/* Card OS Firmware version size */
+#define BAR_FIRMWARE_TAG_SIZE 50
+#define FIRMWARE_STATUS_PRE_INIT_DONE 0x1F
+
+/* VK MSG_ID defines */
+#define VK_MSG_ID_BITMAP_SIZE 4096
+#define VK_MSG_ID_BITMAP_MASK (VK_MSG_ID_BITMAP_SIZE - 1)
+#define VK_MSG_ID_OVERFLOW 0xFFFF
+
+/* VK device supports a maximum of 3 bars */
+#define MAX_BAR 3
+
+/* default number of msg blk for inband SGL */
+#define BCM_VK_DEF_IB_SGL_BLK_LEN 16
+#define BCM_VK_IB_SGL_BLK_MAX 24
+
+enum pci_barno {
+ BAR_0 = 0,
+ BAR_1,
+ BAR_2
+};
+
+#define BCM_VK_NUM_TTY 2
+
+struct bcm_vk_tty {
+ struct tty_port port;
+ uint32_t to_offset; /* bar offset to use */
+ uint32_t to_size; /* to VK buffer size */
+ uint32_t wr; /* write offset shadow */
+ uint32_t from_offset; /* bar offset to use */
+ uint32_t from_size; /* from VK buffer size */
+ uint32_t rd; /* read offset shadow */
+};
+
+/* VK device max power state, supports 3, full, reduced and low */
+#define MAX_OPP 3
+#define MAX_CARD_INFO_TAG_SIZE 64
+
+struct bcm_vk_card_info {
+ uint32_t version;
+ char os_tag[MAX_CARD_INFO_TAG_SIZE];
+ char cmpt_tag[MAX_CARD_INFO_TAG_SIZE];
+ uint32_t cpu_freq_mhz;
+ uint32_t cpu_scale[MAX_OPP];
+ uint32_t ddr_freq_mhz;
+ uint32_t ddr_size_MB;
+ uint32_t video_core_freq_mhz;
+};
+
+/*
+ * Control structure of logging messages from the card. This
+ * buffer is for logmsg that comes from vk
+ */
+struct bcm_vk_peer_log {
+ uint32_t rd_idx;
+ uint32_t wr_idx;
+ uint32_t buf_size;
+ uint32_t mask;
+ char data[0];
+};
+/* max size per line of peer log */
+#define BCM_VK_PEER_LOG_LINE_MAX 256
+
+struct bcm_vk_hb_ctrl {
+ struct timer_list timer;
+ uint32_t last_uptime;
+ uint32_t lost_cnt;
+};
+
+struct bcm_vk_alert {
+ uint16_t flags;
+ uint16_t notfs;
+};
+
+/* some alert counters that the driver will keep track */
+struct bcm_vk_alert_cnts {
+ uint16_t ecc;
+ uint16_t uecc;
+};
+
+struct bcm_vk {
+ struct pci_dev *pdev;
+ void __iomem *bar[MAX_BAR];
+ int num_irqs;
+
+ struct bcm_vk_card_info card_info;
+
+ /* mutex to protect the ioctls */
+ struct mutex mutex;
+ struct miscdevice miscdev;
+ int misc_devid; /* dev id allocated */
+
+ struct tty_driver *tty_drv;
+ struct timer_list serial_timer;
+ struct bcm_vk_tty tty[BCM_VK_NUM_TTY];
+
+ /* Reference-counting to handle file operations */
+ struct kref kref;
+
+ spinlock_t msg_id_lock;
+ uint16_t msg_id;
+ DECLARE_BITMAP(bmap, VK_MSG_ID_BITMAP_SIZE);
+ spinlock_t ctx_lock;
+ struct bcm_vk_ctx ctx[VK_CMPT_CTX_MAX];
+ struct bcm_vk_ht_entry pid_ht[VK_PID_HT_SZ];
+ struct task_struct *reset_ppid; /* process that issue reset */
+
+ atomic_t msgq_inited; /* indicate if info has been synced with vk */
+ struct bcm_vk_msg_chan h2vk_msg_chan;
+ struct bcm_vk_msg_chan vk2h_msg_chan;
+
+ struct workqueue_struct *wq_thread;
+ struct work_struct wq_work; /* work queue for deferred job */
+ unsigned long wq_offload[1]; /* various flags on wq requested */
+ void *tdma_vaddr; /* test dma segment virtual addr */
+ dma_addr_t tdma_addr; /* test dma segment bus addr */
+
+ struct notifier_block panic_nb;
+ uint32_t ib_sgl_size; /* size allocated for inband sgl insertion */
+
+ /* heart beat mechanism control structure */
+ struct bcm_vk_hb_ctrl hb_ctrl;
+ /* house-keeping variable of error logs */
+ spinlock_t host_alert_lock; /* protection to access host_alert struct */
+ struct bcm_vk_alert host_alert;
+ struct bcm_vk_alert peer_alert; /* bits set by the card */
+ struct bcm_vk_alert_cnts alert_cnts;
+
+ /* offset of the peer log control in BAR2 */
+ uint32_t peerlog_off;
+};
+
+/* wq offload work items bits definitions */
+enum bcm_vk_wq_offload_flags {
+ BCM_VK_WQ_DWNLD_PEND = 0,
+ BCM_VK_WQ_DWNLD_AUTO = 1,
+ BCM_VK_WQ_NOTF_PEND = 2,
+};
+
+/* a macro to get an individual field with mask and shift */
+#define BCM_VK_EXTRACT_FIELD(_field, _reg, _mask, _shift) \
+ (_field = (((_reg) >> (_shift)) & (_mask)))
+
+/* structure that is used to faciliate displaying of register content */
+struct bcm_vk_entry {
+ const uint32_t mask;
+ const uint32_t exp_val;
+ const char *str;
+};
+
+/* alerts that could be generated from peer */
+#define BCM_VK_PEER_ERR_NUM 9
+extern struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM];
+/* alerts detected by the host */
+#define BCM_VK_HOST_ERR_NUM 2
+extern struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM];
+
+/*
+ * check if PCIe interface is down on read. Use it when it is
+ * certain that _val should never be all ones.
+ */
+#define BCM_VK_INTF_IS_DOWN(val) ((val) == 0xFFFFFFFF)
+
+static inline u32 vkread32(struct bcm_vk *vk,
+ enum pci_barno bar,
+ uint64_t offset)
+{
+ u32 value;
+
+ value = ioread32(vk->bar[bar] + offset);
+ return value;
+}
+
+static inline void vkwrite32(struct bcm_vk *vk,
+ u32 value,
+ enum pci_barno bar,
+ uint64_t offset)
+{
+ iowrite32(value, vk->bar[bar] + offset);
+}
+
+static inline u8 vkread8(struct bcm_vk *vk,
+ enum pci_barno bar,
+ uint64_t offset)
+{
+ u8 value;
+
+ value = ioread8(vk->bar[bar] + offset);
+ return value;
+}
+
+static inline void vkwrite8(struct bcm_vk *vk,
+ u8 value,
+ enum pci_barno bar,
+ uint64_t offset)
+{
+ iowrite8(value, vk->bar[bar] + offset);
+}
+
+int bcm_vk_open(struct inode *inode, struct file *p_file);
+ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
+ loff_t *f_pos);
+ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
+ size_t count, loff_t *f_pos);
+int bcm_vk_release(struct inode *inode, struct file *p_file);
+void bcm_vk_release_data(struct kref *kref);
+irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id);
+irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id);
+int bcm_vk_msg_init(struct bcm_vk *vk);
+void bcm_vk_msg_remove(struct bcm_vk *vk);
+int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync);
+bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk);
+void bcm_vk_blk_drv_access(struct bcm_vk *vk);
+int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type, pid_t pid);
+void bcm_vk_trigger_reset(struct bcm_vk *vk);
+void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num, uint32_t db_val);
+int bcm_vk_auto_load_all_images(struct bcm_vk *vk);
+int bcm_vk_tty_init(struct bcm_vk *vk, char *name);
+void bcm_vk_tty_exit(struct bcm_vk *vk);
+void bcm_vk_hb_init(struct bcm_vk *vk);
+void bcm_vk_hb_deinit(struct bcm_vk *vk);
+void bcm_vk_handle_notf(struct bcm_vk *vk);
+bool bcm_vk_drv_access_ok(struct bcm_vk *vk);
+
+#endif
diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
new file mode 100644
index 000000000000..d8f59e898ca8
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
@@ -0,0 +1,1197 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/idr.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pci_regs.h>
+#include <uapi/linux/misc/bcm_vk.h>
+
+#include "bcm_vk.h"
+
+#define PCI_DEVICE_ID_VALKYRIE 0x5E87
+#define PCI_DEVICE_ID_VIPER 0x5E88
+
+static DEFINE_IDA(bcm_vk_ida);
+
+struct load_image_tab {
+ const uint32_t image_type;
+ const char *image_name;
+};
+
+enum soc_idx {
+ VALKYRIE = 0,
+ VIPER
+};
+
+#define NUM_BOOT_STAGES 2
+const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
+ [VALKYRIE] = {
+ {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VALKYRIE_FILENAME},
+ {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VALKYRIE_FILENAME}
+ },
+ [VIPER] = {
+ {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VIPER_FILENAME},
+ {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VIPER_FILENAME}
+ }
+};
+
+/* Location of memory base addresses of interest in BAR1 */
+/* Load Boot1 to start of ITCM */
+#define BAR1_CODEPUSH_BASE_BOOT1 0x100000
+
+/* Allow minimum 1s for Load Image timeout responses */
+#define LOAD_IMAGE_TIMEOUT_MS (1 * MSEC_PER_SEC)
+
+/* Image startup timeouts */
+#define BOOT1_STARTUP_TIMEOUT_MS (5 * MSEC_PER_SEC)
+#define BOOT2_STARTUP_TIMEOUT_MS (10 * MSEC_PER_SEC)
+
+/* 1ms wait for checking the transfer complete status */
+#define TXFR_COMPLETE_TIMEOUT_MS 1
+
+/* MSIX usages */
+#define VK_MSIX_MSGQ_MAX 3
+#define VK_MSIX_NOTF_MAX 1
+#define VK_MSIX_IRQ_MAX (VK_MSIX_MSGQ_MAX + VK_MSIX_NOTF_MAX)
+
+/* Number of bits set in DMA mask*/
+#define BCM_VK_DMA_BITS 64
+
+/* deinit time for the card os after receiving doorbell */
+#define BCM_VK_DEINIT_TIME_MS (2 * MSEC_PER_SEC)
+
+/*
+ * module parameters
+ */
+static bool auto_load = true;
+module_param(auto_load, bool, 0444);
+MODULE_PARM_DESC(auto_load,
+ "Load images automatically at PCIe probe time.\n");
+static uint nr_scratch_pages = VK_BAR1_SCRATCH_DEF_NR_PAGES;
+module_param(nr_scratch_pages, uint, 0444);
+MODULE_PARM_DESC(nr_scratch_pages,
+ "Number of pre allocated DMAable coherent pages.\n");
+static uint nr_ib_sgl_blk = BCM_VK_DEF_IB_SGL_BLK_LEN;
+module_param(nr_ib_sgl_blk, uint, 0444);
+MODULE_PARM_DESC(nr_ib_sgl_blk,
+ "Number of in-band msg blks for short SGL.\n");
+
+/*
+ * alerts that could be generated from peer
+ */
+struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM] = {
+ {ERR_LOG_UECC, ERR_LOG_UECC, "uecc"},
+ {ERR_LOG_SSIM_BUSY, ERR_LOG_SSIM_BUSY, "ssim_busy"},
+ {ERR_LOG_AFBC_BUSY, ERR_LOG_AFBC_BUSY, "afbc_busy"},
+ {ERR_LOG_HIGH_TEMP_ERR, ERR_LOG_HIGH_TEMP_ERR, "high_temp"},
+ {ERR_LOG_WDOG_TIMEOUT, ERR_LOG_WDOG_TIMEOUT, "wdog_timeout"},
+ {ERR_LOG_SYS_FAULT, ERR_LOG_SYS_FAULT, "sys_fault"},
+ {ERR_LOG_MEM_ALLOC_FAIL, ERR_LOG_MEM_ALLOC_FAIL, "malloc_fail warn"},
+ {ERR_LOG_LOW_TEMP_WARN, ERR_LOG_LOW_TEMP_WARN, "low_temp warn"},
+ {ERR_LOG_ECC, ERR_LOG_ECC, "ecc"},
+};
+/* alerts detected by the host */
+struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM] = {
+ {ERR_LOG_HOST_PCIE_DWN, ERR_LOG_HOST_PCIE_DWN, "PCIe_down"},
+ {ERR_LOG_HOST_HB_FAIL, ERR_LOG_HOST_HB_FAIL, "hb_fail"},
+};
+
+irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id)
+{
+ struct bcm_vk *vk = dev_id;
+
+ if (!bcm_vk_drv_access_ok(vk)) {
+ dev_err(&vk->pdev->dev,
+ "Interrupt %d received when msgq not inited\n", irq);
+ goto skip_schedule_work;
+ }
+
+ /* if notification is not pending, set bit and schedule work */
+ if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
+ queue_work(vk->wq_thread, &vk->wq_work);
+
+skip_schedule_work:
+ return IRQ_HANDLED;
+}
+
+static void bcm_vk_log_notf(struct bcm_vk *vk,
+ struct bcm_vk_alert *alert,
+ struct bcm_vk_entry const *entry_tab,
+ const uint32_t table_size)
+{
+ uint32_t i;
+ uint32_t masked_val, latched_val;
+ struct bcm_vk_entry const *entry;
+ uint32_t reg;
+ uint16_t ecc_mem_err, uecc_mem_err;
+ struct device *dev = &vk->pdev->dev;
+
+ for (i = 0; i < table_size; i++) {
+ entry = &entry_tab[i];
+ masked_val = entry->mask & alert->notfs;
+ latched_val = entry->mask & alert->flags;
+
+ if (masked_val == ERR_LOG_UECC) {
+ /*
+ * if there is difference between stored cnt and it
+ * is greater than threshold, log it.
+ */
+ reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
+ BCM_VK_EXTRACT_FIELD(uecc_mem_err, reg,
+ BCM_VK_MEM_ERR_FIELD_MASK,
+ BCM_VK_UECC_MEM_ERR_SHIFT);
+ if ((uecc_mem_err != vk->alert_cnts.uecc) &&
+ (uecc_mem_err >= BCM_VK_UECC_THRESHOLD))
+ dev_info(dev,
+ "ALERT! %s.%d uecc RAISED - ErrCnt %d\n",
+ DRV_MODULE_NAME, vk->misc_devid,
+ uecc_mem_err);
+ vk->alert_cnts.uecc = uecc_mem_err;
+ } else if (masked_val == ERR_LOG_ECC) {
+ reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
+ BCM_VK_EXTRACT_FIELD(ecc_mem_err, reg,
+ BCM_VK_MEM_ERR_FIELD_MASK,
+ BCM_VK_ECC_MEM_ERR_SHIFT);
+ if ((ecc_mem_err != vk->alert_cnts.ecc) &&
+ (ecc_mem_err >= BCM_VK_ECC_THRESHOLD))
+ dev_info(dev, "ALERT! %s.%d ecc RAISED - ErrCnt %d\n",
+ DRV_MODULE_NAME, vk->misc_devid,
+ ecc_mem_err);
+ vk->alert_cnts.ecc = ecc_mem_err;
+ } else if (masked_val != latched_val) {
+ /* print a log as info */
+ dev_info(dev, "ALERT! %s.%d %s %s\n",
+ DRV_MODULE_NAME, vk->misc_devid, entry->str,
+ masked_val ? "RAISED" : "CLEARED");
+ }
+ }
+}
+
+static void bcm_vk_dump_peer_log(struct bcm_vk *vk)
+{
+ struct bcm_vk_peer_log log, *p_ctl;
+ char loc_buf[BCM_VK_PEER_LOG_LINE_MAX];
+ int cnt;
+ struct device *dev = &vk->pdev->dev;
+ uint data_offset;
+
+ p_ctl = vk->bar[BAR_2] + vk->peerlog_off;
+ log = *p_ctl;
+ /* do a rmb() to make sure log is updated */
+ rmb();
+
+ dev_dbg(dev, "Peer PANIC: Size 0x%x(0x%x), [Rd Wr] = [%d %d]\n",
+ log.buf_size, log.mask, log.rd_idx, log.wr_idx);
+
+ cnt = 0;
+ data_offset = vk->peerlog_off + sizeof(struct bcm_vk_peer_log);
+ while (log.rd_idx != log.wr_idx) {
+ loc_buf[cnt] = vkread8(vk, BAR_2, data_offset + log.rd_idx);
+
+ if ((loc_buf[cnt] == '\0') ||
+ (cnt == (BCM_VK_PEER_LOG_LINE_MAX - 1))) {
+ dev_err(dev, "%s", loc_buf);
+ cnt = 0;
+ } else {
+ cnt++;
+ }
+ log.rd_idx = (log.rd_idx + 1) & log.mask;
+ }
+ /* update rd idx at the end */
+ vkwrite32(vk, log.rd_idx, BAR_2, vk->peerlog_off);
+}
+
+void bcm_vk_handle_notf(struct bcm_vk *vk)
+{
+ uint32_t reg;
+ struct bcm_vk_alert alert;
+ bool intf_down;
+ unsigned long flags;
+
+ /* handle peer alerts and then locally detected ones */
+ reg = vkread32(vk, BAR_0, BAR_CARD_ERR_LOG);
+ intf_down = BCM_VK_INTF_IS_DOWN(reg);
+ if (!intf_down) {
+ vk->peer_alert.notfs = reg;
+ bcm_vk_log_notf(vk, &vk->peer_alert, bcm_vk_peer_err,
+ ARRAY_SIZE(bcm_vk_peer_err));
+ vk->peer_alert.flags = vk->peer_alert.notfs;
+ } else {
+ /* turn off access */
+ bcm_vk_blk_drv_access(vk);
+ }
+
+ /* check and make copy of alert with lock and then free lock */
+ spin_lock_irqsave(&vk->host_alert_lock, flags);
+ if (intf_down)
+ vk->host_alert.notfs |= ERR_LOG_HOST_PCIE_DWN;
+
+ alert = vk->host_alert;
+ vk->host_alert.flags = vk->host_alert.notfs;
+ spin_unlock_irqrestore(&vk->host_alert_lock, flags);
+
+ /* call display with copy */
+ bcm_vk_log_notf(vk, &alert, bcm_vk_host_err,
+ ARRAY_SIZE(bcm_vk_host_err));
+
+ /*
+ * If it is a sys fault or heartbeat timeout, we would like extract
+ * log msg from the card so that we would know what is the last fault
+ */
+ if ((!intf_down) &&
+ ((vk->host_alert.flags & ERR_LOG_HOST_HB_FAIL) ||
+ (vk->peer_alert.flags & ERR_LOG_SYS_FAULT)))
+ bcm_vk_dump_peer_log(vk);
+}
+
+static inline int bcm_vk_wait(struct bcm_vk *vk, enum pci_barno bar,
+ uint64_t offset, u32 mask, u32 value,
+ unsigned long timeout_ms)
+{
+ struct device *dev = &vk->pdev->dev;
+ unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
+ u32 rd_val;
+
+ do {
+ rd_val = vkread32(vk, bar, offset);
+ dev_dbg(dev, "BAR%d Offset=0x%llx: 0x%x\n",
+ bar, offset, rd_val);
+
+ if (time_after(jiffies, timeout))
+ return -ETIMEDOUT;
+
+ cpu_relax();
+ cond_resched();
+ } while ((rd_val & mask) != value);
+
+ return 0;
+}
+
+static void bcm_vk_get_card_info(struct bcm_vk *vk)
+{
+ struct device *dev = &vk->pdev->dev;
+ uint32_t offset;
+ int i;
+ uint8_t *dst;
+ struct bcm_vk_card_info *info = &vk->card_info;
+
+ /* first read the offset from spare register */
+ offset = vkread32(vk, BAR_0, BAR_CARD_STATIC_INFO);
+ offset &= (pci_resource_len(vk->pdev, BAR_2 * 2) - 1);
+
+ /* based on the offset, read info to internal card info structure */
+ dst = (uint8_t *)info;
+ for (i = 0; i < sizeof(*info); i++)
+ *dst++ = vkread8(vk, BAR_2, offset++);
+
+#define CARD_INFO_LOG_FMT "version : %x\n" \
+ "os_tag : %s\n" \
+ "cmpt_tag : %s\n" \
+ "cpu_freq : %d MHz\n" \
+ "cpu_scale : %d full, %d lowest\n" \
+ "ddr_freq : %d MHz\n" \
+ "ddr_size : %d MB\n" \
+ "video_freq: %d MHz\n"
+ dev_dbg(dev, CARD_INFO_LOG_FMT, info->version, info->os_tag,
+ info->cmpt_tag, info->cpu_freq_mhz, info->cpu_scale[0],
+ info->cpu_scale[MAX_OPP - 1], info->ddr_freq_mhz,
+ info->ddr_size_MB, info->video_core_freq_mhz);
+
+ /* get the peer log pointer, only need the offset */
+ vk->peerlog_off = offset;
+}
+
+static int bcm_vk_sync_card_info(struct bcm_vk *vk)
+{
+ uint32_t rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
+
+ /* check for marker, but allow diags mode to skip sync */
+ if (!bcm_vk_msgq_marker_valid(vk))
+ return (rdy_marker == VK_BAR1_DIAG_RDY_MARKER ? 0 : -EINVAL);
+
+ /*
+ * Write down scratch addr which is used for DMA. For
+ * signed part, BAR1 is accessible only after boot2 has come
+ * up
+ */
+ if (vk->tdma_addr) {
+ vkwrite32(vk, vk->tdma_addr >> 32, BAR_1,
+ VK_BAR1_SCRATCH_OFF_LO);
+ vkwrite32(vk, (uint32_t)vk->tdma_addr, BAR_1,
+ VK_BAR1_SCRATCH_OFF_HI);
+ vkwrite32(vk, nr_scratch_pages * PAGE_SIZE, BAR_1,
+ VK_BAR1_SCRATCH_SZ_ADDR);
+ }
+
+ /* get static card info, only need to read once */
+ bcm_vk_get_card_info(vk);
+
+ return 0;
+}
+
+void bcm_vk_blk_drv_access(struct bcm_vk *vk)
+{
+ int i;
+
+ /*
+ * kill all the apps except for the process that is resetting.
+ * If not called during reset, reset_ppid == NULL, and all will be
+ * killed.
+ */
+ spin_lock(&vk->ctx_lock);
+
+ /* set msgq_inited to 0 so that all rd/wr will be blocked */
+ atomic_set(&vk->msgq_inited, 0);
+
+ for (i = 0; i < VK_PID_HT_SZ; i++) {
+ struct bcm_vk_ctx *ctx;
+
+ list_for_each_entry(ctx, &vk->pid_ht[i].head, node) {
+ if (ctx->ppid != vk->reset_ppid) {
+ dev_dbg(&vk->pdev->dev,
+ "Send kill signal to pid %d\n",
+ task_pid_nr(ctx->ppid));
+ kill_pid(task_pid(ctx->ppid), SIGKILL, 1);
+ }
+ }
+ }
+ spin_unlock(&vk->ctx_lock);
+}
+
+static void bcm_vk_buf_notify(struct bcm_vk *vk, void *bufp,
+ dma_addr_t host_buf_addr, uint32_t buf_size)
+{
+ /* update the dma address to the card */
+ vkwrite32(vk, host_buf_addr >> 32, BAR_1,
+ VK_BAR1_DMA_BUF_OFF_HI);
+ vkwrite32(vk, (uint32_t)host_buf_addr, BAR_1,
+ VK_BAR1_DMA_BUF_OFF_LO);
+ vkwrite32(vk, buf_size, BAR_1, VK_BAR1_DMA_BUF_SZ);
+}
+
+static int bcm_vk_load_image_by_type(struct bcm_vk *vk, u32 load_type,
+ const char *filename)
+{
+ struct device *dev = &vk->pdev->dev;
+ const struct firmware *fw;
+ void *bufp;
+ size_t max_buf;
+ int ret;
+ uint64_t offset_codepush;
+ u32 codepush;
+ u32 value;
+ dma_addr_t boot2_dma_addr;
+
+ if (load_type == VK_IMAGE_TYPE_BOOT1) {
+ /*
+ * After POR, enable VK soft BOOTSRC so bootrom do not clear
+ * the pushed image (the TCM memories).
+ */
+ value = vkread32(vk, BAR_0, BAR_BOOTSRC_SELECT);
+ value |= BOOTSRC_SOFT_ENABLE;
+ vkwrite32(vk, value, BAR_0, BAR_BOOTSRC_SELECT);
+
+ codepush = CODEPUSH_BOOTSTART + CODEPUSH_BOOT1_ENTRY;
+ offset_codepush = BAR_CODEPUSH_SBL;
+
+ /* Write a 1 to request SRAM open bit */
+ vkwrite32(vk, CODEPUSH_BOOTSTART, BAR_0, offset_codepush);
+
+ /* Wait for VK to respond */
+ ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, SRAM_OPEN,
+ SRAM_OPEN, LOAD_IMAGE_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_err(dev, "boot1 timeout\n");
+ goto err_out;
+ }
+
+ bufp = vk->bar[BAR_1] + BAR1_CODEPUSH_BASE_BOOT1;
+ max_buf = SZ_256K;
+ } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
+ codepush = CODEPUSH_BOOT2_ENTRY;
+ offset_codepush = BAR_CODEPUSH_SBI;
+
+ /* Wait for VK to respond */
+ ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, DDR_OPEN,
+ DDR_OPEN, LOAD_IMAGE_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_err(dev, "boot2 timeout\n");
+ goto err_out;
+ }
+
+ max_buf = SZ_4M;
+ bufp = dma_alloc_coherent(dev,
+ max_buf,
+ &boot2_dma_addr, GFP_KERNEL);
+ if (!bufp) {
+ dev_err(dev, "Error allocating 0x%zx\n", max_buf);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ bcm_vk_buf_notify(vk, bufp, boot2_dma_addr, max_buf);
+ } else {
+ dev_err(dev, "Error invalid image type 0x%x\n", load_type);
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ ret = request_firmware_into_buf(&fw, filename, dev,
+ bufp, max_buf, 0,
+ KERNEL_PREAD_FLAG_PART);
+ if (ret) {
+ dev_err(dev, "Error %d requesting firmware file: %s\n",
+ ret, filename);
+ goto err_out;
+ }
+ dev_dbg(dev, "size=0x%zx\n", fw->size);
+
+ dev_dbg(dev, "Signaling 0x%x to 0x%llx\n", codepush, offset_codepush);
+ vkwrite32(vk, codepush, BAR_0, offset_codepush);
+
+ if (load_type == VK_IMAGE_TYPE_BOOT1) {
+ /* wait until done */
+ ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
+ BOOT1_RUNNING,
+ BOOT1_RUNNING,
+ BOOT1_STARTUP_TIMEOUT_MS);
+ if (ret) {
+ dev_err(dev,
+ "Timeout %ld ms waiting for boot1 to come up\n",
+ BOOT1_STARTUP_TIMEOUT_MS);
+ goto err_firmware_out;
+ }
+ } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
+ unsigned long timeout = jiffies + msecs_to_jiffies(
+ LOAD_IMAGE_TIMEOUT_MS);
+
+ /* To send more data to VK than max_buf allowed at a time */
+ do {
+ /*
+ * Check for ack from card. when Ack is received,
+ * it means all the data is received by card.
+ * Exit the loop after ack is received.
+ */
+ ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
+ FW_LOADER_ACK_RCVD_ALL_DATA,
+ FW_LOADER_ACK_RCVD_ALL_DATA,
+ TXFR_COMPLETE_TIMEOUT_MS);
+ if (ret == 0) {
+ dev_info(dev, "Exit boot2 download\n");
+ break;
+ }
+
+ /* exit the loop, if there is no response from card */
+ if (time_after(jiffies, timeout)) {
+ dev_err(dev, "Error. No reply from card\n");
+ ret = -ETIMEDOUT;
+ goto err_firmware_out;
+ }
+
+ /* Wait for VK to open BAR space to copy new data */
+ ret = bcm_vk_wait(vk, BAR_0, offset_codepush,
+ codepush, 0,
+ TXFR_COMPLETE_TIMEOUT_MS);
+ if (ret == 0) {
+ ret = request_firmware_into_buf(
+ &fw,
+ filename,
+ dev, bufp,
+ max_buf,
+ fw->size,
+ KERNEL_PREAD_FLAG_PART);
+ if (ret) {
+ dev_err(dev, "Error %d requesting firmware file: %s offset: 0x%zx\n",
+ ret, filename,
+ fw->size);
+ goto err_firmware_out;
+ }
+ dev_dbg(dev, "size=0x%zx\n", fw->size);
+ dev_dbg(dev, "Signaling 0x%x to 0x%llx\n",
+ codepush, offset_codepush);
+ vkwrite32(vk, codepush, BAR_0, offset_codepush);
+ /* reload timeout after every codepush */
+ timeout = jiffies + msecs_to_jiffies(
+ LOAD_IMAGE_TIMEOUT_MS);
+ }
+ } while (1);
+
+ /* wait for fw status bits to indicate app ready */
+ ret = bcm_vk_wait(vk, BAR_0, VK_BAR_FWSTS,
+ VK_FWSTS_READY,
+ VK_FWSTS_READY,
+ BOOT2_STARTUP_TIMEOUT_MS);
+ if (ret < 0) {
+ dev_err(dev, "Boot2 not ready - timeout\n");
+ goto err_firmware_out;
+ }
+
+ /*
+ * Next, initialize Message Q if we are loading boot2.
+ * Do a force sync
+ */
+ ret = bcm_vk_sync_msgq(vk, true);
+ if (ret) {
+ dev_err(dev, "Boot2 Error reading comm msg Q info\n");
+ ret = -EIO;
+ goto err_firmware_out;
+ }
+
+ /* sync & channel other info */
+ ret = bcm_vk_sync_card_info(vk);
+ if (ret) {
+ dev_err(dev, "Syncing Card Info failure\n");
+ goto err_firmware_out;
+ }
+ }
+
+err_firmware_out:
+ release_firmware(fw);
+
+err_out:
+ if (load_type == VK_IMAGE_TYPE_BOOT2)
+ dma_free_coherent(dev, max_buf, bufp, boot2_dma_addr);
+
+ return ret;
+}
+
+static u32 bcm_vk_next_boot_image(struct bcm_vk *vk)
+{
+ uint32_t boot_status;
+ uint32_t fw_status;
+ u32 load_type = 0; /* default for unknown */
+
+ boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
+ fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
+
+ if (!BCM_VK_INTF_IS_DOWN(boot_status) && (boot_status & SRAM_OPEN))
+ load_type = VK_IMAGE_TYPE_BOOT1;
+ else if (boot_status == BOOT1_RUNNING)
+ load_type = VK_IMAGE_TYPE_BOOT2;
+
+ /*
+ * TO_FIX: For now, like to know what value we get everytime
+ * for debugging.
+ */
+ dev_info(&vk->pdev->dev,
+ "boot-status value for next image: 0x%x : fw-status 0x%x\n",
+ boot_status, fw_status);
+
+ return load_type;
+}
+
+int bcm_vk_auto_load_all_images(struct bcm_vk *vk)
+{
+ int i, id, ret = -1;
+ struct device *dev = &vk->pdev->dev;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ uint32_t curr_type;
+ const char *curr_name;
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_VALKYRIE:
+ id = VALKYRIE;
+ break;
+
+ case PCI_DEVICE_ID_VIPER:
+ id = VIPER;
+ break;
+
+ default:
+ dev_err(dev, "no images for 0x%x\n", pdev->device);
+ goto bcm_vk_auto_load_all_exit;
+ }
+
+ /* log a message to know the relative loading order */
+ dev_info(dev, "Load All for device %d\n", vk->misc_devid);
+
+ for (i = 0; i < NUM_BOOT_STAGES; i++) {
+ curr_type = image_tab[id][i].image_type;
+ if (bcm_vk_next_boot_image(vk) == curr_type) {
+ curr_name = image_tab[id][i].image_name;
+ ret = bcm_vk_load_image_by_type(vk, curr_type,
+ curr_name);
+ dev_info(dev, "Auto load %s, ret %d\n",
+ curr_name, ret);
+
+ if (ret) {
+ dev_err(dev, "Error loading default %s\n",
+ curr_name);
+ goto bcm_vk_auto_load_all_exit;
+ }
+ }
+ }
+
+bcm_vk_auto_load_all_exit:
+ return ret;
+}
+
+static int bcm_vk_trigger_autoload(struct bcm_vk *vk)
+{
+ if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0)
+ return -EPERM;
+
+ set_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
+ queue_work(vk->wq_thread, &vk->wq_work);
+
+ return 0;
+}
+
+static long bcm_vk_load_image(struct bcm_vk *vk, struct vk_image *arg)
+{
+ int ret;
+ struct device *dev = &vk->pdev->dev;
+ struct vk_image image;
+ u32 next_loadable;
+
+ if (copy_from_user(&image, arg, sizeof(image))) {
+ ret = -EACCES;
+ goto bcm_vk_load_image_exit;
+ }
+
+ /* first check if fw is at the right state for the download */
+ next_loadable = bcm_vk_next_boot_image(vk);
+ if (next_loadable != image.type) {
+ dev_err(dev, "Next expected image %u, Loading %u\n",
+ next_loadable, image.type);
+ ret = -EPERM;
+ goto bcm_vk_load_image_exit;
+ }
+
+ /*
+ * if something is pending download already. This could only happen
+ * for now when the driver is being loaded, or if someone has issued
+ * another download command in another shell.
+ */
+ if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0) {
+ dev_err(dev, "Download operation already pending.\n");
+ return -EPERM;
+ }
+
+ ret = bcm_vk_load_image_by_type(vk, image.type, image.filename);
+ clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
+
+bcm_vk_load_image_exit:
+ return ret;
+}
+
+static long bcm_vk_access_bar(struct bcm_vk *vk, struct vk_access *arg)
+{
+ struct device *dev = &vk->pdev->dev;
+ struct vk_access access;
+ long ret = -EINVAL;
+ u32 value;
+ long i;
+ long num;
+
+ if (copy_from_user(&access, arg, sizeof(struct vk_access))) {
+ ret = -EACCES;
+ goto err_out;
+ }
+
+ /* do some range checking in the barno and offset */
+ if (access.barno >= MAX_BAR) {
+ dev_err(dev, "invalid bar no %d\n", access.barno);
+ goto err_out;
+ } else if ((access.offset + access.len) >
+ pci_resource_len(vk->pdev, access.barno * 2)) {
+ dev_err(dev, "invalid bar offset 0x%llx, len 0x%x\n",
+ access.offset, access.len);
+ goto err_out;
+ }
+
+ if (access.type == VK_ACCESS_READ) {
+ dev_dbg(dev, "read barno:%d offset:0x%llx len:0x%x\n",
+ access.barno, access.offset, access.len);
+ num = access.len / sizeof(u32);
+ for (i = 0; i < num; i++) {
+ value = vkread32(vk, access.barno,
+ access.offset + (i * sizeof(u32)));
+ ret = put_user(value, access.data + i);
+ if (ret)
+ goto err_out;
+
+ dev_dbg(dev, "0x%x\n", value);
+ }
+ } else if (access.type == VK_ACCESS_WRITE) {
+ dev_dbg(dev, "write barno:%d offset:0x%llx len:0x%x\n",
+ access.barno, access.offset, access.len);
+ num = access.len / sizeof(u32);
+ for (i = 0; i < num; i++) {
+ ret = get_user(value, access.data + i);
+ if (ret)
+ goto err_out;
+
+ vkwrite32(vk, value, access.barno,
+ access.offset + (i * sizeof(u32)));
+ dev_dbg(dev, "0x%x\n", value);
+ }
+ } else {
+ dev_dbg(dev, "invalid access type %d\n", access.type);
+ }
+err_out:
+ return ret;
+}
+
+static int bcm_vk_reset_successful(struct bcm_vk *vk)
+{
+ struct device *dev = &vk->pdev->dev;
+ u32 fw_status, reset_reason;
+ int ret = -EAGAIN;
+
+ /*
+ * Reset could be triggered when the card in several state:
+ * i) in bootROM
+ * ii) after boot1
+ * iii) boot2 running
+ *
+ * i) & ii) - no status bits will be updated. If vkboot1
+ * runs automatically after reset, it will update the reason
+ * to be unknown reason
+ * iii) - reboot reason match + deinit done.
+ */
+ fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
+ /* immediate exit if interface goes down */
+ if (BCM_VK_INTF_IS_DOWN(fw_status)) {
+ dev_err(dev, "PCIe Intf Down!\n");
+ goto bcm_vk_reset_exit;
+ }
+
+ /* initial check on reset reason */
+ reset_reason = (fw_status & VK_FWSTS_RESET_REASON_MASK);
+ if ((reset_reason == VK_FWSTS_RESET_MBOX_DB)
+ || (reset_reason == VK_FWSTS_RESET_UNKNOWN))
+ ret = 0;
+
+ /*
+ * if some of the deinit bits are set, but done
+ * bit is not, this is a failure if triggered while boot2 is running
+ */
+ if ((fw_status & VK_FWSTS_DEINIT_TRIGGERED)
+ && !(fw_status & VK_FWSTS_RESET_DONE))
+ ret = -EAGAIN;
+
+bcm_vk_reset_exit:
+ dev_dbg(dev, "FW status = 0x%x ret %d\n", fw_status, ret);
+
+ return ret;
+}
+
+static long bcm_vk_reset(struct bcm_vk *vk, struct vk_reset *arg)
+{
+ struct device *dev = &vk->pdev->dev;
+ struct vk_reset reset;
+ int ret = 0;
+
+ if (copy_from_user(&reset, arg, sizeof(struct vk_reset))) {
+ ret = -EACCES;
+ goto err_out;
+ }
+ dev_info(dev, "Issue Reset\n");
+
+ /*
+ * The following is the sequence of reset:
+ * - send card level graceful shut down
+ * - wait enough time for VK to handle its business, stopping DMA etc
+ * - kill host apps
+ * - Trigger interrupt with DB
+ */
+ bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_GRACEFUL, 0);
+
+ spin_lock(&vk->ctx_lock);
+ if (!vk->reset_ppid) {
+ vk->reset_ppid = current;
+ } else {
+ dev_err(dev, "Reset already launched by process pid %d\n",
+ task_pid_nr(vk->reset_ppid));
+ ret = -EACCES;
+ }
+ spin_unlock(&vk->ctx_lock);
+ if (ret)
+ goto err_out;
+
+ bcm_vk_blk_drv_access(vk);
+ bcm_vk_trigger_reset(vk);
+
+ /*
+ * Wait enough time for card os to deinit
+ * and populate the reset reason.
+ */
+ msleep(BCM_VK_DEINIT_TIME_MS);
+
+ ret = bcm_vk_reset_successful(vk);
+
+err_out:
+ return ret;
+}
+
+static int bcm_vk_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct bcm_vk_ctx *ctx = file->private_data;
+ struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
+ unsigned long pg_size;
+
+ /* only BAR2 is mmap possible, which is bar num 4 due to 64bit */
+#define VK_MMAPABLE_BAR 4
+
+ pg_size = ((pci_resource_len(vk->pdev, VK_MMAPABLE_BAR) - 1)
+ >> PAGE_SHIFT) + 1;
+ if (vma->vm_pgoff + vma_pages(vma) > pg_size)
+ return -EINVAL;
+
+ vma->vm_pgoff += (pci_resource_start(vk->pdev, VK_MMAPABLE_BAR)
+ >> PAGE_SHIFT);
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot);
+}
+
+static long bcm_vk_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ long ret = -EINVAL;
+ struct bcm_vk_ctx *ctx = file->private_data;
+ struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
+ void __user *argp = (void __user *)arg;
+
+ dev_dbg(&vk->pdev->dev,
+ "ioctl, cmd=0x%02x, arg=0x%02lx\n",
+ cmd, arg);
+
+ mutex_lock(&vk->mutex);
+
+ switch (cmd) {
+ case VK_IOCTL_LOAD_IMAGE:
+ ret = bcm_vk_load_image(vk, argp);
+ break;
+
+ case VK_IOCTL_ACCESS_BAR:
+ ret = bcm_vk_access_bar(vk, argp);
+ break;
+
+ case VK_IOCTL_RESET:
+ ret = bcm_vk_reset(vk, argp);
+ break;
+
+ default:
+ break;
+ }
+
+ mutex_unlock(&vk->mutex);
+
+ return ret;
+}
+
+static const struct file_operations bcm_vk_fops = {
+ .owner = THIS_MODULE,
+ .open = bcm_vk_open,
+ .read = bcm_vk_read,
+ .write = bcm_vk_write,
+ .release = bcm_vk_release,
+ .mmap = bcm_vk_mmap,
+ .unlocked_ioctl = bcm_vk_ioctl,
+};
+
+static int bcm_vk_on_panic(struct notifier_block *nb,
+ unsigned long e, void *p)
+{
+ struct bcm_vk *vk = container_of(nb, struct bcm_vk, panic_nb);
+
+ bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_HARD);
+
+ return 0;
+}
+
+static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ int err;
+ int i;
+ int id;
+ int irq;
+ char name[20];
+ struct bcm_vk *vk;
+ struct device *dev = &pdev->dev;
+ struct miscdevice *misc_device;
+ uint32_t boot_status;
+
+ /* allocate vk structure which is tied to kref for freeing */
+ vk = kzalloc(sizeof(*vk), GFP_KERNEL);
+ if (!vk)
+ return -ENOMEM;
+
+ kref_init(&vk->kref);
+ if (nr_ib_sgl_blk > BCM_VK_IB_SGL_BLK_MAX) {
+ dev_warn(dev, "Inband SGL blk %d limited to max %d\n",
+ nr_ib_sgl_blk, BCM_VK_IB_SGL_BLK_MAX);
+ nr_ib_sgl_blk = BCM_VK_IB_SGL_BLK_MAX;
+ }
+ vk->ib_sgl_size = nr_ib_sgl_blk * VK_MSGQ_BLK_SIZE;
+ vk->pdev = pdev;
+ mutex_init(&vk->mutex);
+
+ err = pci_enable_device(pdev);
+ if (err) {
+ dev_err(dev, "Cannot enable PCI device\n");
+ return err;
+ }
+
+ err = pci_request_regions(pdev, DRV_MODULE_NAME);
+ if (err) {
+ dev_err(dev, "Cannot obtain PCI resources\n");
+ goto err_disable_pdev;
+ }
+
+ /* make sure DMA is good */
+ err = dma_set_mask_and_coherent(&pdev->dev,
+ DMA_BIT_MASK(BCM_VK_DMA_BITS));
+ if (err) {
+ dev_err(dev, "failed to set DMA mask\n");
+ goto err_disable_pdev;
+ }
+
+ /* The tdma is a scratch area for some DMA testings. */
+ if (nr_scratch_pages) {
+ vk->tdma_vaddr = dma_alloc_coherent(dev,
+ nr_scratch_pages * PAGE_SIZE,
+ &vk->tdma_addr, GFP_KERNEL);
+ if (!vk->tdma_vaddr) {
+ err = -ENOMEM;
+ goto err_disable_pdev;
+ }
+ }
+
+ pci_set_master(pdev);
+ pci_set_drvdata(pdev, vk);
+
+ irq = pci_alloc_irq_vectors(pdev,
+ 1,
+ VK_MSIX_IRQ_MAX,
+ PCI_IRQ_MSI | PCI_IRQ_MSIX);
+
+ if (irq < VK_MSIX_IRQ_MAX) {
+ dev_err(dev, "failed to get %d MSIX interrupts, ret(%d)\n",
+ VK_MSIX_IRQ_MAX, irq);
+ err = (irq >= 0) ? -EINVAL : irq;
+ goto err_disable_pdev;
+ }
+
+ dev_info(dev, "Number of IRQs %d allocated.\n", irq);
+
+ for (i = 0; i < MAX_BAR; i++) {
+ /* multiple by 2 for 64 bit BAR mapping */
+ vk->bar[i] = pci_ioremap_bar(pdev, i * 2);
+ if (!vk->bar[i]) {
+ dev_err(dev, "failed to remap BAR%d\n", i);
+ goto err_iounmap;
+ }
+ }
+
+ for (vk->num_irqs = 0;
+ vk->num_irqs < VK_MSIX_MSGQ_MAX;
+ vk->num_irqs++) {
+ err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
+ bcm_vk_msgq_irqhandler,
+ IRQF_SHARED, DRV_MODULE_NAME, vk);
+ if (err) {
+ dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n",
+ pdev->irq + vk->num_irqs, vk->num_irqs + 1);
+ goto err_irq;
+ }
+ }
+ /* one irq for notification from VK */
+ err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
+ bcm_vk_notf_irqhandler,
+ IRQF_SHARED, DRV_MODULE_NAME, vk);
+ if (err) {
+ dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n",
+ pdev->irq + vk->num_irqs, vk->num_irqs + 1);
+ goto err_irq;
+ }
+ vk->num_irqs++;
+
+ id = ida_simple_get(&bcm_vk_ida, 0, 0, GFP_KERNEL);
+ if (id < 0) {
+ err = id;
+ dev_err(dev, "unable to get id\n");
+ goto err_irq;
+ }
+
+ vk->misc_devid = id;
+ snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
+ misc_device = &vk->miscdev;
+ misc_device->minor = MISC_DYNAMIC_MINOR;
+ misc_device->name = kstrdup(name, GFP_KERNEL);
+ if (!misc_device->name) {
+ err = -ENOMEM;
+ goto err_ida_remove;
+ }
+ misc_device->fops = &bcm_vk_fops,
+
+ err = misc_register(misc_device);
+ if (err) {
+ dev_err(dev, "failed to register device\n");
+ goto err_kfree_name;
+ }
+
+ err = bcm_vk_msg_init(vk);
+ if (err) {
+ dev_err(dev, "failed to init msg queue info\n");
+ goto err_misc_deregister;
+ }
+
+ /* sync other info */
+ bcm_vk_sync_card_info(vk);
+
+ /* register for panic notifier */
+ vk->panic_nb.notifier_call = bcm_vk_on_panic;
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &vk->panic_nb);
+
+ snprintf(name, sizeof(name), KBUILD_MODNAME ".%d_ttyVK", id);
+ err = bcm_vk_tty_init(vk, name);
+ if (err)
+ goto err_unregister_panic_notifier;
+
+ /*
+ * lets trigger an auto download. We don't want to do it serially here
+ * because at probing time, it is not supposed to block for a long time.
+ */
+ boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
+ if (auto_load) {
+ if ((boot_status & BOOT_STATE_MASK) == BROM_RUNNING) {
+ if (bcm_vk_trigger_autoload(vk))
+ goto err_bcm_vk_tty_exit;
+ } else {
+ dev_info(dev,
+ "Auto-load skipped - BROM not in proper state (0x%x)\n",
+ boot_status);
+ }
+ }
+
+ /* enable hb */
+ bcm_vk_hb_init(vk);
+
+ dev_info(dev, "BCM-VK:%u created, 0x%p\n", id, vk);
+
+ return 0;
+
+err_bcm_vk_tty_exit:
+ bcm_vk_tty_exit(vk);
+
+err_unregister_panic_notifier:
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &vk->panic_nb);
+
+err_misc_deregister:
+ misc_deregister(misc_device);
+
+err_kfree_name:
+ kfree(misc_device->name);
+ misc_device->name = NULL;
+
+err_ida_remove:
+ ida_simple_remove(&bcm_vk_ida, id);
+
+err_irq:
+ for (i = 0; i < vk->num_irqs; i++)
+ devm_free_irq(dev, pci_irq_vector(pdev, i), vk);
+
+ pci_disable_msix(pdev);
+ pci_disable_msi(pdev);
+
+err_iounmap:
+ for (i = 0; i < MAX_BAR; i++) {
+ if (vk->bar[i])
+ pci_iounmap(pdev, vk->bar[i]);
+ }
+ pci_release_regions(pdev);
+
+err_disable_pdev:
+ pci_disable_device(pdev);
+
+ return err;
+}
+
+void bcm_vk_release_data(struct kref *kref)
+{
+ struct bcm_vk *vk = container_of(kref, struct bcm_vk, kref);
+
+ /* use raw print, as dev is gone */
+ pr_info("BCM-VK:%d release data 0x%p\n", vk->misc_devid, vk);
+ kfree(vk);
+}
+
+static void bcm_vk_remove(struct pci_dev *pdev)
+{
+ int i;
+ struct bcm_vk *vk = pci_get_drvdata(pdev);
+ struct miscdevice *misc_device = &vk->miscdev;
+
+ bcm_vk_hb_deinit(vk);
+ bcm_vk_tty_exit(vk);
+
+ /* unregister panic notifier */
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &vk->panic_nb);
+
+ bcm_vk_msg_remove(vk);
+
+ if (vk->tdma_vaddr)
+ dma_free_coherent(&pdev->dev, nr_scratch_pages * PAGE_SIZE,
+ vk->tdma_vaddr, vk->tdma_addr);
+
+ /* remove if name is set which means misc dev registered */
+ if (misc_device->name) {
+ misc_deregister(misc_device);
+ kfree(misc_device->name);
+ ida_simple_remove(&bcm_vk_ida, vk->misc_devid);
+ }
+ for (i = 0; i < vk->num_irqs; i++)
+ devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), vk);
+
+ pci_disable_msix(pdev);
+ pci_disable_msi(pdev);
+
+ cancel_work_sync(&vk->wq_work);
+ destroy_workqueue(vk->wq_thread);
+
+ for (i = 0; i < MAX_BAR; i++) {
+ if (vk->bar[i])
+ pci_iounmap(pdev, vk->bar[i]);
+ }
+
+ dev_info(&pdev->dev, "BCM-VK:%d released\n", vk->misc_devid);
+
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+
+ kref_put(&vk->kref, bcm_vk_release_data);
+}
+
+static const struct pci_device_id bcm_vk_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VALKYRIE), },
+ { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VIPER), },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, bcm_vk_ids);
+
+static struct pci_driver pci_driver = {
+ .name = DRV_MODULE_NAME,
+ .id_table = bcm_vk_ids,
+ .probe = bcm_vk_probe,
+ .remove = bcm_vk_remove,
+};
+module_pci_driver(pci_driver);
+
+MODULE_DESCRIPTION("Broadcom VK Host Driver");
+MODULE_AUTHOR("Scott Branden <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
new file mode 100644
index 000000000000..651fba2149e2
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
@@ -0,0 +1,1359 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/hash.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#include "bcm_vk.h"
+#include "bcm_vk_msg.h"
+#include "bcm_vk_sg.h"
+
+#if defined(CONFIG_BCM_VK_H2VK_VERIFY_AND_RETRY)
+/*
+ * Turn on the following to verify the data passed down to VK is good, and
+ * if not, do retry. This is a debug/workaround on FPGA PCIe timing issues
+ * but may be found useful for debugging other PCIe hardware issues.
+ */
+static void bcm_vk_h2vk_verify_idx(struct device *dev,
+ const char *tag,
+ volatile uint32_t *idx,
+ uint32_t expected)
+{
+ unsigned int count = 0;
+
+ while (*idx != expected) {
+ count++;
+ dev_err(dev, "[%d] %s exp %d idx %d\n",
+ count, tag, expected, *idx);
+
+ /* write again */
+ *idx = expected;
+ }
+}
+
+static void bcm_vk_h2vk_verify_blk(struct device *dev,
+ const struct vk_msg_blk *src,
+ volatile struct vk_msg_blk *dst)
+
+{
+ struct vk_msg_blk rd_bck;
+ unsigned int count = 0;
+
+ rd_bck = *dst;
+ while (memcmp(&rd_bck, src, sizeof(rd_bck)) != 0) {
+ count++;
+ dev_err(dev,
+ "[%d]Src Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
+ count,
+ src->function_id,
+ src->size,
+ src->queue_id,
+ src->msg_id,
+ src->context_id,
+ src->args[0],
+ src->args[1]);
+ dev_err(dev,
+ "[%d]Rdb Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
+ count,
+ rd_bck.function_id,
+ rd_bck.size,
+ rd_bck.queue_id,
+ rd_bck.msg_id,
+ rd_bck.context_id,
+ rd_bck.args[0],
+ rd_bck.args[1]);
+
+ *dst = *src;
+ rd_bck = *dst;
+ }
+}
+#else
+static void bcm_vk_h2vk_verify_idx(struct device __always_unused *dev,
+ const char __always_unused *tag,
+ volatile uint32_t __always_unused *idx,
+ uint32_t __always_unused expected)
+{
+}
+
+static void bcm_vk_h2vk_verify_blk
+ (struct device __always_unused *dev,
+ const struct vk_msg_blk __always_unused *src,
+ volatile struct vk_msg_blk __always_unused *dst)
+
+{
+}
+#endif
+
+#if defined(CONFIG_BCM_VK_QSTATS)
+
+/* Use default value of 20000 rd/wr per update */
+#if !defined(BCM_VK_QSTATS_ACC_CNT)
+#define BCM_VK_QSTATS_ACC_CNT 20000
+#endif
+
+void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
+ struct bcm_vk_qstats *qstats, uint32_t occupancy)
+{
+ struct bcm_vk_qs_cnts *qcnts = &qstats->qcnts;
+
+ if (occupancy > qcnts->max_occ) {
+ qcnts->max_occ = occupancy;
+ if (occupancy > qcnts->max_abs)
+ qcnts->max_abs = occupancy;
+ }
+
+ qcnts->acc_sum += occupancy;
+ if (++qcnts->cnt >= BCM_VK_QSTATS_ACC_CNT) {
+ /* log average and clear counters */
+ dev_info(&vk->pdev->dev,
+ "%s[%d]: Max: [%3d/%3d] Acc %d num %d, Aver %d\n",
+ tag, qstats->q_num,
+ qcnts->max_occ, qcnts->max_abs,
+ qcnts->acc_sum,
+ qcnts->cnt,
+ qcnts->acc_sum / qcnts->cnt);
+
+ qcnts->cnt = 0;
+ qcnts->max_occ = 0;
+ qcnts->acc_sum = 0;
+ }
+}
+#endif
+
+/* number of retries when enqueue message fails before returning EAGAIN */
+#define BCM_VK_H2VK_ENQ_RETRY 10
+#define BCM_VK_H2VK_ENQ_RETRY_DELAY_MS 50
+
+bool bcm_vk_drv_access_ok(struct bcm_vk *vk)
+{
+ return (!!atomic_read(&vk->msgq_inited));
+}
+
+static void bcm_vk_set_host_alert(struct bcm_vk *vk, uint32_t bit_mask)
+{
+ struct bcm_vk_alert *alert = &vk->host_alert;
+ unsigned long flags;
+
+ /* use irqsave version as this maybe called inside timer interrupt */
+ spin_lock_irqsave(&vk->host_alert_lock, flags);
+ alert->notfs |= bit_mask;
+ spin_unlock_irqrestore(&vk->host_alert_lock, flags);
+
+ if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
+ queue_work(vk->wq_thread, &vk->wq_work);
+}
+
+/*
+ * Heartbeat related defines
+ * The heartbeat from host is a last resort. If stuck condition happens
+ * on the card, firmware is supposed to detect it. Therefore, the heartbeat
+ * values used will be more relaxed on the driver, which need to be bigger
+ * than the watchdog timeout on the card.
+ */
+#define BCM_VK_HB_TIMER_S 3
+#define BCM_VK_HB_TIMER_VALUE (BCM_VK_HB_TIMER_S * HZ)
+#define BCM_VK_HB_LOST_MAX 4
+
+static void bcm_vk_hb_poll(struct timer_list *t)
+{
+ uint32_t uptime_s;
+ struct bcm_vk_hb_ctrl *hb = container_of(t, struct bcm_vk_hb_ctrl,
+ timer);
+ struct bcm_vk *vk = container_of(hb, struct bcm_vk, hb_ctrl);
+
+ if (bcm_vk_drv_access_ok(vk)) {
+ /* read uptime from register and compare */
+ uptime_s = vkread32(vk, BAR_0, BAR_OS_UPTIME);
+
+ if (uptime_s == hb->last_uptime)
+ hb->lost_cnt++;
+
+ dev_dbg(&vk->pdev->dev, "Last uptime %d current %d, lost %d\n",
+ hb->last_uptime, uptime_s, hb->lost_cnt);
+
+ /*
+ * if the interface goes down without any activity, a value
+ * of 0xFFFFFFFF will be continuously read, and the detection
+ * will be happened eventually.
+ */
+ hb->last_uptime = uptime_s;
+ } else {
+ /* reset heart beat lost cnt */
+ hb->lost_cnt = 0;
+ }
+
+ /* next, check if heartbeat exceeds limit */
+ if (hb->lost_cnt > BCM_VK_HB_LOST_MAX) {
+ dev_err(&vk->pdev->dev, "Heartbeat Misses %d times, %d s!\n",
+ BCM_VK_HB_LOST_MAX,
+ BCM_VK_HB_LOST_MAX * BCM_VK_HB_TIMER_S);
+
+ bcm_vk_blk_drv_access(vk);
+ bcm_vk_set_host_alert(vk, ERR_LOG_HOST_HB_FAIL);
+ }
+ /* re-arm timer */
+ mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
+}
+
+void bcm_vk_hb_init(struct bcm_vk *vk)
+{
+ struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
+
+ timer_setup(&hb->timer, bcm_vk_hb_poll, 0);
+ mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
+}
+
+void bcm_vk_hb_deinit(struct bcm_vk *vk)
+{
+ struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
+
+ del_timer(&hb->timer);
+}
+
+static void bcm_vk_msgid_bitmap_clear(struct bcm_vk *vk,
+ unsigned int start,
+ unsigned int nbits)
+{
+ spin_lock(&vk->msg_id_lock);
+ bitmap_clear(vk->bmap, start, nbits);
+ spin_unlock(&vk->msg_id_lock);
+}
+
+/*
+ * allocate a ctx per file struct
+ */
+static struct bcm_vk_ctx *bcm_vk_get_ctx(struct bcm_vk *vk,
+ struct task_struct *ppid)
+{
+ uint32_t i;
+ struct bcm_vk_ctx *ctx = NULL;
+ const pid_t pid = task_pid_nr(ppid);
+ uint32_t hash_idx = hash_32(pid, VK_PID_HT_SHIFT_BIT);
+
+ spin_lock(&vk->ctx_lock);
+
+ /* check if it is in reset, if so, don't allow */
+ if (vk->reset_ppid) {
+ dev_err(&vk->pdev->dev,
+ "No context allowed during reset by pid %d\n",
+ task_pid_nr(vk->reset_ppid));
+
+ goto in_reset_exit;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
+ if (!vk->ctx[i].in_use) {
+ vk->ctx[i].in_use = true;
+ ctx = &vk->ctx[i];
+ break;
+ }
+ }
+
+ if (!ctx) {
+ dev_err(&vk->pdev->dev, "All context in use\n");
+
+ goto all_in_use_exit;
+ }
+
+ /* set the pid and insert it to hash table */
+ ctx->ppid = ppid;
+ ctx->hash_idx = hash_idx;
+ list_add_tail(&ctx->node, &vk->pid_ht[hash_idx].head);
+
+ /* increase kref */
+ kref_get(&vk->kref);
+
+ /* clear counter */
+ ctx->pend_cnt = 0;
+all_in_use_exit:
+in_reset_exit:
+ spin_unlock(&vk->ctx_lock);
+
+ return ctx;
+}
+
+static uint16_t bcm_vk_get_msg_id(struct bcm_vk *vk)
+{
+ uint16_t rc = VK_MSG_ID_OVERFLOW;
+ uint16_t test_bit_count = 0;
+
+ spin_lock(&vk->msg_id_lock);
+ while (test_bit_count < VK_MSG_ID_BITMAP_SIZE) {
+ vk->msg_id++;
+ vk->msg_id = vk->msg_id & VK_MSG_ID_BITMAP_MASK;
+ if (test_bit(vk->msg_id, vk->bmap)) {
+ test_bit_count++;
+ continue;
+ }
+ rc = vk->msg_id;
+ bitmap_set(vk->bmap, vk->msg_id, 1);
+ break;
+ }
+ spin_unlock(&vk->msg_id_lock);
+
+ return rc;
+}
+
+static int bcm_vk_free_ctx(struct bcm_vk *vk, struct bcm_vk_ctx *ctx)
+{
+ uint32_t idx;
+ uint32_t hash_idx;
+ pid_t pid;
+ struct bcm_vk_ctx *entry;
+ int count = 0;
+
+ if (ctx == NULL) {
+ dev_err(&vk->pdev->dev, "NULL context detected\n");
+ return -EINVAL;
+ }
+ idx = ctx->idx;
+ pid = task_pid_nr(ctx->ppid);
+
+ spin_lock(&vk->ctx_lock);
+
+ if (!vk->ctx[idx].in_use) {
+ dev_err(&vk->pdev->dev, "context[%d] not in use!\n", idx);
+ } else {
+ vk->ctx[idx].in_use = false;
+ vk->ctx[idx].miscdev = NULL;
+
+ /* Remove it from hash list and see if it is the last one. */
+ list_del(&ctx->node);
+ hash_idx = ctx->hash_idx;
+ list_for_each_entry(entry, &vk->pid_ht[hash_idx].head, node) {
+ if (task_pid_nr(entry->ppid) == pid)
+ count++;
+ }
+ }
+
+ spin_unlock(&vk->ctx_lock);
+
+ return count;
+}
+
+static void bcm_vk_free_wkent(struct device *dev, struct bcm_vk_wkent *entry)
+{
+ bcm_vk_sg_free(dev, entry->dma, VK_DMA_MAX_ADDRS);
+
+ kfree(entry->vk2h_msg);
+ kfree(entry);
+}
+
+static void bcm_vk_drain_all_pend(struct device *dev,
+ struct bcm_vk_msg_chan *chan,
+ struct bcm_vk_ctx *ctx)
+{
+ uint32_t num;
+ struct bcm_vk_wkent *entry, *tmp;
+ struct bcm_vk *vk;
+ struct list_head del_q;
+
+ if (ctx)
+ vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
+
+ INIT_LIST_HEAD(&del_q);
+ spin_lock(&chan->pendq_lock);
+ for (num = 0; num < chan->q_nr; num++) {
+ list_for_each_entry_safe(entry, tmp, &chan->pendq[num], node) {
+ if (ctx == NULL) {
+ list_del(&entry->node);
+ list_add_tail(&entry->node, &del_q);
+ } else if (entry->ctx->idx == ctx->idx) {
+ struct vk_msg_blk *msg;
+ int bit_set;
+ bool responded;
+
+ /* if it is specific ctx, log for any stuck */
+ msg = entry->h2vk_msg;
+ bit_set = test_bit(msg->msg_id, vk->bmap);
+ responded = entry->vk2h_msg ? true : false;
+ dev_info(dev,
+ "Drained: fid %u size %u msg 0x%x(seq-%x) ctx 0x%x[fd-%d] args:[0x%x 0x%x] resp %s, bmap %d\n",
+ msg->function_id, msg->size,
+ msg->msg_id, entry->seq_num,
+ msg->context_id, entry->ctx->idx,
+ msg->args[0], msg->args[1],
+ responded ? "T" : "F", bit_set);
+ list_del(&entry->node);
+ list_add_tail(&entry->node, &del_q);
+ if (responded)
+ ctx->pend_cnt--;
+ else if (bit_set)
+ bcm_vk_msgid_bitmap_clear(vk,
+ msg->msg_id,
+ 1);
+ }
+ }
+ }
+ spin_unlock(&chan->pendq_lock);
+
+ /* batch clean up */
+ num = 0;
+ list_for_each_entry_safe(entry, tmp, &del_q, node) {
+ list_del(&entry->node);
+ bcm_vk_free_wkent(dev, entry);
+ num++;
+ }
+ if (num)
+ dev_info(dev, "Total drained items %d\n", num);
+}
+
+bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk)
+{
+ uint32_t rdy_marker = 0;
+ uint32_t fw_status;
+
+ fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
+
+ if ((fw_status & VK_FWSTS_READY) == VK_FWSTS_READY)
+ rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
+
+ return (rdy_marker == VK_BAR1_MSGQ_RDY_MARKER);
+}
+
+/*
+ * Function to sync up the messages queue info that is provided by BAR1
+ */
+int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync)
+{
+ struct bcm_vk_msgq *msgq = NULL;
+ struct device *dev = &vk->pdev->dev;
+ uint32_t msgq_off;
+ uint32_t num_q;
+ struct bcm_vk_msg_chan *chan_list[] = {&vk->h2vk_msg_chan,
+ &vk->vk2h_msg_chan};
+ struct bcm_vk_msg_chan *chan = NULL;
+ int i, j;
+ int ret = 0;
+
+ /*
+ * If the driver is loaded at startup where vk OS is not up yet,
+ * the msgq-info may not be available until a later time. In
+ * this case, we skip and the sync function is supposed to be
+ * called again.
+ */
+ if (!bcm_vk_msgq_marker_valid(vk)) {
+ dev_info(dev, "BAR1 msgq marker not initialized.\n");
+ return ret;
+ }
+
+ msgq_off = vkread32(vk, BAR_1, VK_BAR1_MSGQ_CTRL_OFF);
+
+ /* each side is always half the total */
+ num_q = vk->h2vk_msg_chan.q_nr = vk->vk2h_msg_chan.q_nr =
+ vkread32(vk, BAR_1, VK_BAR1_MSGQ_NR) / 2;
+
+ /* first msgq location */
+ msgq = (struct bcm_vk_msgq *)(vk->bar[BAR_1] + msgq_off);
+
+ /*
+ * if this function is called when it is already inited,
+ * something is wrong
+ */
+ if (bcm_vk_drv_access_ok(vk) && (!force_sync)) {
+ dev_err(dev, "Msgq info already in sync\n");
+ ret = -EPERM;
+ goto already_inited;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(chan_list); i++) {
+ chan = chan_list[i];
+ memset(chan->sync_qinfo, 0, sizeof(chan->sync_qinfo));
+
+ for (j = 0; j < num_q; j++) {
+ chan->msgq[j] = msgq;
+
+ dev_info(dev,
+ "MsgQ[%d] type %d num %d, @ 0x%x, rd_idx %d wr_idx %d, size %d, nxt 0x%x\n",
+ j,
+ chan->msgq[j]->type,
+ chan->msgq[j]->num,
+ chan->msgq[j]->start,
+ chan->msgq[j]->rd_idx,
+ chan->msgq[j]->wr_idx,
+ chan->msgq[j]->size,
+ chan->msgq[j]->nxt);
+
+ /* formulate and record static info */
+ chan->sync_qinfo[j].q_start =
+ vk->bar[BAR_1] + chan->msgq[j]->start;
+ chan->sync_qinfo[j].q_size = chan->msgq[j]->size;
+ /* set low threshold as 50% or 1/2 */
+ chan->sync_qinfo[j].q_low =
+ chan->sync_qinfo[j].q_size >> 1;
+ chan->sync_qinfo[j].q_mask =
+ chan->sync_qinfo[j].q_size - 1;
+
+ msgq = (struct bcm_vk_msgq *)
+ ((char *)msgq + sizeof(*msgq) + msgq->nxt);
+
+ rmb(); /* do a read mb to guarantee */
+ }
+ }
+ atomic_set(&vk->msgq_inited, 1);
+
+already_inited:
+ return ret;
+}
+
+static int bcm_vk_msg_chan_init(struct bcm_vk_msg_chan *chan)
+{
+ int rc = 0;
+ uint32_t i;
+
+ mutex_init(&chan->msgq_mutex);
+ spin_lock_init(&chan->pendq_lock);
+ for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
+ INIT_LIST_HEAD(&chan->pendq[i]);
+#if defined(CONFIG_BCM_VK_QSTATS)
+ chan->qstats[i].q_num = i;
+#endif
+ }
+
+ return rc;
+}
+
+static void bcm_vk_append_pendq(struct bcm_vk_msg_chan *chan, uint16_t q_num,
+ struct bcm_vk_wkent *entry)
+{
+ spin_lock(&chan->pendq_lock);
+ list_add_tail(&entry->node, &chan->pendq[q_num]);
+ if (entry->vk2h_msg)
+ entry->ctx->pend_cnt++;
+ spin_unlock(&chan->pendq_lock);
+}
+
+static uint32_t bcm_vk_append_ib_sgl(struct bcm_vk *vk,
+ struct bcm_vk_wkent *entry,
+ struct _vk_data *data,
+ unsigned int num_planes)
+{
+ unsigned int i;
+ unsigned int item_cnt = 0;
+ struct device *dev = &vk->pdev->dev;
+ struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
+ struct vk_msg_blk *msg = &entry->h2vk_msg[0];
+ struct bcm_vk_msgq *msgq;
+ struct bcm_vk_sync_qinfo *qinfo;
+ uint32_t ib_sgl_size = 0;
+ uint8_t *buf = (uint8_t *)&entry->h2vk_msg[entry->h2vk_blks];
+ uint32_t avail;
+
+ /* check if high watermark is hit, and if so, skip */
+ msgq = chan->msgq[msg->queue_id];
+ qinfo = &chan->sync_qinfo[msg->queue_id];
+ avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
+ if (avail < qinfo->q_low) {
+ dev_dbg(dev, "Skip inserting inband SGL, [0x%x/0x%x]\n",
+ avail, qinfo->q_size);
+ return ib_sgl_size;
+ }
+
+ for (i = 0; i < num_planes; i++) {
+ if (data[i].address &&
+ (ib_sgl_size + data[i].size) <= vk->ib_sgl_size) {
+
+ item_cnt++;
+ memcpy(buf, entry->dma[i].sglist, data[i].size);
+ ib_sgl_size += data[i].size;
+ buf += data[i].size;
+ }
+ }
+
+ dev_dbg(dev, "Num %u sgl items appended, size 0x%x, room 0x%x\n",
+ item_cnt, ib_sgl_size, vk->ib_sgl_size);
+
+ /* round up size */
+ ib_sgl_size = (ib_sgl_size + VK_MSGQ_BLK_SIZE - 1)
+ >> VK_MSGQ_BLK_SZ_SHIFT;
+
+ return ib_sgl_size;
+}
+
+void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num,
+ uint32_t db_val)
+{
+ /* press door bell based on q_num */
+ vkwrite32(vk,
+ db_val,
+ BAR_0,
+ VK_BAR0_REGSEG_DB_BASE + q_num * VK_BAR0_REGSEG_DB_REG_GAP);
+}
+
+static int bcm_h2vk_msg_enqueue(struct bcm_vk *vk, struct bcm_vk_wkent *entry)
+{
+ static uint32_t seq_num;
+ struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
+ struct device *dev = &vk->pdev->dev;
+ struct vk_msg_blk *src = &entry->h2vk_msg[0];
+
+ volatile struct vk_msg_blk *dst;
+ struct bcm_vk_msgq *msgq;
+ struct bcm_vk_sync_qinfo *qinfo;
+ uint32_t q_num = src->queue_id;
+ uint32_t wr_idx; /* local copy */
+ uint32_t i;
+ uint32_t avail;
+ uint32_t retry;
+
+ if (entry->h2vk_blks != src->size + 1) {
+ dev_err(dev, "number of blks %d not matching %d MsgId[0x%x]: func %d ctx 0x%x\n",
+ entry->h2vk_blks,
+ src->size + 1,
+ src->msg_id,
+ src->function_id,
+ src->context_id);
+ return -EMSGSIZE;
+ }
+
+ msgq = chan->msgq[q_num];
+ qinfo = &chan->sync_qinfo[q_num];
+
+ rmb(); /* start with a read barrier */
+ mutex_lock(&chan->msgq_mutex);
+
+ avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
+
+#if defined(CONFIG_BCM_VK_QSTATS)
+ bcm_vk_update_qstats(vk, "h2vk", &chan->qstats[q_num],
+ qinfo->q_size - avail);
+#endif
+ /* if not enough space, return EAGAIN and let app handles it */
+ retry = 0;
+ while ((avail < entry->h2vk_blks)
+ && (retry++ < BCM_VK_H2VK_ENQ_RETRY)) {
+ mutex_unlock(&chan->msgq_mutex);
+
+ msleep(BCM_VK_H2VK_ENQ_RETRY_DELAY_MS);
+ mutex_lock(&chan->msgq_mutex);
+ avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
+ }
+ if (retry > BCM_VK_H2VK_ENQ_RETRY) {
+ mutex_unlock(&chan->msgq_mutex);
+ return -EAGAIN;
+ }
+
+ /* at this point, mutex is taken and there is enough space */
+ entry->seq_num = seq_num++; /* update debug seq number */
+ wr_idx = msgq->wr_idx;
+
+ if (wr_idx >= qinfo->q_size) {
+ dev_crit(dev, "Invalid wr_idx 0x%x => max 0x%x!",
+ wr_idx, qinfo->q_size);
+ bcm_vk_blk_drv_access(vk);
+ bcm_vk_set_host_alert(vk, ERR_LOG_HOST_PCIE_DWN);
+ goto idx_err;
+ }
+
+ dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
+ for (i = 0; i < entry->h2vk_blks; i++) {
+ *dst = *src;
+
+ bcm_vk_h2vk_verify_blk(dev, src, dst);
+
+ src++;
+ wr_idx = VK_MSGQ_INC(qinfo, wr_idx, 1);
+ dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
+ }
+
+ /* flush the write pointer */
+ msgq->wr_idx = wr_idx;
+ wmb(); /* flush */
+
+ bcm_vk_h2vk_verify_idx(dev, "wr_idx", &msgq->wr_idx, wr_idx);
+
+ /* log new info for debugging */
+ dev_dbg(dev,
+ "MsgQ[%d] [Rd Wr] = [%d %d] blks inserted %d - Q = [u-%d a-%d]/%d\n",
+ msgq->num,
+ msgq->rd_idx, msgq->wr_idx, entry->h2vk_blks,
+ VK_MSGQ_OCCUPIED(msgq, qinfo),
+ VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
+ msgq->size);
+ /*
+ * press door bell based on queue number. 1 is added to the wr_idx
+ * to avoid the value of 0 appearing on the VK side to distinguish
+ * from initial value.
+ */
+ bcm_h2vk_doorbell(vk, q_num, wr_idx + 1);
+idx_err:
+ mutex_unlock(&chan->msgq_mutex);
+ return 0;
+}
+
+int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type,
+ pid_t pid)
+{
+ int rc = 0;
+ struct bcm_vk_wkent *entry;
+ struct device *dev = &vk->pdev->dev;
+
+ /*
+ * check if the marker is still good. Sometimes, the PCIe interface may
+ * have gone done, and if so and we ship down thing based on broken
+ * values, kernel may panic.
+ */
+ if (!bcm_vk_msgq_marker_valid(vk)) {
+ dev_info(dev, "PCIe comm chan - invalid marker (0x%x)!\n",
+ vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY));
+ return -EINVAL;
+ }
+
+ entry = kzalloc(sizeof(struct bcm_vk_wkent) +
+ sizeof(struct vk_msg_blk), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ /* just fill up non-zero data */
+ entry->h2vk_msg[0].function_id = VK_FID_SHUTDOWN;
+ entry->h2vk_msg[0].queue_id = 0; /* use highest queue */
+ entry->h2vk_blks = 1; /* always 1 block */
+
+ entry->h2vk_msg[0].args[0] = shut_type;
+ entry->h2vk_msg[0].args[1] = pid;
+
+ rc = bcm_h2vk_msg_enqueue(vk, entry);
+ if (rc)
+ dev_err(dev,
+ "Sending shutdown message to q %d for pid %d fails.\n",
+ entry->h2vk_msg[0].queue_id, pid);
+
+ kfree(entry);
+
+ return rc;
+}
+
+int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
+{
+ int rc = 0;
+ pid_t pid = task_pid_nr(ppid);
+ struct device *dev = &vk->pdev->dev;
+
+ /*
+ * don't send down or do anything if message queue is not initialized
+ * and if it is the reset session, clear it.
+ */
+ if (!bcm_vk_drv_access_ok(vk)) {
+
+ if (vk->reset_ppid == ppid)
+ vk->reset_ppid = NULL;
+ return -EPERM;
+ }
+
+ dev_dbg(dev, "No more sessions, shut down pid %d\n", pid);
+
+ /* only need to do it if it is not the reset process */
+ if (vk->reset_ppid != ppid)
+ rc = bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_PID, pid);
+ else
+ /* reset the pointer if it is exiting last session */
+ vk->reset_ppid = NULL;
+
+ return rc;
+}
+
+static struct bcm_vk_wkent *bcm_vk_find_pending(struct bcm_vk *vk,
+ struct bcm_vk_msg_chan *chan,
+ uint16_t q_num,
+ uint16_t msg_id)
+{
+ bool found = false;
+ struct bcm_vk_wkent *entry;
+
+ spin_lock(&chan->pendq_lock);
+ list_for_each_entry(entry, &chan->pendq[q_num], node) {
+
+ if (entry->h2vk_msg[0].msg_id == msg_id) {
+ list_del(&entry->node);
+ found = true;
+ bcm_vk_msgid_bitmap_clear(vk, msg_id, 1);
+ break;
+ }
+ }
+ spin_unlock(&chan->pendq_lock);
+ return ((found) ? entry : NULL);
+}
+
+static uint32_t bcm_vk2h_msg_dequeue(struct bcm_vk *vk)
+{
+ struct device *dev = &vk->pdev->dev;
+ struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
+ struct vk_msg_blk *data;
+ volatile struct vk_msg_blk *src;
+ struct vk_msg_blk *dst;
+ struct bcm_vk_msgq *msgq;
+ struct bcm_vk_sync_qinfo *qinfo;
+ struct bcm_vk_wkent *entry;
+ uint32_t rd_idx;
+ uint32_t q_num, j;
+ uint32_t num_blks;
+ uint32_t total = 0;
+
+ /*
+ * drain all the messages from the queues, and find its pending
+ * entry in the h2vk queue, based on msg_id & q_num, and move the
+ * entry to the vk2h pending queue, waiting for user space
+ * program to extract
+ */
+ mutex_lock(&chan->msgq_mutex);
+ rmb(); /* start with a read barrier */
+ for (q_num = 0; q_num < chan->q_nr; q_num++) {
+ msgq = chan->msgq[q_num];
+ qinfo = &chan->sync_qinfo[q_num];
+
+ while (!VK_MSGQ_EMPTY(msgq)) {
+
+ /*
+ * Make a local copy and get pointer to src blk
+ * The rd_idx is masked before getting the pointer to
+ * avoid out of bound access in case the interface goes
+ * down. It will end up pointing to the last block in
+ * the buffer, but subsequent src->size check would be
+ * able to catch this.
+ */
+ rd_idx = msgq->rd_idx;
+ src = VK_MSGQ_BLK_ADDR(qinfo,
+ rd_idx & VK_MSGQ_SIZE_MASK(qinfo));
+
+ if ((rd_idx >= qinfo->q_size)
+ || (src->size > (qinfo->q_size - 1))) {
+ dev_crit(dev,
+ "Invalid rd_idx 0x%x or size 0x%x => max 0x%x!",
+ rd_idx, src->size, qinfo->q_size);
+ bcm_vk_blk_drv_access(vk);
+ bcm_vk_set_host_alert(
+ vk, ERR_LOG_HOST_PCIE_DWN);
+ goto idx_err;
+ }
+
+#if defined(CONFIG_BCM_VK_QSTATS)
+ bcm_vk_update_qstats(vk, "vk2h", &chan->qstats[q_num],
+ VK_MSGQ_OCCUPIED(msgq, qinfo));
+#endif
+ num_blks = src->size + 1;
+ data = kzalloc(num_blks * VK_MSGQ_BLK_SIZE, GFP_KERNEL);
+ if (data) {
+ /* copy messages and linearize it */
+ dst = data;
+ for (j = 0; j < num_blks; j++) {
+ *dst = *src;
+
+ dst++;
+ rd_idx = VK_MSGQ_INC(qinfo, rd_idx, 1);
+ src = VK_MSGQ_BLK_ADDR(qinfo, rd_idx);
+ }
+ total++;
+ } else {
+ dev_crit(dev, "Error allocating memory\n");
+ /* just keep draining..... */
+ rd_idx = VK_MSGQ_INC(qinfo, rd_idx, num_blks);
+ }
+
+ /* flush rd pointer after a message is dequeued */
+ msgq->rd_idx = rd_idx;
+ mb(); /* do both rd/wr as we are extracting data out */
+
+ bcm_vk_h2vk_verify_idx(dev, "rd_idx",
+ &msgq->rd_idx, rd_idx);
+
+ /* log new info for debugging */
+ dev_dbg(dev,
+ "MsgQ[%d] [Rd Wr] = [%d %d] blks extracted %d - Q = [u-%d a-%d]/%d\n",
+ msgq->num,
+ msgq->rd_idx, msgq->wr_idx, num_blks,
+ VK_MSGQ_OCCUPIED(msgq, qinfo),
+ VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
+ msgq->size);
+
+ /*
+ * No need to search if it is an autonomous one-way
+ * message from driver, as these messages do not bear
+ * a h2vk pending item. Currently, only the shutdown
+ * message falls into this category.
+ */
+ if (data->function_id == VK_FID_SHUTDOWN) {
+ kfree(data);
+ continue;
+ }
+
+ /* lookup original message in h2vk direction */
+ entry = bcm_vk_find_pending(vk,
+ &vk->h2vk_msg_chan,
+ q_num,
+ data->msg_id);
+
+ /*
+ * if there is message to does not have prior send,
+ * this is the location to add here
+ */
+ if (entry) {
+ entry->vk2h_blks = num_blks;
+ entry->vk2h_msg = data;
+ bcm_vk_append_pendq(&vk->vk2h_msg_chan,
+ q_num, entry);
+
+ } else {
+ dev_crit(dev,
+ "Could not find MsgId[0x%x] for resp func %d bmap %d\n",
+ data->msg_id, data->function_id,
+ test_bit(data->msg_id, vk->bmap));
+ kfree(data);
+ }
+
+ }
+ }
+idx_err:
+ mutex_unlock(&chan->msgq_mutex);
+ dev_dbg(dev, "total %d drained from queues\n", total);
+
+ return total;
+}
+
+/*
+ * deferred work queue for draining and auto download.
+ */
+static void bcm_vk_wq_handler(struct work_struct *work)
+{
+ struct bcm_vk *vk = container_of(work, struct bcm_vk, wq_work);
+ struct device *dev = &vk->pdev->dev;
+ uint32_t tot;
+
+ /* check wq offload bit map to perform various operations */
+ if (test_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload)) {
+ /* clear bit right the way for notification */
+ clear_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload);
+ bcm_vk_handle_notf(vk);
+ }
+ if (test_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload)) {
+ bcm_vk_auto_load_all_images(vk);
+
+ /*
+ * at the end of operation, clear AUTO bit and pending
+ * bit
+ */
+ clear_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
+ clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
+ }
+
+ /* next, try to drain */
+ tot = bcm_vk2h_msg_dequeue(vk);
+
+ if (tot == 0)
+ dev_dbg(dev, "Spurious trigger for workqueue\n");
+}
+
+/*
+ * init routine for all required data structures
+ */
+static int bcm_vk_data_init(struct bcm_vk *vk)
+{
+ int rc = 0;
+ int i;
+
+ spin_lock_init(&vk->ctx_lock);
+ for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
+ vk->ctx[i].in_use = false;
+ vk->ctx[i].idx = i; /* self identity */
+ vk->ctx[i].miscdev = NULL;
+ }
+ spin_lock_init(&vk->msg_id_lock);
+ spin_lock_init(&vk->host_alert_lock);
+ vk->msg_id = 0;
+
+ /* initialize hash table */
+ for (i = 0; i < VK_PID_HT_SZ; i++)
+ INIT_LIST_HEAD(&vk->pid_ht[i].head);
+
+ INIT_WORK(&vk->wq_work, bcm_vk_wq_handler);
+ return rc;
+}
+
+irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id)
+{
+ struct bcm_vk *vk = dev_id;
+
+ if (!bcm_vk_drv_access_ok(vk)) {
+ dev_err(&vk->pdev->dev,
+ "Interrupt %d received when msgq not inited\n", irq);
+ goto skip_schedule_work;
+ }
+
+ queue_work(vk->wq_thread, &vk->wq_work);
+
+skip_schedule_work:
+ return IRQ_HANDLED;
+}
+
+int bcm_vk_open(struct inode *inode, struct file *p_file)
+{
+ struct bcm_vk_ctx *ctx;
+ struct miscdevice *miscdev = (struct miscdevice *)p_file->private_data;
+ struct bcm_vk *vk = container_of(miscdev, struct bcm_vk, miscdev);
+ struct device *dev = &vk->pdev->dev;
+ int rc = 0;
+
+ /* get a context and set it up for file */
+ ctx = bcm_vk_get_ctx(vk, current);
+ if (!ctx) {
+ dev_err(dev, "Error allocating context\n");
+ rc = -ENOMEM;
+ } else {
+
+ /*
+ * set up context and replace private data with context for
+ * other methods to use. Reason for the context is because
+ * it is allowed for multiple sessions to open the sysfs, and
+ * for each file open, when upper layer query the response,
+ * only those that are tied to a specific open should be
+ * returned. The context->idx will be used for such binding
+ */
+ ctx->miscdev = miscdev;
+ p_file->private_data = ctx;
+ dev_dbg(dev, "ctx_returned with idx %d, pid %d\n",
+ ctx->idx, task_pid_nr(ctx->ppid));
+ }
+ return rc;
+}
+
+ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ ssize_t rc = -ENOMSG;
+ struct bcm_vk_ctx *ctx = p_file->private_data;
+ struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
+ miscdev);
+ struct device *dev = &vk->pdev->dev;
+ struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
+ struct bcm_vk_wkent *entry = NULL;
+ uint32_t q_num;
+ uint32_t rsp_length;
+ bool found = false;
+
+ if (!bcm_vk_drv_access_ok(vk))
+ return -EPERM;
+
+ dev_dbg(dev, "Buf count %ld\n", count);
+ found = false;
+
+ /*
+ * search through the pendq on the vk2h chan, and return only those
+ * that belongs to the same context. Search is always from the high to
+ * the low priority queues
+ */
+ spin_lock(&chan->pendq_lock);
+ for (q_num = 0; q_num < chan->q_nr; q_num++) {
+ list_for_each_entry(entry, &chan->pendq[q_num], node) {
+ if (entry->ctx->idx == ctx->idx) {
+ if (count >=
+ (entry->vk2h_blks * VK_MSGQ_BLK_SIZE)) {
+ list_del(&entry->node);
+ ctx->pend_cnt--;
+ found = true;
+ } else {
+ /* buffer not big enough */
+ rc = -EMSGSIZE;
+ }
+ goto bcm_vk_read_loop_exit;
+ }
+ }
+ }
+ bcm_vk_read_loop_exit:
+ spin_unlock(&chan->pendq_lock);
+
+ if (found) {
+ /* retrieve the passed down msg_id */
+ entry->vk2h_msg[0].msg_id = entry->usr_msg_id;
+ rsp_length = entry->vk2h_blks * VK_MSGQ_BLK_SIZE;
+ if (copy_to_user(buf, entry->vk2h_msg, rsp_length) == 0)
+ rc = rsp_length;
+
+ bcm_vk_free_wkent(dev, entry);
+ } else if (rc == -EMSGSIZE) {
+ struct vk_msg_blk tmp_msg = entry->vk2h_msg[0];
+
+ /*
+ * in this case, return just the first block, so
+ * that app knows what size it is looking for.
+ */
+ tmp_msg.msg_id = entry->usr_msg_id;
+ tmp_msg.size = entry->vk2h_blks - 1;
+ if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) {
+ dev_err(dev, "Error return 1st block in -EMSGSIZE\n");
+ rc = -EFAULT;
+ }
+ }
+ return rc;
+}
+
+ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ ssize_t rc = -EPERM;
+ struct bcm_vk_ctx *ctx = p_file->private_data;
+ struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
+ miscdev);
+ struct bcm_vk_msgq *msgq;
+ struct device *dev = &vk->pdev->dev;
+ struct bcm_vk_wkent *entry;
+ uint32_t sgl_extra_blks;
+
+ if (!bcm_vk_drv_access_ok(vk))
+ return -EPERM;
+
+ dev_dbg(dev, "Msg count %ld\n", count);
+
+ /* first, do sanity check where count should be multiple of basic blk */
+ if (count & (VK_MSGQ_BLK_SIZE - 1)) {
+ dev_err(dev, "Failure with size %ld not multiple of %ld\n",
+ count, VK_MSGQ_BLK_SIZE);
+ rc = -EBADR;
+ goto bcm_vk_write_err;
+ }
+
+ /* allocate the work entry + buffer for size count and inband sgl */
+ entry = kzalloc(sizeof(struct bcm_vk_wkent) + count + vk->ib_sgl_size,
+ GFP_KERNEL);
+ if (!entry) {
+ rc = -ENOMEM;
+ goto bcm_vk_write_err;
+ }
+
+ /* now copy msg from user space, and then formulate the wk ent */
+ if (copy_from_user(&entry->h2vk_msg[0], buf, count))
+ goto bcm_vk_write_free_ent;
+
+ entry->h2vk_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
+ entry->ctx = ctx;
+
+ /* do a check on the blk size which could not exceed queue space */
+ msgq = vk->h2vk_msg_chan.msgq[entry->h2vk_msg[0].queue_id];
+ if (entry->h2vk_blks + (vk->ib_sgl_size >> VK_MSGQ_BLK_SZ_SHIFT)
+ > (msgq->size - 1)) {
+ dev_err(dev, "Blk size %d exceed max queue size allowed %d\n",
+ entry->h2vk_blks, msgq->size - 1);
+ rc = -EOVERFLOW;
+ goto bcm_vk_write_free_ent;
+ }
+
+ /* Use internal message id */
+ entry->usr_msg_id = entry->h2vk_msg[0].msg_id;
+ rc = bcm_vk_get_msg_id(vk);
+ if (rc == VK_MSG_ID_OVERFLOW) {
+ dev_err(dev, "msg_id overflow\n");
+ rc = -EOVERFLOW;
+ goto bcm_vk_write_free_ent;
+ }
+ entry->h2vk_msg[0].msg_id = rc;
+
+ dev_dbg(dev,
+ "Message ctx id %d, usr_msg_id 0x%x sent msg_id 0x%x\n",
+ ctx->idx, entry->usr_msg_id,
+ entry->h2vk_msg[0].msg_id);
+
+ /* Convert any pointers to sg list */
+ if (entry->h2vk_msg[0].function_id == VK_FID_TRANS_BUF) {
+ unsigned int num_planes;
+ int dir;
+ struct _vk_data *data;
+
+ /*
+ * check if we are in reset, if so, no buffer transfer is
+ * allowed and return error.
+ */
+ if (vk->reset_ppid) {
+ dev_dbg(dev, "No Transfer allowed during reset, pid %d.\n",
+ task_pid_nr(ctx->ppid));
+ rc = -EACCES;
+ goto bcm_vk_write_free_msgid;
+ }
+
+ num_planes = entry->h2vk_msg[0].args[0] & VK_CMD_PLANES_MASK;
+ if ((entry->h2vk_msg[0].args[0] & VK_CMD_MASK)
+ == VK_CMD_DOWNLOAD) {
+ /* Memory transfer from vk device */
+ dir = DMA_FROM_DEVICE;
+ } else {
+ /* Memory transfer to vk device */
+ dir = DMA_TO_DEVICE;
+ }
+
+ /* Calculate vk_data location */
+ /* Go to end of the message */
+ data = (struct _vk_data *)
+ &(entry->h2vk_msg[entry->h2vk_msg[0].size + 1]);
+ /* Now back up to the start of the pointers */
+ data -= num_planes;
+
+ /* Convert user addresses to DMA SG List */
+ rc = bcm_vk_sg_alloc(dev, entry->dma, dir, data, num_planes);
+ if (rc)
+ goto bcm_vk_write_free_msgid;
+
+ /* try to embed inband sgl */
+ sgl_extra_blks = bcm_vk_append_ib_sgl(vk, entry, data,
+ num_planes);
+ entry->h2vk_blks += sgl_extra_blks;
+ entry->h2vk_msg[0].size += sgl_extra_blks;
+ }
+
+ /*
+ * store wk ent to pending queue until a response is got. This needs to
+ * be done before enqueuing the message
+ */
+ bcm_vk_append_pendq(&vk->h2vk_msg_chan, entry->h2vk_msg[0].queue_id,
+ entry);
+
+ rc = bcm_h2vk_msg_enqueue(vk, entry);
+ if (rc) {
+ dev_err(dev, "Fail to enqueue msg to h2vk queue\n");
+
+ /* remove message from pending list */
+ entry = bcm_vk_find_pending(vk,
+ &vk->h2vk_msg_chan,
+ entry->h2vk_msg[0].queue_id,
+ entry->h2vk_msg[0].msg_id);
+ goto bcm_vk_write_free_ent;
+ }
+
+ return count;
+
+bcm_vk_write_free_msgid:
+ bcm_vk_msgid_bitmap_clear(vk, entry->h2vk_msg[0].msg_id, 1);
+bcm_vk_write_free_ent:
+ kfree(entry);
+bcm_vk_write_err:
+ return rc;
+}
+
+int bcm_vk_release(struct inode *inode, struct file *p_file)
+{
+ int ret;
+ struct bcm_vk_ctx *ctx = p_file->private_data;
+ struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
+ struct device *dev = &vk->pdev->dev;
+ struct task_struct *ppid = ctx->ppid;
+ pid_t pid = task_pid_nr(ppid);
+
+ dev_dbg(dev, "Draining with context idx %d pid %d\n",
+ ctx->idx, pid);
+
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, ctx);
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, ctx);
+
+ ret = bcm_vk_free_ctx(vk, ctx);
+ if (ret == 0)
+ ret = bcm_vk_handle_last_sess(vk, ppid);
+
+ /* free memory if it is the last reference */
+ kref_put(&vk->kref, bcm_vk_release_data);
+
+ return ret;
+}
+
+int bcm_vk_msg_init(struct bcm_vk *vk)
+{
+ struct device *dev = &vk->pdev->dev;
+ int err = 0;
+
+ if (bcm_vk_data_init(vk)) {
+ dev_err(dev, "Error initializing internal data structures\n");
+ err = -EINVAL;
+ goto err_out;
+ }
+
+ if (bcm_vk_msg_chan_init(&vk->h2vk_msg_chan) ||
+ bcm_vk_msg_chan_init(&vk->vk2h_msg_chan)) {
+ dev_err(dev, "Error initializing communication channel\n");
+ err = -EIO;
+ goto err_out;
+ }
+
+ /* create dedicated workqueue */
+ vk->wq_thread = create_singlethread_workqueue(vk->miscdev.name);
+ if (!vk->wq_thread) {
+ dev_err(dev, "Fail to create workqueue thread\n");
+ err = -ENOMEM;
+ goto err_out;
+ }
+
+ /* read msgq info */
+ if (bcm_vk_sync_msgq(vk, false)) {
+ dev_err(dev, "Error reading comm msg Q info\n");
+ err = -EIO;
+ goto err_out;
+ }
+
+err_out:
+ return err;
+}
+
+void bcm_vk_msg_remove(struct bcm_vk *vk)
+{
+ bcm_vk_blk_drv_access(vk);
+
+ /* drain all pending items */
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
+}
+
+void bcm_vk_trigger_reset(struct bcm_vk *vk)
+{
+ uint32_t i;
+ u32 value;
+
+ /* clean up before pressing the door bell */
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
+ bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
+ vkwrite32(vk, 0, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
+ /* make tag '\0' terminated */
+ vkwrite32(vk, 0, BAR_1, VK_BAR1_BOOT1_VER_TAG);
+
+ for (i = 0; i < VK_BAR1_DAUTH_MAX; i++) {
+ vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_STORE_ADDR(i));
+ vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_VALID_ADDR(i));
+ }
+ for (i = 0; i < VK_BAR1_SOTP_REVID_MAX; i++)
+ vkwrite32(vk, 0, BAR_1, VK_BAR1_SOTP_REVID_ADDR(i));
+
+ memset(&vk->card_info, 0, sizeof(vk->card_info));
+ memset(&vk->alert_cnts, 0, sizeof(vk->alert_cnts));
+
+ /*
+ * When boot request fails, the CODE_PUSH_OFFSET stays persistent.
+ * Allowing us to debug the failure. When we call reset,
+ * we should clear CODE_PUSH_OFFSET so ROM does not execute
+ * boot again (and fails again) and instead waits for a new
+ * codepush.
+ */
+ value = vkread32(vk, BAR_0, BAR_CODEPUSH_SBL);
+ value &= ~CODEPUSH_MASK;
+ vkwrite32(vk, value, BAR_0, BAR_CODEPUSH_SBL);
+
+ /* reset fw_status with proper reason, and press db */
+ vkwrite32(vk, VK_FWSTS_RESET_MBOX_DB, BAR_0, VK_BAR_FWSTS);
+ bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_SOFT);
+
+ /* clear the uptime register after reset pressed and alert record */
+ vkwrite32(vk, 0, BAR_0, BAR_OS_UPTIME);
+ memset(&vk->host_alert, 0, sizeof(vk->host_alert));
+ memset(&vk->peer_alert, 0, sizeof(vk->peer_alert));
+#if defined(CONFIG_BCM_VK_QSTATS)
+ /* clear qstats */
+ for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
+ memset(&vk->h2vk_msg_chan.qstats[i].qcnts, 0,
+ sizeof(vk->h2vk_msg_chan.qstats[i].qcnts));
+ memset(&vk->vk2h_msg_chan.qstats[i].qcnts, 0,
+ sizeof(vk->vk2h_msg_chan.qstats[i].qcnts));
+ }
+#endif
+ /* clear 4096 bits of bitmap */
+ bitmap_clear(vk->bmap, 0, VK_MSG_ID_BITMAP_SIZE);
+}
diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.h b/drivers/misc/bcm-vk/bcm_vk_msg.h
new file mode 100644
index 000000000000..076307b2f1c4
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_msg.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#ifndef BCM_VK_MSG_H
+#define BCM_VK_MSG_H
+
+#include <uapi/linux/misc/bcm_vk.h>
+#include "bcm_vk_sg.h"
+
+/* Single message queue control structure */
+struct bcm_vk_msgq {
+ uint16_t type; /* queue type */
+ uint16_t num; /* queue number */
+ uint32_t start; /* offset in BAR1 where the queue memory starts */
+ volatile uint32_t rd_idx; /* read idx */
+ volatile uint32_t wr_idx; /* write idx */
+ uint32_t size; /*
+ * size, which is in number of 16byte blocks,
+ * to align with the message data structure.
+ */
+ uint32_t nxt; /*
+ * nxt offset to the next msg queue struct.
+ * This is to provide flexibity for alignment purposes.
+ */
+};
+
+/*
+ * Structure to record static info from the msgq sync. We keep local copy
+ * for some of these variables for both performance + checking purpose.
+ */
+struct bcm_vk_sync_qinfo {
+ void __iomem *q_start;
+ uint32_t q_size;
+ uint32_t q_mask;
+ uint32_t q_low;
+};
+
+#define VK_MSGQ_MAX_NR 4 /* Maximum number of message queues */
+
+/*
+ * some useful message queue macros
+ */
+
+/* vk_msg_blk is 16 bytes fixed */
+#define VK_MSGQ_BLK_SIZE (sizeof(struct vk_msg_blk))
+/* shift for fast division of basic msg blk size */
+#define VK_MSGQ_BLK_SZ_SHIFT 4
+
+#define VK_MSGQ_EMPTY(_msgq) ((_msgq)->rd_idx == (_msgq)->wr_idx)
+
+#define VK_MSGQ_SIZE_MASK(_qinfo) ((_qinfo)->q_mask)
+
+#define VK_MSGQ_INC(_qinfo, _idx, _inc) \
+ (((_idx) + (_inc)) & VK_MSGQ_SIZE_MASK(_qinfo))
+
+#define VK_MSGQ_BLK_ADDR(_qinfo, _idx) \
+ (volatile struct vk_msg_blk *)((_qinfo)->q_start + \
+ (VK_MSGQ_BLK_SIZE * (_idx)))
+
+#define VK_MSGQ_OCCUPIED(_msgq, _qinfo) \
+ (((_msgq)->wr_idx - (_msgq)->rd_idx) & VK_MSGQ_SIZE_MASK(_qinfo))
+
+#define VK_MSGQ_AVAIL_SPACE(_msgq, _qinfo) \
+ ((_qinfo)->q_size - VK_MSGQ_OCCUPIED(_msgq, _qinfo) - 1)
+
+/* context per session opening of sysfs */
+struct bcm_vk_ctx {
+ struct list_head node; /* use for linkage in Hash Table */
+ uint idx;
+ bool in_use;
+ struct task_struct *ppid;
+ uint32_t hash_idx;
+ struct miscdevice *miscdev;
+ int pend_cnt; /* number of items pending to be read from host */
+};
+
+/* pid hash table entry */
+struct bcm_vk_ht_entry {
+ struct list_head head;
+};
+
+#define VK_DMA_MAX_ADDRS 4 /* Max 4 DMA Addresses */
+/* structure for house keeping a single work entry */
+struct bcm_vk_wkent {
+
+ struct list_head node; /* for linking purpose */
+ struct bcm_vk_ctx *ctx;
+
+ /* Store up to 4 dma pointers */
+ struct bcm_vk_dma dma[VK_DMA_MAX_ADDRS];
+
+ uint32_t vk2h_blks; /* response */
+ struct vk_msg_blk *vk2h_msg;
+
+ /*
+ * put the h2vk_msg at the end so that we could simply append h2vk msg
+ * to the end of the allocated block
+ */
+ uint32_t usr_msg_id;
+ uint32_t h2vk_blks;
+ uint32_t seq_num;
+ struct vk_msg_blk h2vk_msg[0];
+};
+
+/* queue stats counters */
+struct bcm_vk_qs_cnts {
+ uint32_t cnt; /* general counter, used to limit output */
+ uint32_t acc_sum;
+ uint32_t max_occ; /* max during a sampling period */
+ uint32_t max_abs; /* the abs max since reset */
+};
+
+/* stats structure */
+struct bcm_vk_qstats {
+ uint32_t q_num;
+ struct bcm_vk_qs_cnts qcnts;
+};
+
+/* control channel structure for either h2vk or vk2h communication */
+struct bcm_vk_msg_chan {
+ uint32_t q_nr;
+ struct mutex msgq_mutex;
+ /* pointing to BAR locations */
+ struct bcm_vk_msgq *msgq[VK_MSGQ_MAX_NR];
+
+ spinlock_t pendq_lock;
+ /* for temporary storing pending items, one for each queue */
+ struct list_head pendq[VK_MSGQ_MAX_NR];
+ /* static queue info from the sync */
+ struct bcm_vk_sync_qinfo sync_qinfo[VK_MSGQ_MAX_NR];
+#if defined(CONFIG_BCM_VK_QSTATS)
+ /* qstats */
+ struct bcm_vk_qstats qstats[VK_MSGQ_MAX_NR];
+#endif
+};
+
+/* total number of supported ctx, 32 ctx each for 5 components */
+#define VK_CMPT_CTX_MAX (32 * 5)
+
+/* hash table defines to store the opened FDs */
+#define VK_PID_HT_SHIFT_BIT 7 /* 128 */
+#define VK_PID_HT_SZ (1 << VK_PID_HT_SHIFT_BIT)
+
+/* The following are offsets of DDR info provided by the vk card */
+#define VK_BAR0_SEG_SIZE (4 * SZ_1K) /* segment size for BAR0 */
+
+/* shutdown types supported */
+#define VK_SHUTDOWN_PID 1
+#define VK_SHUTDOWN_GRACEFUL 2
+
+/*
+ * first door bell reg, ie for queue = 0. Only need the first one, as
+ * we will use the queue number to derive the others
+ */
+#define VK_BAR0_REGSEG_DB_BASE 0x484
+#define VK_BAR0_REGSEG_DB_REG_GAP 8 /*
+ * DB register gap,
+ * DB1 at 0x48c and DB2 at 0x494
+ */
+
+/* reset register and specific values */
+#define VK_BAR0_RESET_DB_NUM 3
+#define VK_BAR0_RESET_DB_SOFT 0xFFFFFFFF
+#define VK_BAR0_RESET_DB_HARD 0xFFFFFFFD
+
+/* BAR1 message q definition */
+
+/* indicate if msgq ctrl in BAR1 is populated */
+#define VK_BAR1_MSGQ_DEF_RDY 0x60c0
+/* ready marker value for the above location, normal boot2 */
+#define VK_BAR1_MSGQ_RDY_MARKER 0xbeefcafe
+/* ready marker value for the above location, normal boot2 */
+#define VK_BAR1_DIAG_RDY_MARKER 0xdeadcafe
+/* number of msgqs in BAR1 */
+#define VK_BAR1_MSGQ_NR 0x60c4
+/* BAR1 queue control structure offset */
+#define VK_BAR1_MSGQ_CTRL_OFF 0x60c8
+/* BAR1 ucode and boot1 version tag */
+#define VK_BAR1_UCODE_VER_TAG 0x6170
+#define VK_BAR1_BOOT1_VER_TAG 0x61b0
+#define VK_BAR1_VER_TAG_SIZE 64
+/* Memory to hold the DMA buffer memory address allocated for boot2 download */
+#define VK_BAR1_DMA_BUF_OFF_HI 0x61e0
+#define VK_BAR1_DMA_BUF_OFF_LO (VK_BAR1_DMA_BUF_OFF_HI + 4)
+#define VK_BAR1_DMA_BUF_SZ (VK_BAR1_DMA_BUF_OFF_HI + 8)
+/* Scratch memory allocated on host for VK */
+#define VK_BAR1_SCRATCH_OFF_LO 0x61f0
+#define VK_BAR1_SCRATCH_OFF_HI (VK_BAR1_SCRATCH_OFF_LO + 4)
+#define VK_BAR1_SCRATCH_SZ_ADDR (VK_BAR1_SCRATCH_OFF_LO + 8)
+#define VK_BAR1_SCRATCH_DEF_NR_PAGES 32
+ /* BAR1 SOTP AUTH and REVID info */
+#define VK_BAR1_DAUTH_BASE_ADDR 0x6200
+#define VK_BAR1_DAUTH_STORE_SIZE 0x48
+#define VK_BAR1_DAUTH_VALID_SIZE 0x8
+#define VK_BAR1_DAUTH_MAX 4
+#define VK_BAR1_DAUTH_STORE_ADDR(x) \
+ (VK_BAR1_DAUTH_BASE_ADDR + \
+ (x) * (VK_BAR1_DAUTH_STORE_SIZE + VK_BAR1_DAUTH_VALID_SIZE))
+#define VK_BAR1_DAUTH_VALID_ADDR(x) \
+ (VK_BAR1_DAUTH_STORE_ADDR(x) + VK_BAR1_DAUTH_STORE_SIZE)
+
+#define VK_BAR1_SOTP_REVID_BASE_ADDR 0x6340
+#define VK_BAR1_SOTP_REVID_SIZE 0x10
+#define VK_BAR1_SOTP_REVID_MAX 2
+#define VK_BAR1_SOTP_REVID_ADDR(x) \
+ (VK_BAR1_SOTP_REVID_BASE_ADDR + (x) * VK_BAR1_SOTP_REVID_SIZE)
+
+#endif
diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.c b/drivers/misc/bcm-vk/bcm_vk_sg.c
new file mode 100644
index 000000000000..e5de07958f0b
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_sg.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/unaligned.h>
+
+#include <uapi/linux/misc/bcm_vk.h>
+
+#include "bcm_vk.h"
+#include "bcm_vk_msg.h"
+#include "bcm_vk_sg.h"
+
+/*
+ * Valkyrie has a hardware limitation of 16M transfer size.
+ * So limit the SGL chunks to 16M.
+ */
+#define BCM_VK_MAX_SGL_CHUNK SZ_16M
+
+static int bcm_vk_dma_alloc(struct device *dev,
+ struct bcm_vk_dma *dma,
+ int dir,
+ struct _vk_data *vkdata);
+static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
+
+/* Uncomment to dump SGLIST */
+//#define BCM_VK_DUMP_SGLIST
+
+static int bcm_vk_dma_alloc(struct device *dev,
+ struct bcm_vk_dma *dma,
+ int direction,
+ struct _vk_data *vkdata)
+{
+ dma_addr_t addr, sg_addr;
+ int err;
+ int i;
+ int offset;
+ uint32_t size;
+ uint32_t remaining_size;
+ uint32_t transfer_size;
+ uint64_t data;
+ unsigned long first, last;
+ struct _vk_data *sgdata;
+
+ /* Get 64-bit user address */
+ data = get_unaligned(&(vkdata->address));
+
+ /* offset into first page */
+ offset = offset_in_page(data);
+
+ /* Calculate number of pages */
+ first = (data & PAGE_MASK) >> PAGE_SHIFT;
+ last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
+ dma->nr_pages = last - first + 1;
+
+ /* Allocate DMA pages */
+ dma->pages = kmalloc_array(dma->nr_pages,
+ sizeof(struct page *),
+ GFP_KERNEL);
+ if (dma->pages == NULL)
+ return -ENOMEM;
+
+ dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
+ data, vkdata->size, dma->nr_pages);
+
+ dma->direction = direction;
+
+ /* Get user pages into memory */
+ err = get_user_pages_fast(data & PAGE_MASK,
+ dma->nr_pages,
+ direction == DMA_FROM_DEVICE,
+ dma->pages);
+ if (err != dma->nr_pages) {
+ dma->nr_pages = (err >= 0) ? err : 0;
+ dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
+ err, dma->nr_pages);
+ return err < 0 ? err : -EINVAL;
+ }
+
+ /* Max size of sg list is 1 per mapped page + fields at start */
+ dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
+ (sizeof(uint32_t) * SGLIST_VKDATA_START);
+
+ /* Allocate sglist */
+ dma->sglist = dma_alloc_coherent(dev,
+ dma->sglen,
+ &dma->handle,
+ GFP_KERNEL);
+ if (!dma->sglist)
+ return -ENOMEM;
+
+ dma->sglist[SGLIST_NUM_SG] = 0;
+ dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
+ remaining_size = vkdata->size;
+ sgdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
+
+ /* Map all pages into DMA */
+ i = 0;
+ size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
+ remaining_size -= size;
+ sg_addr = dma_map_page(dev,
+ dma->pages[0],
+ offset,
+ size,
+ dma->direction);
+ transfer_size = size;
+ if (unlikely(dma_mapping_error(dev, sg_addr))) {
+ __free_page(dma->pages[0]);
+ return -EIO;
+ }
+
+ for (i = 1; i < dma->nr_pages; i++) {
+ size = min_t(size_t, PAGE_SIZE, remaining_size);
+ remaining_size -= size;
+ addr = dma_map_page(dev,
+ dma->pages[i],
+ 0,
+ size,
+ dma->direction);
+ if (unlikely(dma_mapping_error(dev, addr))) {
+ __free_page(dma->pages[i]);
+ return -EIO;
+ }
+
+ /*
+ * Compress SG list entry when pages are contiguous
+ * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
+ */
+ if ((addr == (sg_addr + transfer_size)) &&
+ ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
+ /* pages are contiguous, add to same sg entry */
+ transfer_size += size;
+ } else {
+ /* pages are not contiguous, write sg entry */
+ sgdata->size = transfer_size;
+ put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
+ dma->sglist[SGLIST_NUM_SG]++;
+
+ /* start new sg entry */
+ sgdata++;
+ sg_addr = addr;
+ transfer_size = size;
+ }
+ }
+ /* Write last sg list entry */
+ sgdata->size = transfer_size;
+ put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
+ dma->sglist[SGLIST_NUM_SG]++;
+
+ /* Update pointers and size field to point to sglist */
+ put_unaligned((uint64_t)dma->handle, &(vkdata->address));
+ vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
+ (sizeof(uint32_t) * SGLIST_VKDATA_START);
+
+#ifdef BCM_VK_DUMP_SGLIST
+ dev_dbg(dev,
+ "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
+ (uint64_t)dma->sglist,
+ dma->handle,
+ dma->sglen,
+ vkdata->size);
+ for (i = 0; i < vkdata->size / sizeof(uint32_t); i++)
+ dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
+#endif
+
+ return 0;
+}
+
+int bcm_vk_sg_alloc(struct device *dev,
+ struct bcm_vk_dma *dma,
+ int dir,
+ struct _vk_data *vkdata,
+ int num)
+{
+ int i;
+ int rc = -EINVAL;
+
+ /* Convert user addresses to DMA SG List */
+ for (i = 0; i < num; i++) {
+ if (vkdata[i].size && vkdata[i].address) {
+ /*
+ * If both size and address are non-zero
+ * then DMA alloc.
+ */
+ rc = bcm_vk_dma_alloc(dev,
+ &dma[i],
+ dir,
+ &vkdata[i]);
+ } else if (vkdata[i].size ||
+ vkdata[i].address) {
+ /*
+ * If one of size and address are zero
+ * there is a problem.
+ */
+ dev_err(dev,
+ "Invalid vkdata %x 0x%x 0x%llx\n",
+ i, vkdata[i].size, vkdata[i].address);
+ rc = -EINVAL;
+ } else {
+ /*
+ * If size and address are both zero
+ * don't convert, but return success.
+ */
+ rc = 0;
+ }
+
+ if (rc)
+ goto fail_alloc;
+ }
+ return rc;
+
+fail_alloc:
+ while (i > 0) {
+ i--;
+ if (dma[i].sglist)
+ bcm_vk_dma_free(dev, &dma[i]);
+ }
+ return rc;
+}
+
+static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
+{
+ dma_addr_t addr;
+ int i;
+ int num_sg;
+ uint32_t size;
+ struct _vk_data *vkdata;
+
+ dev_dbg(dev, "free sglist=%p sglen=0x%x\n",
+ dma->sglist, dma->sglen);
+
+ /* Unmap all pages in the sglist */
+ num_sg = dma->sglist[SGLIST_NUM_SG];
+ vkdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
+ for (i = 0; i < num_sg; i++) {
+ size = vkdata[i].size;
+ addr = get_unaligned(&(vkdata[i].address));
+
+ dma_unmap_page(dev, addr, size, dma->direction);
+ }
+
+ /* Free allocated sglist */
+ dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
+
+ /* Release lock on all pages */
+ for (i = 0; i < dma->nr_pages; i++)
+ put_page(dma->pages[i]);
+
+ /* Free allocated dma pages */
+ kfree(dma->pages);
+ dma->sglist = NULL;
+
+ return 0;
+}
+
+int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num)
+{
+ int i;
+
+ /* Unmap and free all pages and sglists */
+ for (i = 0; i < num; i++) {
+ if (dma[i].sglist)
+ bcm_vk_dma_free(dev, &dma[i]);
+ }
+
+ return 0;
+}
diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.h b/drivers/misc/bcm-vk/bcm_vk_sg.h
new file mode 100644
index 000000000000..df88154e80b7
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_sg.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#ifndef BCM_VK_SG_H
+#define BCM_VK_SG_H
+
+#include <linux/dma-mapping.h>
+
+struct bcm_vk_dma {
+ /* for userland buffer */
+ struct page **pages;
+ int nr_pages;
+
+ /* common */
+ dma_addr_t handle;
+ /*
+ * sglist is of the following LE format
+ * [U32] num_sg = number of sg addresses (N)
+ * [U32] totalsize = totalsize of data being transferred in sglist
+ * [U32] size[0] = size of data in address0
+ * [U32] addr_l[0] = lower 32-bits of address0
+ * [U32] addr_h[0] = higher 32-bits of address0
+ * ..
+ * [U32] size[N-1] = size of data in addressN-1
+ * [U32] addr_l[N-1] = lower 32-bits of addressN-1
+ * [U32] addr_h[N-1] = higher 32-bits of addressN-1
+ */
+ uint32_t *sglist;
+#define SGLIST_NUM_SG 0
+#define SGLIST_TOTALSIZE 1
+#define SGLIST_VKDATA_START 2
+
+ int sglen; /* Length (bytes) of sglist */
+ int direction;
+};
+
+struct _vk_data {
+ uint32_t size; /* data size in bytes */
+ uint64_t address; /* Pointer to data */
+} __packed;
+
+/*
+ * Scatter-gather DMA buffer API.
+ *
+ * These functions provide a simple way to create a page list and a
+ * scatter-gather list from userspace address and map the memory
+ * for DMA operation.
+ */
+int bcm_vk_sg_alloc(struct device *dev,
+ struct bcm_vk_dma *dma,
+ int dir,
+ struct _vk_data *vkdata,
+ int num);
+
+int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num);
+
+#endif
+
diff --git a/drivers/misc/bcm-vk/bcm_vk_tty.c b/drivers/misc/bcm-vk/bcm_vk_tty.c
new file mode 100644
index 000000000000..3835218aaec6
--- /dev/null
+++ b/drivers/misc/bcm-vk/bcm_vk_tty.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#include "bcm_vk.h"
+
+/* TTYVK base offset is 0x30000 into BAR1 */
+#define BAR1_TTYVK_BASE_OFFSET 0x300000
+/* Each TTYVK channel (TO or FROM) is 0x10000 */
+#define BAR1_TTYVK_CHAN_OFFSET 0x100000
+/* Each TTYVK channel has TO and FROM, hence the * 2 */
+#define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
+ ((index) * BAR1_TTYVK_CHAN_OFFSET * 2))
+/* TO TTYVK channel base comes before FROM for each index */
+#define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
+#define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
+ BAR1_TTYVK_CHAN_OFFSET)
+
+struct bcm_vk_tty_chan {
+ uint32_t reserved;
+ uint32_t size;
+ uint32_t wr;
+ uint32_t rd;
+ uint32_t *data;
+};
+
+#define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
+ + offsetof(struct bcm_vk_tty_chan, e))
+#define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
+#define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
+#define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
+#define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + off)
+
+/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
+#define SERIAL_TIMER_VALUE (HZ / 10)
+
+#if defined(BCM_VK_LEGACY_API)
+
+/* No support in legacy case, and do a dummy init and exit */
+int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
+{
+ struct device *dev = &vk->pdev->dev;
+
+ dev_dbg(dev, "init %s\n", name);
+ return 0;
+}
+
+void bcm_vk_tty_exit(struct bcm_vk *vk)
+{
+ struct device *dev = &vk->pdev->dev;
+
+ dev_dbg(dev, "exit\n");
+}
+
+#else
+
+static void bcm_vk_tty_poll(struct timer_list *t)
+{
+ struct bcm_vk *vk = from_timer(vk, t, serial_timer);
+ struct bcm_vk_tty *vktty;
+ int card_status;
+ int ready_mask;
+ int count = 0;
+ unsigned char c;
+ int i;
+ int wr;
+
+ card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
+
+ for (i = 0; i < BCM_VK_NUM_TTY; i++) {
+ /* Check the card status that the tty channel is ready */
+ ready_mask = BIT(i);
+ if ((card_status & ready_mask) == 0)
+ continue;
+
+ vktty = &vk->tty[i];
+
+ /* Fetch the wr offset in buffer from VK */
+ wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from));
+ if (wr >= vktty->from_size) {
+ dev_err(&vk->pdev->dev,
+ "ERROR: poll ttyVK%d wr:0x%x > 0x%x\n",
+ i, wr, vktty->from_size);
+ /* Need to signal and close device in this case */
+ return;
+ }
+
+ /*
+ * Simple read of circular buffer and
+ * insert into tty flip buffer
+ */
+ while (vk->tty[i].rd != wr) {
+ c = vkread8(vk, BAR_1,
+ VK_BAR_CHAN_DATA(vktty, from, vktty->rd));
+ vktty->rd++;
+ if (vktty->rd >= vktty->from_size)
+ vktty->rd = 0;
+ tty_insert_flip_char(&vktty->port, c, TTY_NORMAL);
+ count++;
+ }
+ }
+
+ if (count) {
+ tty_flip_buffer_push(&vktty->port);
+
+ /* Update read offset from shadow register to card */
+ vkwrite32(vk, vktty->rd, BAR_1, VK_BAR_CHAN_RD(vktty, from));
+ }
+
+ mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
+}
+
+static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file)
+{
+ int card_status;
+ int ready_mask;
+ struct bcm_vk *vk;
+ struct bcm_vk_tty *vktty;
+ int index;
+
+ /* initialize the pointer in case something fails */
+ tty->driver_data = NULL;
+
+ vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
+ index = tty->index;
+
+ if (index >= BCM_VK_NUM_TTY)
+ return -EINVAL;
+
+ vktty = &vk->tty[index];
+
+ vktty->to_offset = TO_TTYK_BASE(index);
+ vktty->from_offset = FROM_TTYK_BASE(index);
+
+ /* Do not allow tty device to be opened if tty on card not ready */
+ card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
+ if (card_status == -1)
+ return -1;
+
+ ready_mask = BIT(index);
+ if ((card_status & ready_mask) == 0)
+ return -1;
+
+ /*
+ * Get shadow registers of the buffer sizes and the "to" write offset
+ * and "from" read offset
+ */
+ vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to));
+ vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to));
+ vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from));
+ vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from));
+
+ if (tty->count == 1) {
+ timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0);
+ mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
+ }
+ return 0;
+}
+
+static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file)
+{
+ struct bcm_vk *vk;
+
+ vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
+
+ if (tty->count == 1)
+ del_timer_sync(&vk->serial_timer);
+}
+
+static int bcm_vk_tty_write(struct tty_struct *tty,
+ const unsigned char *buffer,
+ int count)
+{
+ int index;
+ struct bcm_vk *vk;
+ struct bcm_vk_tty *vktty;
+ int i;
+ int retval;
+
+ index = tty->index;
+ vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
+ vktty = &vk->tty[index];
+
+ /* Simple write each byte to circular buffer */
+ for (i = 0; i < count; i++) {
+ vkwrite8(vk,
+ buffer[i],
+ BAR_1,
+ VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
+ vktty->wr++;
+ if (vktty->wr >= vktty->to_size)
+ vktty->wr = 0;
+ }
+ /* Update write offset from shadow register to card */
+ /* TODO: Need to add write to doorbell here */
+ vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to));
+
+ retval = count;
+
+ return retval;
+}
+
+static int bcm_vk_tty_write_room(struct tty_struct *tty)
+{
+ int room;
+ struct bcm_vk *vk;
+ struct bcm_vk_tty *vktty;
+
+ vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
+ vktty = &vk->tty[tty->index];
+
+ /*
+ * Calculate how much room is left in the device
+ * Just return the size -1 of buffer. We could care about
+ * overflow but don't at this point.
+ */
+ room = vktty->to_size - 1;
+
+ return room;
+}
+
+static const struct tty_operations serial_ops = {
+ .open = bcm_vk_tty_open,
+ .close = bcm_vk_tty_close,
+ .write = bcm_vk_tty_write,
+ .write_room = bcm_vk_tty_write_room,
+};
+
+int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
+{
+ int i;
+ int err;
+ unsigned long flags;
+ struct tty_driver *tty_drv;
+ struct device *dev = &vk->pdev->dev;
+
+ /* allocate the tty driver */
+ flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ tty_drv = tty_alloc_driver(BCM_VK_NUM_TTY, flags);
+
+ if (IS_ERR(tty_drv)) {
+ err = PTR_ERR(tty_drv);
+ goto err_exit;
+ }
+ /* Save struct tty_driver for uninstalling the device */
+ vk->tty_drv = tty_drv;
+
+ /* initialize the tty driver */
+ tty_drv->driver_name = KBUILD_MODNAME;
+ tty_drv->name = kstrdup(name, GFP_KERNEL);
+ if (!tty_drv->name) {
+ err = -ENOMEM;
+ goto err_put_tty_driver;
+ }
+ tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+ tty_drv->subtype = SERIAL_TYPE_NORMAL;
+ tty_drv->init_termios = tty_std_termios;
+ tty_set_operations(tty_drv, &serial_ops);
+
+ /* register the tty driver */
+ err = tty_register_driver(tty_drv);
+ if (err) {
+ dev_err(dev, "tty_register_driver failed\n");
+ goto err_kfree_tty_name;
+ }
+
+ for (i = 0; i < BCM_VK_NUM_TTY; i++) {
+ struct device *tty_dev;
+
+ tty_port_init(&vk->tty[i].port);
+ tty_dev = tty_port_register_device(&vk->tty[i].port,
+ tty_drv,
+ i,
+ dev);
+ dev_set_drvdata(tty_dev, vk);
+
+ if (IS_ERR(tty_dev)) {
+ int j;
+
+ for (j = 0; j < i; j++)
+ tty_port_unregister_device(&vk->tty[j].port,
+ tty_drv,
+ j);
+ goto err_tty_unregister_driver;
+ }
+ }
+
+ return 0;
+
+err_tty_unregister_driver:
+ tty_unregister_driver(tty_drv);
+
+err_kfree_tty_name:
+ kfree(tty_drv->name);
+ tty_drv->name = NULL;
+
+err_put_tty_driver:
+ put_tty_driver(tty_drv);
+
+err_exit:
+ return err;
+}
+
+void bcm_vk_tty_exit(struct bcm_vk *vk)
+{
+ int i;
+
+ del_timer_sync(&vk->serial_timer);
+ for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
+ tty_port_unregister_device(&vk->tty[i].port,
+ vk->tty_drv,
+ i);
+ tty_port_destroy(&vk->tty[i].port);
+ }
+ tty_unregister_driver(vk->tty_drv);
+ put_tty_driver(vk->tty_drv);
+
+ kfree(vk->tty_drv->name);
+ vk->tty_drv->name = NULL;
+}
+
+#endif
--
2.17.1

2020-02-20 00:49:51

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 7/7] MAINTAINERS: bcm-vk: add maintainer for Broadcom VK Driver

Add maintainer entry for new Broadcom VK Driver

Signed-off-by: Scott Branden <[email protected]>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 4beb8dc4c7eb..c55f34f00b85 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3564,6 +3564,13 @@ L: [email protected]
S: Supported
F: drivers/net/ethernet/broadcom/tg3.*

+BROADCOM VK DRIVER
+M: Scott Branden <[email protected]>
+L: [email protected]
+S: Supported
+F: drivers/misc/bcm-vk/
+F: include/uapi/linux/misc/bcm_vk.h
+
BROCADE BFA FC SCSI DRIVER
M: Anil Gurumurthy <[email protected]>
M: Sudarsana Kalluru <[email protected]>
--
2.17.1

2020-02-20 00:50:03

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

Add additional hooks to test_firmware to pass in support
for partial file read using request_firmware_into_buf.
buf_size: size of buffer to request firmware into
partial: indicates that a partial file request is being made
file_offset: to indicate offset into file to request

Signed-off-by: Scott Branden <[email protected]>
---
lib/test_firmware.c | 141 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 138 insertions(+), 3 deletions(-)

diff --git a/lib/test_firmware.c b/lib/test_firmware.c
index 7d1d97fa9a23..6050d3113f92 100644
--- a/lib/test_firmware.c
+++ b/lib/test_firmware.c
@@ -49,6 +49,9 @@ struct test_batched_req {
* @name: the name of the firmware file to look for
* @into_buf: when the into_buf is used if this is true
* request_firmware_into_buf() will be used instead.
+ * @buf_size: size of buf to allocate when into_buf is true
+ * @file_offset: file offset to request when calling request_firmware_into_buf
+ * @partial: partial read flag value when calling request_firmware_into_buf
* @sync_direct: when the sync trigger is used if this is true
* request_firmware_direct() will be used instead.
* @send_uevent: whether or not to send a uevent for async requests
@@ -88,6 +91,9 @@ struct test_batched_req {
struct test_config {
char *name;
bool into_buf;
+ size_t buf_size;
+ size_t file_offset;
+ bool partial;
bool sync_direct;
bool send_uevent;
u8 num_requests;
@@ -182,6 +188,9 @@ static int __test_firmware_config_init(void)
test_fw_config->num_requests = TEST_FIRMWARE_NUM_REQS;
test_fw_config->send_uevent = true;
test_fw_config->into_buf = false;
+ test_fw_config->buf_size = TEST_FIRMWARE_BUF_SIZE;
+ test_fw_config->file_offset = 0;
+ test_fw_config->partial = false;
test_fw_config->sync_direct = false;
test_fw_config->req_firmware = request_firmware;
test_fw_config->test_result = 0;
@@ -253,6 +262,13 @@ static ssize_t config_show(struct device *dev,
len += scnprintf(buf+len, PAGE_SIZE - len,
"into_buf:\t\t%s\n",
test_fw_config->into_buf ? "true" : "false");
+ len += scnprintf(buf+len, PAGE_SIZE - len,
+ "buf_size:\t%zu\n", test_fw_config->buf_size);
+ len += scnprintf(buf+len, PAGE_SIZE - len,
+ "file_offset:\t%zu\n", test_fw_config->file_offset);
+ len += scnprintf(buf+len, PAGE_SIZE - len,
+ "partial:\t\t%s\n",
+ test_fw_config->partial ? "true" : "false");
len += scnprintf(buf+len, PAGE_SIZE - len,
"sync_direct:\t\t%s\n",
test_fw_config->sync_direct ? "true" : "false");
@@ -322,6 +338,39 @@ test_dev_config_show_bool(char *buf,
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}

+static int test_dev_config_update_size_t(const char *buf,
+ size_t size,
+ size_t *cfg)
+{
+ int ret;
+ long new;
+
+ ret = kstrtol(buf, 10, &new);
+ if (ret)
+ return ret;
+
+ if (new > SIZE_MAX)
+ return -EINVAL;
+
+ mutex_lock(&test_fw_mutex);
+ *(size_t *)cfg = new;
+ mutex_unlock(&test_fw_mutex);
+
+ /* Always return full write size even if we didn't consume all */
+ return size;
+}
+
+static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
+{
+ size_t val;
+
+ mutex_lock(&test_fw_mutex);
+ val = cfg;
+ mutex_unlock(&test_fw_mutex);
+
+ return snprintf(buf, PAGE_SIZE, "%zu\n", val);
+}
+
static ssize_t test_dev_config_show_int(char *buf, int cfg)
{
int val;
@@ -419,6 +468,83 @@ static ssize_t config_into_buf_show(struct device *dev,
}
static DEVICE_ATTR_RW(config_into_buf);

+static ssize_t config_buf_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+
+ mutex_lock(&test_fw_mutex);
+ if (test_fw_config->reqs) {
+ pr_err("Must call release_all_firmware prior to changing config\n");
+ rc = -EINVAL;
+ mutex_unlock(&test_fw_mutex);
+ goto out;
+ }
+ mutex_unlock(&test_fw_mutex);
+
+ rc = test_dev_config_update_size_t(buf, count,
+ &test_fw_config->buf_size);
+
+out:
+ return rc;
+}
+
+static ssize_t config_buf_size_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return test_dev_config_show_size_t(buf, test_fw_config->buf_size);
+}
+static DEVICE_ATTR_RW(config_buf_size);
+
+static ssize_t config_file_offset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int rc;
+
+ mutex_lock(&test_fw_mutex);
+ if (test_fw_config->reqs) {
+ pr_err("Must call release_all_firmware prior to changing config\n");
+ rc = -EINVAL;
+ mutex_unlock(&test_fw_mutex);
+ goto out;
+ }
+ mutex_unlock(&test_fw_mutex);
+
+ rc = test_dev_config_update_size_t(buf, count,
+ &test_fw_config->file_offset);
+
+out:
+ return rc;
+}
+
+static ssize_t config_file_offset_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return test_dev_config_show_size_t(buf, test_fw_config->file_offset);
+}
+static DEVICE_ATTR_RW(config_file_offset);
+
+static ssize_t config_partial_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return test_dev_config_update_bool(buf,
+ count,
+ &test_fw_config->partial);
+}
+
+static ssize_t config_partial_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return test_dev_config_show_bool(buf, test_fw_config->partial);
+}
+static DEVICE_ATTR_RW(config_partial);
+
static ssize_t config_sync_direct_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
@@ -613,18 +739,24 @@ static int test_fw_run_batch_request(void *data)

if (test_fw_config->into_buf) {
void *test_buf;
+ unsigned int pread_flags;

test_buf = kzalloc(TEST_FIRMWARE_BUF_SIZE, GFP_KERNEL);
if (!test_buf)
return -ENOSPC;

+ if (test_fw_config->partial)
+ pread_flags = KERNEL_PREAD_FLAG_PART;
+ else
+ pread_flags = KERNEL_PREAD_FLAG_WHOLE;
+
req->rc = request_firmware_into_buf(&req->fw,
req->name,
req->dev,
test_buf,
- TEST_FIRMWARE_BUF_SIZE,
- 0,
- KERNEL_PREAD_FLAG_WHOLE);
+ test_fw_config->buf_size,
+ test_fw_config->file_offset,
+ pread_flags);
if (!req->fw)
kfree(test_buf);
} else {
@@ -897,6 +1029,9 @@ static struct attribute *test_dev_attrs[] = {
TEST_FW_DEV_ATTR(config_name),
TEST_FW_DEV_ATTR(config_num_requests),
TEST_FW_DEV_ATTR(config_into_buf),
+ TEST_FW_DEV_ATTR(config_buf_size),
+ TEST_FW_DEV_ATTR(config_file_offset),
+ TEST_FW_DEV_ATTR(config_partial),
TEST_FW_DEV_ATTR(config_sync_direct),
TEST_FW_DEV_ATTR(config_send_uevent),
TEST_FW_DEV_ATTR(config_read_fw_idx),
--
2.17.1

2020-02-20 00:50:27

by Scott Branden

[permalink] [raw]
Subject: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI

Add user space api for bcm-vk driver.

Signed-off-by: Scott Branden <[email protected]>
---
include/uapi/linux/misc/bcm_vk.h | 117 +++++++++++++++++++++++++++++++
1 file changed, 117 insertions(+)
create mode 100644 include/uapi/linux/misc/bcm_vk.h

diff --git a/include/uapi/linux/misc/bcm_vk.h b/include/uapi/linux/misc/bcm_vk.h
new file mode 100644
index 000000000000..56a2178e06f5
--- /dev/null
+++ b/include/uapi/linux/misc/bcm_vk.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+/*
+ * Copyright 2018-2020 Broadcom.
+ */
+
+#ifndef __UAPI_LINUX_MISC_BCM_VK_H
+#define __UAPI_LINUX_MISC_BCM_VK_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+struct vk_image {
+ __u32 type; /* Type of image */
+#define VK_IMAGE_TYPE_BOOT1 1 /* 1st stage (load to SRAM) */
+#define VK_IMAGE_TYPE_BOOT2 2 /* 2nd stage (load to DDR) */
+ char filename[64]; /* Filename of image */
+};
+
+/* default firmware images names */
+#define VK_BOOT1_DEF_VALKYRIE_FILENAME "vk-boot1.bin"
+#define VK_BOOT2_DEF_VALKYRIE_FILENAME "vk-boot2.bin"
+
+#define VK_BOOT1_DEF_VIPER_FILENAME "vp-boot1.bin"
+#define VK_BOOT2_DEF_VIPER_FILENAME "vp-boot2.bin"
+
+struct vk_access {
+ __u8 barno; /* BAR number to use */
+ __u8 type; /* Type of access */
+#define VK_ACCESS_READ 0
+#define VK_ACCESS_WRITE 1
+ __u32 len; /* length of data */
+ __u64 offset; /* offset in BAR */
+ __u32 *data; /* where to read/write data to */
+};
+
+struct vk_reset {
+ __u32 arg1;
+ __u32 arg2;
+};
+
+#define VK_MAGIC 0x5E
+
+/* Load image to Valkyrie */
+#define VK_IOCTL_LOAD_IMAGE _IOW(VK_MAGIC, 0x2, struct vk_image)
+
+/* Read data from Valkyrie */
+#define VK_IOCTL_ACCESS_BAR _IOWR(VK_MAGIC, 0x3, struct vk_access)
+
+/* Send Reset to Valkyrie */
+#define VK_IOCTL_RESET _IOW(VK_MAGIC, 0x4, struct vk_reset)
+
+/*
+ * message block - basic unit in the message where a message's size is always
+ * N x sizeof(basic_block)
+ */
+struct vk_msg_blk {
+ __u8 function_id;
+#define VK_FID_TRANS_BUF 5
+#define VK_FID_SHUTDOWN 8
+ __u8 size;
+ __u16 queue_id:4;
+ __u16 msg_id:12;
+ __u32 context_id;
+ __u32 args[2];
+#define VK_CMD_PLANES_MASK 0x000F /* number of planes to up/download */
+#define VK_CMD_UPLOAD 0x0400 /* memory transfer to vk */
+#define VK_CMD_DOWNLOAD 0x0500 /* memory transfer from vk */
+#define VK_CMD_MASK 0x0F00 /* command mask */
+};
+
+#define VK_BAR_FWSTS 0x41C
+/* VK_FWSTS definitions */
+#define VK_FWSTS_RELOCATION_ENTRY BIT(0)
+#define VK_FWSTS_RELOCATION_EXIT BIT(1)
+#define VK_FWSTS_INIT_START BIT(2)
+#define VK_FWSTS_ARCH_INIT_DONE BIT(3)
+#define VK_FWSTS_PRE_KNL1_INIT_DONE BIT(4)
+#define VK_FWSTS_PRE_KNL2_INIT_DONE BIT(5)
+#define VK_FWSTS_POST_KNL_INIT_DONE BIT(6)
+#define VK_FWSTS_INIT_DONE BIT(7)
+#define VK_FWSTS_APP_INIT_START BIT(8)
+#define VK_FWSTS_APP_INIT_DONE BIT(9)
+#define VK_FWSTS_MASK 0xFFFFFFFF
+#define VK_FWSTS_READY (VK_FWSTS_INIT_START | \
+ VK_FWSTS_ARCH_INIT_DONE | \
+ VK_FWSTS_PRE_KNL1_INIT_DONE | \
+ VK_FWSTS_PRE_KNL2_INIT_DONE | \
+ VK_FWSTS_POST_KNL_INIT_DONE | \
+ VK_FWSTS_INIT_DONE | \
+ VK_FWSTS_APP_INIT_START | \
+ VK_FWSTS_APP_INIT_DONE)
+/* Deinit */
+#define VK_FWSTS_APP_DEINIT_START BIT(23)
+#define VK_FWSTS_APP_DEINIT_DONE BIT(24)
+#define VK_FWSTS_DRV_DEINIT_START BIT(25)
+#define VK_FWSTS_DRV_DEINIT_DONE BIT(26)
+#define VK_FWSTS_RESET_DONE BIT(27)
+#define VK_FWSTS_DEINIT_TRIGGERED (VK_FWSTS_APP_DEINIT_START | \
+ VK_FWSTS_APP_DEINIT_DONE | \
+ VK_FWSTS_DRV_DEINIT_START | \
+ VK_FWSTS_DRV_DEINIT_DONE)
+/* Last nibble for reboot reason */
+#define VK_FWSTS_RESET_REASON_SHIFT 28
+#define VK_FWSTS_RESET_REASON_MASK (0xF << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_SYS_PWRUP (0x0 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_MBOX_DB (0x1 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_M7_WDOG (0x2 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_TEMP (0x3 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_PCI_FLR (0x4 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_PCI_HOT (0x5 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_PCI_WARM (0x6 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_PCI_COLD (0x7 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_L1 (0x8 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_L0 (0x9 << VK_FWSTS_RESET_REASON_SHIFT)
+#define VK_FWSTS_RESET_UNKNOWN (0xF << VK_FWSTS_RESET_REASON_SHIFT)
+
+#endif /* __UAPI_LINUX_MISC_BCM_VK_H */
--
2.17.1

2020-02-20 01:05:01

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Hi,

On 2/19/20 4:48 PM, Scott Branden wrote:
> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
> new file mode 100644
> index 000000000000..c75dfb89a38d
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/Kconfig
> @@ -0,0 +1,42 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Broadcom VK device
> +#
> +config BCM_VK
> + tristate "Support for Broadcom VK Accelerators"
> + depends on PCI_MSI
> + default m

Need to justify default m. Normally we don't add drivers as enabled unless
they are required for basic (boot) operation.

> + help
> + Select this option to enable support for Broadcom
> + VK Accelerators. VK is used for performing
> + specific video offload processing. This driver enables
> + userspace programs to access these accelerators via /dev/bcm-vk.N
> + devices.
> +
> + If unsure, say N.
> +
> +if BCM_VK
> +
> +config BCM_VK_H2VK_VERIFY_AND_RETRY
> + bool "Host To VK Verifiy Data and Retry"

Verify

> + help
> + Turn on to verify the data passed down to VK is good,
> + and if not, do a retry until it succeeds.

No timeout on that retry?

> + This is a debug/workaround on FPGA PCIe timing issues
> + but may be found useful for debugging other PCIe hardware issues.
> + Small performance loss by enabling this debug config.
> + For properly operating PCIe hardware no need to enable this.
> +
> + If unsure, say N.
> +
> +config BCM_VK_QSTATS
> + bool "VK Queue Statistics"
> + help
> + Turn on to enable Queue Statistics.
> + These are useful for debugging purposes.
> + Some performance loss by enabling this debug config.
> + For properly operating PCIe hardware no need to enable this.
> +
> + If unsure, say N.
> +
> +endif

cheers.
--
~Randy

2020-02-20 01:23:17

by Luis Chamberlain

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] firmware: add offset to request_firmware_into_buf

On Wed, Feb 19, 2020 at 04:48:20PM -0800, Scott Branden wrote:
> Add offset to request_firmware_into_buf to allow for portions
> of firmware file to be read into a buffer. Necessary where firmware
> needs to be loaded in portions from file in memory constrained systems.
>
> Signed-off-by: Scott Branden <[email protected]>

Thanks for following up Scott! However you failed to address the
feedback last time by Takashi, so until then, this remains a blocker for
this.

http://lkml.kernel.org/r/[email protected]

Luis

2020-02-20 07:47:35

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Wed, Feb 19, 2020 at 04:48:24PM -0800, Scott Branden wrote:
> Add Broadcom VK driver offload engine.
> This driver interfaces to the VK PCIe offload engine to perform
> should offload functions as video transcoding on multiple streams
> in parallel. VK device is booted from files loaded using
> request_firmware_into_buf mechanism. After booted card status is updated
> and messages can then be sent to the card.
> Such messages contain scatter gather list of addresses
> to pull data from the host to perform operations on.

Why is this a tty driver?

Have you worked with the V4L developers to tie this into the proper
in-kernel apis for this type of functionality?

Using a tty driver seems like the totally incorrect way to do this, what
am I missing?

Also, do not make up random error values, you return "-1" a lot here,
that is not ok. Please fix up to return the correct -Ewhatever values
instead.

thanks,

greg k-h

2020-02-20 07:51:10

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI

On Wed, Feb 19, 2020 at 04:48:23PM -0800, Scott Branden wrote:
> Add user space api for bcm-vk driver.
>
> Signed-off-by: Scott Branden <[email protected]>
> ---
> include/uapi/linux/misc/bcm_vk.h | 117 +++++++++++++++++++++++++++++++
> 1 file changed, 117 insertions(+)
> create mode 100644 include/uapi/linux/misc/bcm_vk.h
>
> diff --git a/include/uapi/linux/misc/bcm_vk.h b/include/uapi/linux/misc/bcm_vk.h
> new file mode 100644
> index 000000000000..56a2178e06f5
> --- /dev/null
> +++ b/include/uapi/linux/misc/bcm_vk.h
> @@ -0,0 +1,117 @@
> +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#ifndef __UAPI_LINUX_MISC_BCM_VK_H
> +#define __UAPI_LINUX_MISC_BCM_VK_H
> +
> +#include <linux/ioctl.h>
> +#include <linux/types.h>
> +
> +struct vk_image {
> + __u32 type; /* Type of image */
> +#define VK_IMAGE_TYPE_BOOT1 1 /* 1st stage (load to SRAM) */
> +#define VK_IMAGE_TYPE_BOOT2 2 /* 2nd stage (load to DDR) */
> + char filename[64]; /* Filename of image */

__u8?

> +};
> +
> +/* default firmware images names */
> +#define VK_BOOT1_DEF_VALKYRIE_FILENAME "vk-boot1.bin"
> +#define VK_BOOT2_DEF_VALKYRIE_FILENAME "vk-boot2.bin"
> +
> +#define VK_BOOT1_DEF_VIPER_FILENAME "vp-boot1.bin"
> +#define VK_BOOT2_DEF_VIPER_FILENAME "vp-boot2.bin"

Why do these need to be in a uapi .h file? Shouldn't they just be part
of the normal MODULE_FIRMWARE() macro in the driver itself?

> +
> +struct vk_access {
> + __u8 barno; /* BAR number to use */
> + __u8 type; /* Type of access */
> +#define VK_ACCESS_READ 0
> +#define VK_ACCESS_WRITE 1
> + __u32 len; /* length of data */

Horrible padding issues, are you sure this all works properly?

> + __u64 offset; /* offset in BAR */
> + __u32 *data; /* where to read/write data to */

Are you _SURE_ you want a pointer here? How do you handle the compat
issues with 32/64 user/kernel space?

> +};

And isn't this just a normal PCI write thing? Can't you do it from
userspace using the existing userspace PCI accesses? Why do you need a
special ioctl for it?

> +
> +struct vk_reset {
> + __u32 arg1;
> + __u32 arg2;
> +};
> +
> +#define VK_MAGIC 0x5E
> +
> +/* Load image to Valkyrie */
> +#define VK_IOCTL_LOAD_IMAGE _IOW(VK_MAGIC, 0x2, struct vk_image)
> +
> +/* Read data from Valkyrie */
> +#define VK_IOCTL_ACCESS_BAR _IOWR(VK_MAGIC, 0x3, struct vk_access)
> +
> +/* Send Reset to Valkyrie */
> +#define VK_IOCTL_RESET _IOW(VK_MAGIC, 0x4, struct vk_reset)
> +
> +/*
> + * message block - basic unit in the message where a message's size is always
> + * N x sizeof(basic_block)
> + */
> +struct vk_msg_blk {
> + __u8 function_id;
> +#define VK_FID_TRANS_BUF 5
> +#define VK_FID_SHUTDOWN 8
> + __u8 size;
> + __u16 queue_id:4;
> + __u16 msg_id:12;

Do not use bitfields in ioctls, they will not work properly on all
systems. Use masks and shifts instead.

> + __u32 context_id;
> + __u32 args[2];
> +#define VK_CMD_PLANES_MASK 0x000F /* number of planes to up/download */
> +#define VK_CMD_UPLOAD 0x0400 /* memory transfer to vk */
> +#define VK_CMD_DOWNLOAD 0x0500 /* memory transfer from vk */
> +#define VK_CMD_MASK 0x0F00 /* command mask */
> +};
> +
> +#define VK_BAR_FWSTS 0x41C
> +/* VK_FWSTS definitions */
> +#define VK_FWSTS_RELOCATION_ENTRY BIT(0)
> +#define VK_FWSTS_RELOCATION_EXIT BIT(1)
> +#define VK_FWSTS_INIT_START BIT(2)
> +#define VK_FWSTS_ARCH_INIT_DONE BIT(3)
> +#define VK_FWSTS_PRE_KNL1_INIT_DONE BIT(4)
> +#define VK_FWSTS_PRE_KNL2_INIT_DONE BIT(5)
> +#define VK_FWSTS_POST_KNL_INIT_DONE BIT(6)
> +#define VK_FWSTS_INIT_DONE BIT(7)
> +#define VK_FWSTS_APP_INIT_START BIT(8)
> +#define VK_FWSTS_APP_INIT_DONE BIT(9)

I do not think that BIT() is exported to userspace properly, is it
really ok here?

> +#define VK_FWSTS_MASK 0xFFFFFFFF
> +#define VK_FWSTS_READY (VK_FWSTS_INIT_START | \
> + VK_FWSTS_ARCH_INIT_DONE | \
> + VK_FWSTS_PRE_KNL1_INIT_DONE | \
> + VK_FWSTS_PRE_KNL2_INIT_DONE | \
> + VK_FWSTS_POST_KNL_INIT_DONE | \
> + VK_FWSTS_INIT_DONE | \
> + VK_FWSTS_APP_INIT_START | \
> + VK_FWSTS_APP_INIT_DONE)
> +/* Deinit */
> +#define VK_FWSTS_APP_DEINIT_START BIT(23)
> +#define VK_FWSTS_APP_DEINIT_DONE BIT(24)
> +#define VK_FWSTS_DRV_DEINIT_START BIT(25)
> +#define VK_FWSTS_DRV_DEINIT_DONE BIT(26)
> +#define VK_FWSTS_RESET_DONE BIT(27)
> +#define VK_FWSTS_DEINIT_TRIGGERED (VK_FWSTS_APP_DEINIT_START | \
> + VK_FWSTS_APP_DEINIT_DONE | \
> + VK_FWSTS_DRV_DEINIT_START | \
> + VK_FWSTS_DRV_DEINIT_DONE)
> +/* Last nibble for reboot reason */
> +#define VK_FWSTS_RESET_REASON_SHIFT 28
> +#define VK_FWSTS_RESET_REASON_MASK (0xF << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_SYS_PWRUP (0x0 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_MBOX_DB (0x1 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_M7_WDOG (0x2 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_TEMP (0x3 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_PCI_FLR (0x4 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_PCI_HOT (0x5 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_PCI_WARM (0x6 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_PCI_COLD (0x7 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_L1 (0x8 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_L0 (0x9 << VK_FWSTS_RESET_REASON_SHIFT)
> +#define VK_FWSTS_RESET_UNKNOWN (0xF << VK_FWSTS_RESET_REASON_SHIFT)

What are all of these #defines doing in an uapi file? How is userspace
going to use them?

thanks,

greg k-h

2020-02-20 08:44:53

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

On Wed, Feb 19, 2020 at 04:48:21PM -0800, Scott Branden wrote:
> +static int test_dev_config_update_size_t(const char *buf,
> + size_t size,
> + size_t *cfg)
> +{
> + int ret;
> + long new;
> +
> + ret = kstrtol(buf, 10, &new);
> + if (ret)
> + return ret;
> +
> + if (new > SIZE_MAX)

This "new" variable is long and SIZE_MAX is ULONG_MAX so the condition
can't be true.

> + return -EINVAL;
> +
> + mutex_lock(&test_fw_mutex);
> + *(size_t *)cfg = new;
> + mutex_unlock(&test_fw_mutex);
> +
> + /* Always return full write size even if we didn't consume all */
> + return size;
> +}
> +
> +static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
> +{
> + size_t val;
> +
> + mutex_lock(&test_fw_mutex);
> + val = cfg;
> + mutex_unlock(&test_fw_mutex);

Both val and cfg are stack variables so there is no need for locking.
Probably you meant to pass a pointer to cfg?

> +
> + return snprintf(buf, PAGE_SIZE, "%zu\n", val);
> +}
> +
> static ssize_t test_dev_config_show_int(char *buf, int cfg)
> {
> int val;

regards,
dan carpenter


2020-02-20 10:46:49

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

I'm not sure what git tree you're building on top of. This patchset
doesn't apply to linux-next which is the only thing I tested...

On Wed, Feb 19, 2020 at 04:48:24PM -0800, Scott Branden wrote:
> Add Broadcom VK driver offload engine.
> This driver interfaces to the VK PCIe offload engine to perform
> should offload functions as video transcoding on multiple streams
> in parallel. VK device is booted from files loaded using
> request_firmware_into_buf mechanism. After booted card status is updated
> and messages can then be sent to the card.
> Such messages contain scatter gather list of addresses
> to pull data from the host to perform operations on.
>
> Signed-off-by: Scott Branden <[email protected]>
> Signed-off-by: Desmond Yan <[email protected]>
> Signed-off-by: James Hu <[email protected]>
> ---
> drivers/misc/Kconfig | 1 +
> drivers/misc/Makefile | 1 +
> drivers/misc/bcm-vk/Kconfig | 42 +
> drivers/misc/bcm-vk/Makefile | 11 +
> drivers/misc/bcm-vk/bcm_vk.h | 357 ++++++++
> drivers/misc/bcm-vk/bcm_vk_dev.c | 1197 ++++++++++++++++++++++++++
> drivers/misc/bcm-vk/bcm_vk_msg.c | 1359 ++++++++++++++++++++++++++++++
> drivers/misc/bcm-vk/bcm_vk_msg.h | 210 +++++
> drivers/misc/bcm-vk/bcm_vk_sg.c | 273 ++++++
> drivers/misc/bcm-vk/bcm_vk_sg.h | 60 ++
> drivers/misc/bcm-vk/bcm_vk_tty.c | 327 +++++++
> 11 files changed, 3838 insertions(+)
> create mode 100644 drivers/misc/bcm-vk/Kconfig
> create mode 100644 drivers/misc/bcm-vk/Makefile
> create mode 100644 drivers/misc/bcm-vk/bcm_vk.h
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_dev.c
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.c
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.h
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.c
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.h
> create mode 100644 drivers/misc/bcm-vk/bcm_vk_tty.c
>
> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
> index 7f0d48f406e3..39c74ed6a846 100644
> --- a/drivers/misc/Kconfig
> +++ b/drivers/misc/Kconfig
> @@ -478,6 +478,7 @@ source "drivers/misc/genwqe/Kconfig"
> source "drivers/misc/echo/Kconfig"
> source "drivers/misc/cxl/Kconfig"
> source "drivers/misc/ocxl/Kconfig"
> +source "drivers/misc/bcm-vk/Kconfig"
> source "drivers/misc/cardreader/Kconfig"
> source "drivers/misc/habanalabs/Kconfig"
> endmenu
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index c1860d35dc7e..d9a3b2791560 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
> obj-$(CONFIG_CXL_BASE) += cxl/
> obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
> obj-$(CONFIG_OCXL) += ocxl/
> +obj-$(CONFIG_BCM_VK) += bcm-vk/
> obj-y += cardreader/
> obj-$(CONFIG_PVPANIC) += pvpanic.o
> obj-$(CONFIG_HABANA_AI) += habanalabs/
> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
> new file mode 100644
> index 000000000000..c75dfb89a38d
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/Kconfig
> @@ -0,0 +1,42 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Broadcom VK device
> +#
> +config BCM_VK
> + tristate "Support for Broadcom VK Accelerators"
> + depends on PCI_MSI
> + default m
> + help
> + Select this option to enable support for Broadcom
> + VK Accelerators. VK is used for performing
> + specific video offload processing. This driver enables
> + userspace programs to access these accelerators via /dev/bcm-vk.N
> + devices.
> +
> + If unsure, say N.
> +
> +if BCM_VK
> +
> +config BCM_VK_H2VK_VERIFY_AND_RETRY
> + bool "Host To VK Verifiy Data and Retry"
> + help
> + Turn on to verify the data passed down to VK is good,
> + and if not, do a retry until it succeeds.
> + This is a debug/workaround on FPGA PCIe timing issues
> + but may be found useful for debugging other PCIe hardware issues.
> + Small performance loss by enabling this debug config.
> + For properly operating PCIe hardware no need to enable this.
> +
> + If unsure, say N.
> +
> +config BCM_VK_QSTATS
> + bool "VK Queue Statistics"
> + help
> + Turn on to enable Queue Statistics.
> + These are useful for debugging purposes.
> + Some performance loss by enabling this debug config.
> + For properly operating PCIe hardware no need to enable this.
> +
> + If unsure, say N.
> +
> +endif
> diff --git a/drivers/misc/bcm-vk/Makefile b/drivers/misc/bcm-vk/Makefile
> new file mode 100644
> index 000000000000..05cb213ee826
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/Makefile
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for Broadcom VK driver
> +#
> +
> +obj-$(CONFIG_BCM_VK) += bcm_vk.o
> +bcm_vk-objs := \
> + bcm_vk_dev.o \
> + bcm_vk_msg.o \
> + bcm_vk_sg.o \
> + bcm_vk_tty.o
> diff --git a/drivers/misc/bcm-vk/bcm_vk.h b/drivers/misc/bcm-vk/bcm_vk.h
> new file mode 100644
> index 000000000000..203ca741bf88
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk.h
> @@ -0,0 +1,357 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#ifndef BCM_VK_H
> +#define BCM_VK_H
> +
> +#include <linux/atomic.h>
> +#include <linux/pci.h>
> +#include <linux/irq.h>
> +#include <linux/miscdevice.h>
> +#include <linux/mutex.h>
> +#include <linux/sched/signal.h>
> +#include <linux/tty.h>
> +#include <linux/uaccess.h>
> +#include <linux/version.h>
> +#include <uapi/linux/misc/bcm_vk.h>
> +
> +#include "bcm_vk_msg.h"
> +
> +#define DRV_MODULE_NAME "bcm-vk"
> +
> +/*
> + * Load Image is completed in two stages:
> + *
> + * 1) When the VK device boot-up, M7 CPU runs and executes the BootROM.
> + * The Secure Boot Loader (SBL) as part of the BootROM will run
> + * to open up ITCM for host to push BOOT1 image.
> + * SBL will authenticate the image before jumping to BOOT1 image.
> + *
> + * 2) Because BOOT1 image is a secured image, we also called it the
> + * Secure Boot Image (SBI). At second stage, SBI will initialize DDR
> + * and wait for host to push BOOT2 image to DDR.
> + * SBI will authenticate the image before jumping to BOOT2 image.
> + *
> + */
> +/* Location of registers of interest in BAR0 */
> +
> +/* Request register for Secure Boot Loader (SBL) download */
> +#define BAR_CODEPUSH_SBL 0x400
> +/* Start of ITCM */
> +#define CODEPUSH_BOOT1_ENTRY 0x00400000
> +#define CODEPUSH_MASK 0xFFFFF000
> +#define CODEPUSH_BOOTSTART BIT(0)
> +
> +/* Boot Status register */
> +#define BAR_BOOT_STATUS 0x404
> +
> +#define SRAM_OPEN BIT(16)
> +#define DDR_OPEN BIT(17)
> +
> +/* Firmware loader progress status definitions */
> +#define FW_LOADER_ACK_SEND_MORE_DATA BIT(18)
> +#define FW_LOADER_ACK_IN_PROGRESS BIT(19)
> +#define FW_LOADER_ACK_RCVD_ALL_DATA BIT(20)
> +
> +/* definitions for boot status register */
> +#define BOOT_STATE_MASK 0xFFF3FFFF
> +#define BROM_STATUS_NOT_RUN 0x2
> +#define BROM_NOT_RUN (SRAM_OPEN | BROM_STATUS_NOT_RUN)
> +#define BROM_STATUS_COMPLETE 0x6
> +#define BROM_RUNNING (SRAM_OPEN | BROM_STATUS_COMPLETE)
> +#define BOOT1_STATUS_COMPLETE 0x6
> +#define BOOT1_RUNNING (DDR_OPEN | BOOT1_STATUS_COMPLETE)
> +#define BOOT2_STATUS_COMPLETE 0x6
> +#define BOOT2_RUNNING (FW_LOADER_ACK_RCVD_ALL_DATA | \
> + BOOT2_STATUS_COMPLETE)
> +
> +/* Boot request for Secure Boot Image (SBI) */
> +#define BAR_CODEPUSH_SBI 0x408
> +/* 64M mapped to BAR2 */
> +#define CODEPUSH_BOOT2_ENTRY 0x60000000
> +
> +#define BAR_CARD_STATUS 0x410
> +/* CARD_STATUS definitions */
> +#define CARD_STATUS_TTYVK0_READY BIT(0)
> +#define CARD_STATUS_TTYVK1_READY BIT(1)
> +
> +#define BAR_METADATA_VERSION 0x440
> +#define BAR_OS_UPTIME 0x444
> +#define BAR_CHIP_ID 0x448
> +
> +#define BAR_CARD_TEMPERATURE 0x45C
> +/* defines for all temperature sensor */
> +#define BCM_VK_TEMP_FIELD_MASK 0xFF
> +#define BCM_VK_CPU_TEMP_SHIFT 0
> +#define BCM_VK_DDR0_TEMP_SHIFT 8
> +#define BCM_VK_DDR1_TEMP_SHIFT 16
> +
> +#define BAR_CARD_VOLTAGE 0x460
> +/* defines for voltage rail conversion */
> +#define BCM_VK_VOLT_RAIL_MASK 0xFFFF
> +#define BCM_VK_3P3_VOLT_REG_SHIFT 16
> +
> +#define BAR_CARD_ERR_LOG 0x464
> +/* Error log register bit definition - register for error alerts */
> +#define ERR_LOG_UECC BIT(0)
> +#define ERR_LOG_SSIM_BUSY BIT(1)
> +#define ERR_LOG_AFBC_BUSY BIT(2)
> +#define ERR_LOG_HIGH_TEMP_ERR BIT(3)
> +#define ERR_LOG_WDOG_TIMEOUT BIT(4)
> +#define ERR_LOG_SYS_FAULT BIT(5)
> +#define ERR_LOG_MEM_ALLOC_FAIL BIT(8)
> +#define ERR_LOG_LOW_TEMP_WARN BIT(9)
> +#define ERR_LOG_ECC BIT(10)
> +/* Alert bit definitions detectd on host */
> +#define ERR_LOG_HOST_HB_FAIL BIT(14)
> +#define ERR_LOG_HOST_PCIE_DWN BIT(15)
> +
> +#define BAR_CARD_ERR_MEM 0x468
> +/* defines for mem err, all fields have same width */
> +#define BCM_VK_MEM_ERR_FIELD_MASK 0xFF
> +#define BCM_VK_ECC_MEM_ERR_SHIFT 0
> +#define BCM_VK_UECC_MEM_ERR_SHIFT 8
> +/* threshold of event occurrence and logs start to come out */
> +#define BCM_VK_ECC_THRESHOLD 10
> +#define BCM_VK_UECC_THRESHOLD 1
> +
> +#define BAR_CARD_PWR_AND_THRE 0x46C
> +/* defines for power and temp threshold, all fields have same width */
> +#define BCM_VK_PWR_AND_THRE_FIELD_MASK 0xFF
> +#define BCM_VK_LOW_TEMP_THRE_SHIFT 0
> +#define BCM_VK_HIGH_TEMP_THRE_SHIFT 8
> +#define BCM_VK_PWR_STATE_SHIFT 16
> +
> +#define BAR_CARD_STATIC_INFO 0x470
> +
> +#define BAR_BOOTSRC_SELECT 0xC78
> +/* BOOTSRC definitions */
> +#define BOOTSRC_SOFT_ENABLE BIT(14)
> +
> +/* Card OS Firmware version size */
> +#define BAR_FIRMWARE_TAG_SIZE 50
> +#define FIRMWARE_STATUS_PRE_INIT_DONE 0x1F
> +
> +/* VK MSG_ID defines */
> +#define VK_MSG_ID_BITMAP_SIZE 4096
> +#define VK_MSG_ID_BITMAP_MASK (VK_MSG_ID_BITMAP_SIZE - 1)
> +#define VK_MSG_ID_OVERFLOW 0xFFFF
> +
> +/* VK device supports a maximum of 3 bars */
> +#define MAX_BAR 3
> +
> +/* default number of msg blk for inband SGL */
> +#define BCM_VK_DEF_IB_SGL_BLK_LEN 16
> +#define BCM_VK_IB_SGL_BLK_MAX 24
> +
> +enum pci_barno {
> + BAR_0 = 0,
> + BAR_1,
> + BAR_2
> +};
> +
> +#define BCM_VK_NUM_TTY 2
> +
> +struct bcm_vk_tty {
> + struct tty_port port;
> + uint32_t to_offset; /* bar offset to use */
> + uint32_t to_size; /* to VK buffer size */
> + uint32_t wr; /* write offset shadow */
> + uint32_t from_offset; /* bar offset to use */
> + uint32_t from_size; /* from VK buffer size */
> + uint32_t rd; /* read offset shadow */
> +};
> +
> +/* VK device max power state, supports 3, full, reduced and low */
> +#define MAX_OPP 3
> +#define MAX_CARD_INFO_TAG_SIZE 64
> +
> +struct bcm_vk_card_info {
> + uint32_t version;
> + char os_tag[MAX_CARD_INFO_TAG_SIZE];
> + char cmpt_tag[MAX_CARD_INFO_TAG_SIZE];
> + uint32_t cpu_freq_mhz;
> + uint32_t cpu_scale[MAX_OPP];
> + uint32_t ddr_freq_mhz;
> + uint32_t ddr_size_MB;
> + uint32_t video_core_freq_mhz;
> +};
> +
> +/*
> + * Control structure of logging messages from the card. This
> + * buffer is for logmsg that comes from vk
> + */
> +struct bcm_vk_peer_log {
> + uint32_t rd_idx;
> + uint32_t wr_idx;
> + uint32_t buf_size;
> + uint32_t mask;
> + char data[0];
> +};
> +/* max size per line of peer log */
> +#define BCM_VK_PEER_LOG_LINE_MAX 256
> +
> +struct bcm_vk_hb_ctrl {
> + struct timer_list timer;
> + uint32_t last_uptime;
> + uint32_t lost_cnt;
> +};
> +
> +struct bcm_vk_alert {
> + uint16_t flags;
> + uint16_t notfs;
> +};
> +
> +/* some alert counters that the driver will keep track */
> +struct bcm_vk_alert_cnts {
> + uint16_t ecc;
> + uint16_t uecc;
> +};
> +
> +struct bcm_vk {
> + struct pci_dev *pdev;
> + void __iomem *bar[MAX_BAR];
> + int num_irqs;
> +
> + struct bcm_vk_card_info card_info;
> +
> + /* mutex to protect the ioctls */
> + struct mutex mutex;
> + struct miscdevice miscdev;
> + int misc_devid; /* dev id allocated */
> +
> + struct tty_driver *tty_drv;
> + struct timer_list serial_timer;
> + struct bcm_vk_tty tty[BCM_VK_NUM_TTY];
> +
> + /* Reference-counting to handle file operations */
> + struct kref kref;
> +
> + spinlock_t msg_id_lock;
> + uint16_t msg_id;
> + DECLARE_BITMAP(bmap, VK_MSG_ID_BITMAP_SIZE);
> + spinlock_t ctx_lock;
> + struct bcm_vk_ctx ctx[VK_CMPT_CTX_MAX];
> + struct bcm_vk_ht_entry pid_ht[VK_PID_HT_SZ];
> + struct task_struct *reset_ppid; /* process that issue reset */
> +
> + atomic_t msgq_inited; /* indicate if info has been synced with vk */
> + struct bcm_vk_msg_chan h2vk_msg_chan;
> + struct bcm_vk_msg_chan vk2h_msg_chan;

These two names are almost the same except the letters are jumbled up.
Please, figure out better names because the human mind sees them as
exactly the same at first glance.

https://www.mnn.com/lifestyle/arts-culture/stories/why-your-brain-can-read-jumbled-letters


> +
> + struct workqueue_struct *wq_thread;
> + struct work_struct wq_work; /* work queue for deferred job */
> + unsigned long wq_offload[1]; /* various flags on wq requested */
> + void *tdma_vaddr; /* test dma segment virtual addr */
> + dma_addr_t tdma_addr; /* test dma segment bus addr */
> +
> + struct notifier_block panic_nb;
> + uint32_t ib_sgl_size; /* size allocated for inband sgl insertion */
> +
> + /* heart beat mechanism control structure */
> + struct bcm_vk_hb_ctrl hb_ctrl;
> + /* house-keeping variable of error logs */
> + spinlock_t host_alert_lock; /* protection to access host_alert struct */
> + struct bcm_vk_alert host_alert;
> + struct bcm_vk_alert peer_alert; /* bits set by the card */
> + struct bcm_vk_alert_cnts alert_cnts;
> +
> + /* offset of the peer log control in BAR2 */
> + uint32_t peerlog_off;
> +};
> +
> +/* wq offload work items bits definitions */
> +enum bcm_vk_wq_offload_flags {
> + BCM_VK_WQ_DWNLD_PEND = 0,
> + BCM_VK_WQ_DWNLD_AUTO = 1,
> + BCM_VK_WQ_NOTF_PEND = 2,
> +};
> +
> +/* a macro to get an individual field with mask and shift */
> +#define BCM_VK_EXTRACT_FIELD(_field, _reg, _mask, _shift) \
> + (_field = (((_reg) >> (_shift)) & (_mask)))
> +
> +/* structure that is used to faciliate displaying of register content */
> +struct bcm_vk_entry {
> + const uint32_t mask;
> + const uint32_t exp_val;
> + const char *str;
> +};
> +
> +/* alerts that could be generated from peer */
> +#define BCM_VK_PEER_ERR_NUM 9
> +extern struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM];
> +/* alerts detected by the host */
> +#define BCM_VK_HOST_ERR_NUM 2
> +extern struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM];
> +
> +/*
> + * check if PCIe interface is down on read. Use it when it is
> + * certain that _val should never be all ones.
> + */
> +#define BCM_VK_INTF_IS_DOWN(val) ((val) == 0xFFFFFFFF)
> +
> +static inline u32 vkread32(struct bcm_vk *vk,
> + enum pci_barno bar,
> + uint64_t offset)
> +{
> + u32 value;
> +
> + value = ioread32(vk->bar[bar] + offset);
> + return value;


return ioread32(vk->bar[bar] + offset);

> +}
> +
> +static inline void vkwrite32(struct bcm_vk *vk,
> + u32 value,
> + enum pci_barno bar,
> + uint64_t offset)
> +{
> + iowrite32(value, vk->bar[bar] + offset);
> +}
> +
> +static inline u8 vkread8(struct bcm_vk *vk,
> + enum pci_barno bar,
> + uint64_t offset)
> +{
> + u8 value;
> +
> + value = ioread8(vk->bar[bar] + offset);
> + return value;

return ioread8(vk->bar[bar] + offset);

> +}
> +
> +static inline void vkwrite8(struct bcm_vk *vk,
> + u8 value,
> + enum pci_barno bar,
> + uint64_t offset)
> +{
> + iowrite8(value, vk->bar[bar] + offset);
> +}
> +
> +int bcm_vk_open(struct inode *inode, struct file *p_file);
> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
> + loff_t *f_pos);
> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
> + size_t count, loff_t *f_pos);
> +int bcm_vk_release(struct inode *inode, struct file *p_file);
> +void bcm_vk_release_data(struct kref *kref);
> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id);
> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id);
> +int bcm_vk_msg_init(struct bcm_vk *vk);
> +void bcm_vk_msg_remove(struct bcm_vk *vk);
> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync);
> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk);
> +void bcm_vk_blk_drv_access(struct bcm_vk *vk);
> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type, pid_t pid);
> +void bcm_vk_trigger_reset(struct bcm_vk *vk);
> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num, uint32_t db_val);
> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk);
> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name);
> +void bcm_vk_tty_exit(struct bcm_vk *vk);
> +void bcm_vk_hb_init(struct bcm_vk *vk);
> +void bcm_vk_hb_deinit(struct bcm_vk *vk);
> +void bcm_vk_handle_notf(struct bcm_vk *vk);
> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk);
> +
> +#endif
> diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
> new file mode 100644
> index 000000000000..d8f59e898ca8
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
> @@ -0,0 +1,1197 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/firmware.h>
> +#include <linux/fs.h>
> +#include <linux/idr.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/pci_regs.h>
> +#include <uapi/linux/misc/bcm_vk.h>
> +
> +#include "bcm_vk.h"
> +
> +#define PCI_DEVICE_ID_VALKYRIE 0x5E87
> +#define PCI_DEVICE_ID_VIPER 0x5E88
> +
> +static DEFINE_IDA(bcm_vk_ida);
> +
> +struct load_image_tab {
> + const uint32_t image_type;
> + const char *image_name;

Indented too far.

> +};
> +
> +enum soc_idx {
> + VALKYRIE = 0,
> + VIPER
> +};
> +
> +#define NUM_BOOT_STAGES 2
> +const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
> + [VALKYRIE] = {
> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VALKYRIE_FILENAME},
> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VALKYRIE_FILENAME}
> + },
> + [VIPER] = {
> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VIPER_FILENAME},
> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VIPER_FILENAME}
> + }
> +};
> +
> +/* Location of memory base addresses of interest in BAR1 */
> +/* Load Boot1 to start of ITCM */
> +#define BAR1_CODEPUSH_BASE_BOOT1 0x100000
> +
> +/* Allow minimum 1s for Load Image timeout responses */
> +#define LOAD_IMAGE_TIMEOUT_MS (1 * MSEC_PER_SEC)
> +
> +/* Image startup timeouts */
> +#define BOOT1_STARTUP_TIMEOUT_MS (5 * MSEC_PER_SEC)
> +#define BOOT2_STARTUP_TIMEOUT_MS (10 * MSEC_PER_SEC)
> +
> +/* 1ms wait for checking the transfer complete status */
> +#define TXFR_COMPLETE_TIMEOUT_MS 1
> +
> +/* MSIX usages */
> +#define VK_MSIX_MSGQ_MAX 3
> +#define VK_MSIX_NOTF_MAX 1
> +#define VK_MSIX_IRQ_MAX (VK_MSIX_MSGQ_MAX + VK_MSIX_NOTF_MAX)
> +
> +/* Number of bits set in DMA mask*/
> +#define BCM_VK_DMA_BITS 64
> +
> +/* deinit time for the card os after receiving doorbell */
> +#define BCM_VK_DEINIT_TIME_MS (2 * MSEC_PER_SEC)
> +
> +/*
> + * module parameters
> + */
> +static bool auto_load = true;
> +module_param(auto_load, bool, 0444);
> +MODULE_PARM_DESC(auto_load,
> + "Load images automatically at PCIe probe time.\n");
> +static uint nr_scratch_pages = VK_BAR1_SCRATCH_DEF_NR_PAGES;
> +module_param(nr_scratch_pages, uint, 0444);
> +MODULE_PARM_DESC(nr_scratch_pages,
> + "Number of pre allocated DMAable coherent pages.\n");
> +static uint nr_ib_sgl_blk = BCM_VK_DEF_IB_SGL_BLK_LEN;
> +module_param(nr_ib_sgl_blk, uint, 0444);
> +MODULE_PARM_DESC(nr_ib_sgl_blk,
> + "Number of in-band msg blks for short SGL.\n");
> +
> +/*
> + * alerts that could be generated from peer
> + */
> +struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM] = {
> + {ERR_LOG_UECC, ERR_LOG_UECC, "uecc"},
> + {ERR_LOG_SSIM_BUSY, ERR_LOG_SSIM_BUSY, "ssim_busy"},
> + {ERR_LOG_AFBC_BUSY, ERR_LOG_AFBC_BUSY, "afbc_busy"},
> + {ERR_LOG_HIGH_TEMP_ERR, ERR_LOG_HIGH_TEMP_ERR, "high_temp"},
> + {ERR_LOG_WDOG_TIMEOUT, ERR_LOG_WDOG_TIMEOUT, "wdog_timeout"},
> + {ERR_LOG_SYS_FAULT, ERR_LOG_SYS_FAULT, "sys_fault"},
> + {ERR_LOG_MEM_ALLOC_FAIL, ERR_LOG_MEM_ALLOC_FAIL, "malloc_fail warn"},
> + {ERR_LOG_LOW_TEMP_WARN, ERR_LOG_LOW_TEMP_WARN, "low_temp warn"},
> + {ERR_LOG_ECC, ERR_LOG_ECC, "ecc"},
> +};
> +/* alerts detected by the host */
> +struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM] = {
> + {ERR_LOG_HOST_PCIE_DWN, ERR_LOG_HOST_PCIE_DWN, "PCIe_down"},
> + {ERR_LOG_HOST_HB_FAIL, ERR_LOG_HOST_HB_FAIL, "hb_fail"},
> +};
> +
> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id)
> +{
> + struct bcm_vk *vk = dev_id;
> +
> + if (!bcm_vk_drv_access_ok(vk)) {
> + dev_err(&vk->pdev->dev,
> + "Interrupt %d received when msgq not inited\n", irq);
> + goto skip_schedule_work;
> + }
> +
> + /* if notification is not pending, set bit and schedule work */
> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
> + queue_work(vk->wq_thread, &vk->wq_work);
> +
> +skip_schedule_work:
> + return IRQ_HANDLED;
> +}
> +
> +static void bcm_vk_log_notf(struct bcm_vk *vk,
> + struct bcm_vk_alert *alert,
> + struct bcm_vk_entry const *entry_tab,
> + const uint32_t table_size)
> +{
> + uint32_t i;
> + uint32_t masked_val, latched_val;
> + struct bcm_vk_entry const *entry;
> + uint32_t reg;
> + uint16_t ecc_mem_err, uecc_mem_err;
> + struct device *dev = &vk->pdev->dev;
> +
> + for (i = 0; i < table_size; i++) {
> + entry = &entry_tab[i];
> + masked_val = entry->mask & alert->notfs;
> + latched_val = entry->mask & alert->flags;
> +
> + if (masked_val == ERR_LOG_UECC) {
> + /*
> + * if there is difference between stored cnt and it
> + * is greater than threshold, log it.
> + */
> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
> + BCM_VK_EXTRACT_FIELD(uecc_mem_err, reg,
> + BCM_VK_MEM_ERR_FIELD_MASK,
> + BCM_VK_UECC_MEM_ERR_SHIFT);
> + if ((uecc_mem_err != vk->alert_cnts.uecc) &&
> + (uecc_mem_err >= BCM_VK_UECC_THRESHOLD))
> + dev_info(dev,
> + "ALERT! %s.%d uecc RAISED - ErrCnt %d\n",
> + DRV_MODULE_NAME, vk->misc_devid,
> + uecc_mem_err);
> + vk->alert_cnts.uecc = uecc_mem_err;
> + } else if (masked_val == ERR_LOG_ECC) {
> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
> + BCM_VK_EXTRACT_FIELD(ecc_mem_err, reg,
> + BCM_VK_MEM_ERR_FIELD_MASK,
> + BCM_VK_ECC_MEM_ERR_SHIFT);
> + if ((ecc_mem_err != vk->alert_cnts.ecc) &&
> + (ecc_mem_err >= BCM_VK_ECC_THRESHOLD))
> + dev_info(dev, "ALERT! %s.%d ecc RAISED - ErrCnt %d\n",
> + DRV_MODULE_NAME, vk->misc_devid,
> + ecc_mem_err);
> + vk->alert_cnts.ecc = ecc_mem_err;
> + } else if (masked_val != latched_val) {
> + /* print a log as info */
> + dev_info(dev, "ALERT! %s.%d %s %s\n",
> + DRV_MODULE_NAME, vk->misc_devid, entry->str,
> + masked_val ? "RAISED" : "CLEARED");
> + }
> + }
> +}
> +
> +static void bcm_vk_dump_peer_log(struct bcm_vk *vk)
> +{
> + struct bcm_vk_peer_log log, *p_ctl;
> + char loc_buf[BCM_VK_PEER_LOG_LINE_MAX];
> + int cnt;
> + struct device *dev = &vk->pdev->dev;
> + uint data_offset;
> +
> + p_ctl = vk->bar[BAR_2] + vk->peerlog_off;

Have you tried running Sparse on this driver?

> + log = *p_ctl;
> + /* do a rmb() to make sure log is updated */
> + rmb();
> +
> + dev_dbg(dev, "Peer PANIC: Size 0x%x(0x%x), [Rd Wr] = [%d %d]\n",
> + log.buf_size, log.mask, log.rd_idx, log.wr_idx);
> +
> + cnt = 0;
> + data_offset = vk->peerlog_off + sizeof(struct bcm_vk_peer_log);
> + while (log.rd_idx != log.wr_idx) {
> + loc_buf[cnt] = vkread8(vk, BAR_2, data_offset + log.rd_idx);
> +
> + if ((loc_buf[cnt] == '\0') ||
> + (cnt == (BCM_VK_PEER_LOG_LINE_MAX - 1))) {
> + dev_err(dev, "%s", loc_buf);
> + cnt = 0;
> + } else {
> + cnt++;
> + }
> + log.rd_idx = (log.rd_idx + 1) & log.mask;
> + }
> + /* update rd idx at the end */
> + vkwrite32(vk, log.rd_idx, BAR_2, vk->peerlog_off);
> +}
> +
> +void bcm_vk_handle_notf(struct bcm_vk *vk)
> +{
> + uint32_t reg;
> + struct bcm_vk_alert alert;
> + bool intf_down;
> + unsigned long flags;
> +
> + /* handle peer alerts and then locally detected ones */
> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_LOG);
> + intf_down = BCM_VK_INTF_IS_DOWN(reg);
> + if (!intf_down) {
> + vk->peer_alert.notfs = reg;
> + bcm_vk_log_notf(vk, &vk->peer_alert, bcm_vk_peer_err,
> + ARRAY_SIZE(bcm_vk_peer_err));
> + vk->peer_alert.flags = vk->peer_alert.notfs;
> + } else {
> + /* turn off access */
> + bcm_vk_blk_drv_access(vk);
> + }
> +
> + /* check and make copy of alert with lock and then free lock */
> + spin_lock_irqsave(&vk->host_alert_lock, flags);
> + if (intf_down)
> + vk->host_alert.notfs |= ERR_LOG_HOST_PCIE_DWN;
> +
> + alert = vk->host_alert;
> + vk->host_alert.flags = vk->host_alert.notfs;
> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
> +
> + /* call display with copy */
> + bcm_vk_log_notf(vk, &alert, bcm_vk_host_err,
> + ARRAY_SIZE(bcm_vk_host_err));
> +
> + /*
> + * If it is a sys fault or heartbeat timeout, we would like extract
> + * log msg from the card so that we would know what is the last fault
> + */
> + if ((!intf_down) &&
> + ((vk->host_alert.flags & ERR_LOG_HOST_HB_FAIL) ||
> + (vk->peer_alert.flags & ERR_LOG_SYS_FAULT)))
> + bcm_vk_dump_peer_log(vk);
> +}
> +
> +static inline int bcm_vk_wait(struct bcm_vk *vk, enum pci_barno bar,
> + uint64_t offset, u32 mask, u32 value,
> + unsigned long timeout_ms)
> +{
> + struct device *dev = &vk->pdev->dev;
> + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
> + u32 rd_val;
> +
> + do {
> + rd_val = vkread32(vk, bar, offset);
> + dev_dbg(dev, "BAR%d Offset=0x%llx: 0x%x\n",
> + bar, offset, rd_val);
> +
> + if (time_after(jiffies, timeout))
> + return -ETIMEDOUT;
> +
> + cpu_relax();
> + cond_resched();
> + } while ((rd_val & mask) != value);
> +
> + return 0;
> +}
> +
> +static void bcm_vk_get_card_info(struct bcm_vk *vk)
> +{
> + struct device *dev = &vk->pdev->dev;
> + uint32_t offset;
> + int i;
> + uint8_t *dst;
> + struct bcm_vk_card_info *info = &vk->card_info;
> +
> + /* first read the offset from spare register */
> + offset = vkread32(vk, BAR_0, BAR_CARD_STATIC_INFO);
> + offset &= (pci_resource_len(vk->pdev, BAR_2 * 2) - 1);
> +
> + /* based on the offset, read info to internal card info structure */
> + dst = (uint8_t *)info;
> + for (i = 0; i < sizeof(*info); i++)
> + *dst++ = vkread8(vk, BAR_2, offset++);
> +
> +#define CARD_INFO_LOG_FMT "version : %x\n" \
> + "os_tag : %s\n" \
> + "cmpt_tag : %s\n" \
> + "cpu_freq : %d MHz\n" \
> + "cpu_scale : %d full, %d lowest\n" \
> + "ddr_freq : %d MHz\n" \
> + "ddr_size : %d MB\n" \
> + "video_freq: %d MHz\n"
> + dev_dbg(dev, CARD_INFO_LOG_FMT, info->version, info->os_tag,
> + info->cmpt_tag, info->cpu_freq_mhz, info->cpu_scale[0],
> + info->cpu_scale[MAX_OPP - 1], info->ddr_freq_mhz,
> + info->ddr_size_MB, info->video_core_freq_mhz);
> +
> + /* get the peer log pointer, only need the offset */
> + vk->peerlog_off = offset;
> +}
> +
> +static int bcm_vk_sync_card_info(struct bcm_vk *vk)
> +{
> + uint32_t rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
> +
> + /* check for marker, but allow diags mode to skip sync */
> + if (!bcm_vk_msgq_marker_valid(vk))
> + return (rdy_marker == VK_BAR1_DIAG_RDY_MARKER ? 0 : -EINVAL);
> +
> + /*
> + * Write down scratch addr which is used for DMA. For
> + * signed part, BAR1 is accessible only after boot2 has come
> + * up
> + */
> + if (vk->tdma_addr) {
> + vkwrite32(vk, vk->tdma_addr >> 32, BAR_1,
> + VK_BAR1_SCRATCH_OFF_LO);
> + vkwrite32(vk, (uint32_t)vk->tdma_addr, BAR_1,
> + VK_BAR1_SCRATCH_OFF_HI);
> + vkwrite32(vk, nr_scratch_pages * PAGE_SIZE, BAR_1,
> + VK_BAR1_SCRATCH_SZ_ADDR);
> + }
> +
> + /* get static card info, only need to read once */
> + bcm_vk_get_card_info(vk);
> +
> + return 0;
> +}
> +
> +void bcm_vk_blk_drv_access(struct bcm_vk *vk)
> +{
> + int i;
> +
> + /*
> + * kill all the apps except for the process that is resetting.
> + * If not called during reset, reset_ppid == NULL, and all will be
> + * killed.
> + */
> + spin_lock(&vk->ctx_lock);
> +
> + /* set msgq_inited to 0 so that all rd/wr will be blocked */
> + atomic_set(&vk->msgq_inited, 0);
> +
> + for (i = 0; i < VK_PID_HT_SZ; i++) {
> + struct bcm_vk_ctx *ctx;
> +
> + list_for_each_entry(ctx, &vk->pid_ht[i].head, node) {
> + if (ctx->ppid != vk->reset_ppid) {
> + dev_dbg(&vk->pdev->dev,
> + "Send kill signal to pid %d\n",
> + task_pid_nr(ctx->ppid));
> + kill_pid(task_pid(ctx->ppid), SIGKILL, 1);
> + }
> + }
> + }
> + spin_unlock(&vk->ctx_lock);
> +}
> +
> +static void bcm_vk_buf_notify(struct bcm_vk *vk, void *bufp,
> + dma_addr_t host_buf_addr, uint32_t buf_size)
> +{
> + /* update the dma address to the card */
> + vkwrite32(vk, host_buf_addr >> 32, BAR_1,
> + VK_BAR1_DMA_BUF_OFF_HI);
> + vkwrite32(vk, (uint32_t)host_buf_addr, BAR_1,
> + VK_BAR1_DMA_BUF_OFF_LO);
> + vkwrite32(vk, buf_size, BAR_1, VK_BAR1_DMA_BUF_SZ);
> +}
> +
> +static int bcm_vk_load_image_by_type(struct bcm_vk *vk, u32 load_type,
> + const char *filename)
> +{
> + struct device *dev = &vk->pdev->dev;
> + const struct firmware *fw;
> + void *bufp;
> + size_t max_buf;
> + int ret;
> + uint64_t offset_codepush;
> + u32 codepush;
> + u32 value;
> + dma_addr_t boot2_dma_addr;
> +
> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
> + /*
> + * After POR, enable VK soft BOOTSRC so bootrom do not clear
> + * the pushed image (the TCM memories).
> + */
> + value = vkread32(vk, BAR_0, BAR_BOOTSRC_SELECT);
> + value |= BOOTSRC_SOFT_ENABLE;
> + vkwrite32(vk, value, BAR_0, BAR_BOOTSRC_SELECT);
> +
> + codepush = CODEPUSH_BOOTSTART + CODEPUSH_BOOT1_ENTRY;
> + offset_codepush = BAR_CODEPUSH_SBL;
> +
> + /* Write a 1 to request SRAM open bit */
> + vkwrite32(vk, CODEPUSH_BOOTSTART, BAR_0, offset_codepush);
> +
> + /* Wait for VK to respond */
> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, SRAM_OPEN,
> + SRAM_OPEN, LOAD_IMAGE_TIMEOUT_MS);
> + if (ret < 0) {
> + dev_err(dev, "boot1 timeout\n");
> + goto err_out;
> + }
> +
> + bufp = vk->bar[BAR_1] + BAR1_CODEPUSH_BASE_BOOT1;
> + max_buf = SZ_256K;
> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
> + codepush = CODEPUSH_BOOT2_ENTRY;
> + offset_codepush = BAR_CODEPUSH_SBI;
> +
> + /* Wait for VK to respond */
> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, DDR_OPEN,
> + DDR_OPEN, LOAD_IMAGE_TIMEOUT_MS);
> + if (ret < 0) {
> + dev_err(dev, "boot2 timeout\n");
> + goto err_out;
> + }
> +
> + max_buf = SZ_4M;
> + bufp = dma_alloc_coherent(dev,
> + max_buf,
> + &boot2_dma_addr, GFP_KERNEL);
> + if (!bufp) {
> + dev_err(dev, "Error allocating 0x%zx\n", max_buf);
> + ret = -ENOMEM;
> + goto err_out;
> + }
> +
> + bcm_vk_buf_notify(vk, bufp, boot2_dma_addr, max_buf);
> + } else {
> + dev_err(dev, "Error invalid image type 0x%x\n", load_type);
> + ret = -EINVAL;
> + goto err_out;
> + }
> +
> + ret = request_firmware_into_buf(&fw, filename, dev,
> + bufp, max_buf, 0,
> + KERNEL_PREAD_FLAG_PART);
> + if (ret) {
> + dev_err(dev, "Error %d requesting firmware file: %s\n",
> + ret, filename);
> + goto err_out;
> + }
> + dev_dbg(dev, "size=0x%zx\n", fw->size);
> +
> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n", codepush, offset_codepush);
> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
> +
> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
> + /* wait until done */
> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
> + BOOT1_RUNNING,
> + BOOT1_RUNNING,
> + BOOT1_STARTUP_TIMEOUT_MS);
> + if (ret) {
> + dev_err(dev,
> + "Timeout %ld ms waiting for boot1 to come up\n",
> + BOOT1_STARTUP_TIMEOUT_MS);
> + goto err_firmware_out;
> + }
> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
> + unsigned long timeout = jiffies + msecs_to_jiffies(
> + LOAD_IMAGE_TIMEOUT_MS);
> +
> + /* To send more data to VK than max_buf allowed at a time */
> + do {
> + /*
> + * Check for ack from card. when Ack is received,
> + * it means all the data is received by card.
> + * Exit the loop after ack is received.
> + */
> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
> + FW_LOADER_ACK_RCVD_ALL_DATA,
> + FW_LOADER_ACK_RCVD_ALL_DATA,
> + TXFR_COMPLETE_TIMEOUT_MS);
> + if (ret == 0) {
> + dev_info(dev, "Exit boot2 download\n");
> + break;
> + }
> +
> + /* exit the loop, if there is no response from card */
> + if (time_after(jiffies, timeout)) {
> + dev_err(dev, "Error. No reply from card\n");
> + ret = -ETIMEDOUT;
> + goto err_firmware_out;
> + }
> +
> + /* Wait for VK to open BAR space to copy new data */
> + ret = bcm_vk_wait(vk, BAR_0, offset_codepush,
> + codepush, 0,
> + TXFR_COMPLETE_TIMEOUT_MS);
> + if (ret == 0) {
> + ret = request_firmware_into_buf(
> + &fw,
> + filename,
> + dev, bufp,
> + max_buf,
> + fw->size,
> + KERNEL_PREAD_FLAG_PART);
> + if (ret) {
> + dev_err(dev, "Error %d requesting firmware file: %s offset: 0x%zx\n",
> + ret, filename,
> + fw->size);
> + goto err_firmware_out;
> + }
> + dev_dbg(dev, "size=0x%zx\n", fw->size);
> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n",
> + codepush, offset_codepush);
> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
> + /* reload timeout after every codepush */
> + timeout = jiffies + msecs_to_jiffies(
> + LOAD_IMAGE_TIMEOUT_MS);
> + }
> + } while (1);
> +
> + /* wait for fw status bits to indicate app ready */
> + ret = bcm_vk_wait(vk, BAR_0, VK_BAR_FWSTS,
> + VK_FWSTS_READY,
> + VK_FWSTS_READY,
> + BOOT2_STARTUP_TIMEOUT_MS);
> + if (ret < 0) {
> + dev_err(dev, "Boot2 not ready - timeout\n");
> + goto err_firmware_out;
> + }
> +
> + /*
> + * Next, initialize Message Q if we are loading boot2.
> + * Do a force sync
> + */
> + ret = bcm_vk_sync_msgq(vk, true);
> + if (ret) {
> + dev_err(dev, "Boot2 Error reading comm msg Q info\n");
> + ret = -EIO;
> + goto err_firmware_out;
> + }
> +
> + /* sync & channel other info */
> + ret = bcm_vk_sync_card_info(vk);
> + if (ret) {
> + dev_err(dev, "Syncing Card Info failure\n");
> + goto err_firmware_out;
> + }
> + }
> +
> +err_firmware_out:
> + release_firmware(fw);
> +
> +err_out:
> + if (load_type == VK_IMAGE_TYPE_BOOT2)
> + dma_free_coherent(dev, max_buf, bufp, boot2_dma_addr);

bufp is uninitialized. Never use the label name "err" or "out" because
it's always wrong. This is just a combination of the two.

> +
> + return ret;
> +}
> +
> +static u32 bcm_vk_next_boot_image(struct bcm_vk *vk)
> +{
> + uint32_t boot_status;
> + uint32_t fw_status;
> + u32 load_type = 0; /* default for unknown */
> +
> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
> +
> + if (!BCM_VK_INTF_IS_DOWN(boot_status) && (boot_status & SRAM_OPEN))
> + load_type = VK_IMAGE_TYPE_BOOT1;
> + else if (boot_status == BOOT1_RUNNING)
> + load_type = VK_IMAGE_TYPE_BOOT2;
> +
> + /*
> + * TO_FIX: For now, like to know what value we get everytime
> + * for debugging.
> + */
> + dev_info(&vk->pdev->dev,
> + "boot-status value for next image: 0x%x : fw-status 0x%x\n",
> + boot_status, fw_status);
> +
> + return load_type;
> +}
> +
> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk)
> +{
> + int i, id, ret = -1;
> + struct device *dev = &vk->pdev->dev;
> + struct pci_dev *pdev = to_pci_dev(dev);
> + uint32_t curr_type;
> + const char *curr_name;
> +
> + switch (pdev->device) {
> + case PCI_DEVICE_ID_VALKYRIE:
> + id = VALKYRIE;
> + break;
> +
> + case PCI_DEVICE_ID_VIPER:
> + id = VIPER;
> + break;
> +
> + default:
> + dev_err(dev, "no images for 0x%x\n", pdev->device);
> + goto bcm_vk_auto_load_all_exit;
> + }
> +
> + /* log a message to know the relative loading order */
> + dev_info(dev, "Load All for device %d\n", vk->misc_devid);
> +
> + for (i = 0; i < NUM_BOOT_STAGES; i++) {
> + curr_type = image_tab[id][i].image_type;
> + if (bcm_vk_next_boot_image(vk) == curr_type) {
> + curr_name = image_tab[id][i].image_name;
> + ret = bcm_vk_load_image_by_type(vk, curr_type,
> + curr_name);
> + dev_info(dev, "Auto load %s, ret %d\n",
> + curr_name, ret);
> +
> + if (ret) {
> + dev_err(dev, "Error loading default %s\n",
> + curr_name);
> + goto bcm_vk_auto_load_all_exit;
> + }
> + }
> + }
> +
> +bcm_vk_auto_load_all_exit:
> + return ret;
> +}
> +
> +static int bcm_vk_trigger_autoload(struct bcm_vk *vk)
> +{
> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0)
> + return -EPERM;
> +
> + set_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
> + queue_work(vk->wq_thread, &vk->wq_work);
> +
> + return 0;
> +}
> +
> +static long bcm_vk_load_image(struct bcm_vk *vk, struct vk_image *arg)
> +{
> + int ret;
> + struct device *dev = &vk->pdev->dev;
> + struct vk_image image;
> + u32 next_loadable;
> +
> + if (copy_from_user(&image, arg, sizeof(image))) {
> + ret = -EACCES;
> + goto bcm_vk_load_image_exit;
> + }
> +
> + /* first check if fw is at the right state for the download */

What does this comment mean? Just delete it. The code is clear on
its own.

> + next_loadable = bcm_vk_next_boot_image(vk);
> + if (next_loadable != image.type) {
> + dev_err(dev, "Next expected image %u, Loading %u\n",
> + next_loadable, image.type);
> + ret = -EPERM;
> + goto bcm_vk_load_image_exit;
> + }
> +
> + /*
> + * if something is pending download already. This could only happen
> + * for now when the driver is being loaded, or if someone has issued
> + * another download command in another shell.
> + */
> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0) {
> + dev_err(dev, "Download operation already pending.\n");
> + return -EPERM;

Woohoo for the direct return! Just make this function use direct
returns everywhere and delete all the do nothing returns.

> + }
> +
> + ret = bcm_vk_load_image_by_type(vk, image.type, image.filename);
> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
> +
> +bcm_vk_load_image_exit:
> + return ret;
> +}
> +
> +static long bcm_vk_access_bar(struct bcm_vk *vk, struct vk_access *arg)
> +{
> + struct device *dev = &vk->pdev->dev;
> + struct vk_access access;
> + long ret = -EINVAL;
> + u32 value;
> + long i;
> + long num;
> +
> + if (copy_from_user(&access, arg, sizeof(struct vk_access))) {
> + ret = -EACCES;

return -EFAULT;

> + goto err_out;
> + }
> +
> + /* do some range checking in the barno and offset */
> + if (access.barno >= MAX_BAR) {
> + dev_err(dev, "invalid bar no %d\n", access.barno);
> + goto err_out;
> + } else if ((access.offset + access.len) >
> + pci_resource_len(vk->pdev, access.barno * 2)) {

There is an arithmatic overlfow. Also len should be a multiple of
sizeof(u32).

} else if (access.len % sizeof(u32) ||
access.offset + access.len < access.offset ||
access.offset + access.len > pci_resource_len(vk->pdev,
access.barno * 2)) {


> + dev_err(dev, "invalid bar offset 0x%llx, len 0x%x\n",
> + access.offset, access.len);
> + goto err_out;
> + }
> +
> + if (access.type == VK_ACCESS_READ) {
> + dev_dbg(dev, "read barno:%d offset:0x%llx len:0x%x\n",
> + access.barno, access.offset, access.len);
> + num = access.len / sizeof(u32);
> + for (i = 0; i < num; i++) {
> + value = vkread32(vk, access.barno,
> + access.offset + (i * sizeof(u32)));
> + ret = put_user(value, access.data + i);
> + if (ret)
> + goto err_out;
> +
> + dev_dbg(dev, "0x%x\n", value);
> + }
> + } else if (access.type == VK_ACCESS_WRITE) {
> + dev_dbg(dev, "write barno:%d offset:0x%llx len:0x%x\n",
> + access.barno, access.offset, access.len);
> + num = access.len / sizeof(u32);
> + for (i = 0; i < num; i++) {
> + ret = get_user(value, access.data + i);
> + if (ret)
> + goto err_out;
> +
> + vkwrite32(vk, value, access.barno,
> + access.offset + (i * sizeof(u32)));
> + dev_dbg(dev, "0x%x\n", value);
> + }
> + } else {
> + dev_dbg(dev, "invalid access type %d\n", access.type);
> + }
> +err_out:
> + return ret;
> +}
> +
> +static int bcm_vk_reset_successful(struct bcm_vk *vk)
> +{
> + struct device *dev = &vk->pdev->dev;
> + u32 fw_status, reset_reason;
> + int ret = -EAGAIN;
> +
> + /*
> + * Reset could be triggered when the card in several state:
> + * i) in bootROM
> + * ii) after boot1
> + * iii) boot2 running
> + *
> + * i) & ii) - no status bits will be updated. If vkboot1
> + * runs automatically after reset, it will update the reason
> + * to be unknown reason
> + * iii) - reboot reason match + deinit done.
> + */
> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
> + /* immediate exit if interface goes down */
> + if (BCM_VK_INTF_IS_DOWN(fw_status)) {
> + dev_err(dev, "PCIe Intf Down!\n");
> + goto bcm_vk_reset_exit;
> + }
> +
> + /* initial check on reset reason */

This comment doesn't add anything.

> + reset_reason = (fw_status & VK_FWSTS_RESET_REASON_MASK);
> + if ((reset_reason == VK_FWSTS_RESET_MBOX_DB)
> + || (reset_reason == VK_FWSTS_RESET_UNKNOWN))
> + ret = 0;

Please use checkpatch.pl --strict.

if ((reset_reason == VK_FWSTS_RESET_MBOX_DB) ||
(reset_reason == VK_FWSTS_RESET_UNKNOWN))
ret = 0;


> +
> + /*
> + * if some of the deinit bits are set, but done
> + * bit is not, this is a failure if triggered while boot2 is running
> + */
> + if ((fw_status & VK_FWSTS_DEINIT_TRIGGERED)
> + && !(fw_status & VK_FWSTS_RESET_DONE))
> + ret = -EAGAIN;
> +
> +bcm_vk_reset_exit:
> + dev_dbg(dev, "FW status = 0x%x ret %d\n", fw_status, ret);
> +
> + return ret;
> +}
> +
> +static long bcm_vk_reset(struct bcm_vk *vk, struct vk_reset *arg)
> +{
> + struct device *dev = &vk->pdev->dev;
> + struct vk_reset reset;
> + int ret = 0;
> +
> + if (copy_from_user(&reset, arg, sizeof(struct vk_reset))) {
> + ret = -EACCES;


return -EFAULT;

> + goto err_out;
> + }
> + dev_info(dev, "Issue Reset\n");
> +
> + /*
> + * The following is the sequence of reset:
> + * - send card level graceful shut down
> + * - wait enough time for VK to handle its business, stopping DMA etc
> + * - kill host apps
> + * - Trigger interrupt with DB
> + */
> + bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_GRACEFUL, 0);
> +
> + spin_lock(&vk->ctx_lock);
> + if (!vk->reset_ppid) {
> + vk->reset_ppid = current;
> + } else {
> + dev_err(dev, "Reset already launched by process pid %d\n",
> + task_pid_nr(vk->reset_ppid));
> + ret = -EACCES;
> + }
> + spin_unlock(&vk->ctx_lock);
> + if (ret)
> + goto err_out;
> +
> + bcm_vk_blk_drv_access(vk);
> + bcm_vk_trigger_reset(vk);
> +
> + /*
> + * Wait enough time for card os to deinit
> + * and populate the reset reason.
> + */
> + msleep(BCM_VK_DEINIT_TIME_MS);
> +
> + ret = bcm_vk_reset_successful(vk);
> +
> +err_out:
> + return ret;
> +}
> +
> +static int bcm_vk_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> + struct bcm_vk_ctx *ctx = file->private_data;
> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
> + unsigned long pg_size;
> +
> + /* only BAR2 is mmap possible, which is bar num 4 due to 64bit */
> +#define VK_MMAPABLE_BAR 4
> +
> + pg_size = ((pci_resource_len(vk->pdev, VK_MMAPABLE_BAR) - 1)
> + >> PAGE_SHIFT) + 1;
> + if (vma->vm_pgoff + vma_pages(vma) > pg_size)
> + return -EINVAL;
> +
> + vma->vm_pgoff += (pci_resource_start(vk->pdev, VK_MMAPABLE_BAR)
> + >> PAGE_SHIFT);
> + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> +
> + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
> + vma->vm_end - vma->vm_start,
> + vma->vm_page_prot);
> +}
> +
> +static long bcm_vk_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> + long ret = -EINVAL;
> + struct bcm_vk_ctx *ctx = file->private_data;
> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
> + void __user *argp = (void __user *)arg;
> +
> + dev_dbg(&vk->pdev->dev,
> + "ioctl, cmd=0x%02x, arg=0x%02lx\n",
> + cmd, arg);
> +
> + mutex_lock(&vk->mutex);
> +
> + switch (cmd) {
> + case VK_IOCTL_LOAD_IMAGE:
> + ret = bcm_vk_load_image(vk, argp);
> + break;
> +
> + case VK_IOCTL_ACCESS_BAR:
> + ret = bcm_vk_access_bar(vk, argp);
> + break;
> +
> + case VK_IOCTL_RESET:
> + ret = bcm_vk_reset(vk, argp);
> + break;
> +
> + default:
> + break;
> + }
> +
> + mutex_unlock(&vk->mutex);
> +
> + return ret;
> +}
> +
> +static const struct file_operations bcm_vk_fops = {
> + .owner = THIS_MODULE,
> + .open = bcm_vk_open,
> + .read = bcm_vk_read,
> + .write = bcm_vk_write,
> + .release = bcm_vk_release,
> + .mmap = bcm_vk_mmap,
> + .unlocked_ioctl = bcm_vk_ioctl,
> +};
> +
> +static int bcm_vk_on_panic(struct notifier_block *nb,
> + unsigned long e, void *p)
> +{
> + struct bcm_vk *vk = container_of(nb, struct bcm_vk, panic_nb);
> +
> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_HARD);
> +
> + return 0;
> +}
> +
> +static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
> +{
> + int err;
> + int i;
> + int id;
> + int irq;
> + char name[20];
> + struct bcm_vk *vk;
> + struct device *dev = &pdev->dev;
> + struct miscdevice *misc_device;
> + uint32_t boot_status;
> +
> + /* allocate vk structure which is tied to kref for freeing */
> + vk = kzalloc(sizeof(*vk), GFP_KERNEL);
> + if (!vk)
> + return -ENOMEM;
> +
> + kref_init(&vk->kref);
> + if (nr_ib_sgl_blk > BCM_VK_IB_SGL_BLK_MAX) {
> + dev_warn(dev, "Inband SGL blk %d limited to max %d\n",
> + nr_ib_sgl_blk, BCM_VK_IB_SGL_BLK_MAX);
> + nr_ib_sgl_blk = BCM_VK_IB_SGL_BLK_MAX;
> + }
> + vk->ib_sgl_size = nr_ib_sgl_blk * VK_MSGQ_BLK_SIZE;
> + vk->pdev = pdev;
> + mutex_init(&vk->mutex);
> +
> + err = pci_enable_device(pdev);
> + if (err) {
> + dev_err(dev, "Cannot enable PCI device\n");
> + return err;
> + }
> +
> + err = pci_request_regions(pdev, DRV_MODULE_NAME);
> + if (err) {
> + dev_err(dev, "Cannot obtain PCI resources\n");
> + goto err_disable_pdev;
> + }
> +
> + /* make sure DMA is good */
> + err = dma_set_mask_and_coherent(&pdev->dev,
> + DMA_BIT_MASK(BCM_VK_DMA_BITS));
> + if (err) {
> + dev_err(dev, "failed to set DMA mask\n");
> + goto err_disable_pdev;
> + }
> +
> + /* The tdma is a scratch area for some DMA testings. */
> + if (nr_scratch_pages) {
> + vk->tdma_vaddr = dma_alloc_coherent(dev,
> + nr_scratch_pages * PAGE_SIZE,
> + &vk->tdma_addr, GFP_KERNEL);
> + if (!vk->tdma_vaddr) {
> + err = -ENOMEM;
> + goto err_disable_pdev;
> + }
> + }
> +
> + pci_set_master(pdev);
> + pci_set_drvdata(pdev, vk);
> +
> + irq = pci_alloc_irq_vectors(pdev,
> + 1,
> + VK_MSIX_IRQ_MAX,
> + PCI_IRQ_MSI | PCI_IRQ_MSIX);
> +
> + if (irq < VK_MSIX_IRQ_MAX) {
> + dev_err(dev, "failed to get %d MSIX interrupts, ret(%d)\n",
> + VK_MSIX_IRQ_MAX, irq);
> + err = (irq >= 0) ? -EINVAL : irq;
> + goto err_disable_pdev;
> + }
> +
> + dev_info(dev, "Number of IRQs %d allocated.\n", irq);
> +
> + for (i = 0; i < MAX_BAR; i++) {
> + /* multiple by 2 for 64 bit BAR mapping */
> + vk->bar[i] = pci_ioremap_bar(pdev, i * 2);
> + if (!vk->bar[i]) {
> + dev_err(dev, "failed to remap BAR%d\n", i);
> + goto err_iounmap;
> + }
> + }
> +
> + for (vk->num_irqs = 0;
> + vk->num_irqs < VK_MSIX_MSGQ_MAX;
> + vk->num_irqs++) {
> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
> + bcm_vk_msgq_irqhandler,
> + IRQF_SHARED, DRV_MODULE_NAME, vk);
> + if (err) {
> + dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n",
> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
> + goto err_irq;
> + }
> + }
> + /* one irq for notification from VK */
> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
> + bcm_vk_notf_irqhandler,
> + IRQF_SHARED, DRV_MODULE_NAME, vk);
> + if (err) {
> + dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n",
> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
> + goto err_irq;
> + }
> + vk->num_irqs++;
> +
> + id = ida_simple_get(&bcm_vk_ida, 0, 0, GFP_KERNEL);
> + if (id < 0) {
> + err = id;
> + dev_err(dev, "unable to get id\n");
> + goto err_irq;
> + }
> +
> + vk->misc_devid = id;
> + snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
> + misc_device = &vk->miscdev;
> + misc_device->minor = MISC_DYNAMIC_MINOR;
> + misc_device->name = kstrdup(name, GFP_KERNEL);
> + if (!misc_device->name) {
> + err = -ENOMEM;
> + goto err_ida_remove;
> + }
> + misc_device->fops = &bcm_vk_fops,
> +
> + err = misc_register(misc_device);
> + if (err) {
> + dev_err(dev, "failed to register device\n");
> + goto err_kfree_name;
> + }
> +
> + err = bcm_vk_msg_init(vk);
> + if (err) {
> + dev_err(dev, "failed to init msg queue info\n");
> + goto err_misc_deregister;
> + }
> +
> + /* sync other info */
> + bcm_vk_sync_card_info(vk);
> +
> + /* register for panic notifier */
> + vk->panic_nb.notifier_call = bcm_vk_on_panic;
> + atomic_notifier_chain_register(&panic_notifier_list,
> + &vk->panic_nb);
> +
> + snprintf(name, sizeof(name), KBUILD_MODNAME ".%d_ttyVK", id);
> + err = bcm_vk_tty_init(vk, name);
> + if (err)
> + goto err_unregister_panic_notifier;
> +
> + /*
> + * lets trigger an auto download. We don't want to do it serially here
> + * because at probing time, it is not supposed to block for a long time.
> + */
> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
> + if (auto_load) {
> + if ((boot_status & BOOT_STATE_MASK) == BROM_RUNNING) {
> + if (bcm_vk_trigger_autoload(vk))
> + goto err_bcm_vk_tty_exit;
> + } else {
> + dev_info(dev,
> + "Auto-load skipped - BROM not in proper state (0x%x)\n",
> + boot_status);
> + }
> + }
> +
> + /* enable hb */
> + bcm_vk_hb_init(vk);
> +
> + dev_info(dev, "BCM-VK:%u created, 0x%p\n", id, vk);
> +
> + return 0;
> +
> +err_bcm_vk_tty_exit:
> + bcm_vk_tty_exit(vk);
> +
> +err_unregister_panic_notifier:
> + atomic_notifier_chain_unregister(&panic_notifier_list,
> + &vk->panic_nb);
> +
> +err_misc_deregister:
> + misc_deregister(misc_device);
> +
> +err_kfree_name:
> + kfree(misc_device->name);
> + misc_device->name = NULL;
> +
> +err_ida_remove:
> + ida_simple_remove(&bcm_vk_ida, id);
> +
> +err_irq:
> + for (i = 0; i < vk->num_irqs; i++)
> + devm_free_irq(dev, pci_irq_vector(pdev, i), vk);
> +
> + pci_disable_msix(pdev);
> + pci_disable_msi(pdev);
> +
> +err_iounmap:
> + for (i = 0; i < MAX_BAR; i++) {
> + if (vk->bar[i])
> + pci_iounmap(pdev, vk->bar[i]);
> + }
> + pci_release_regions(pdev);
> +
> +err_disable_pdev:
> + pci_disable_device(pdev);
> +
> + return err;
> +}
> +
> +void bcm_vk_release_data(struct kref *kref)
> +{
> + struct bcm_vk *vk = container_of(kref, struct bcm_vk, kref);
> +
> + /* use raw print, as dev is gone */
> + pr_info("BCM-VK:%d release data 0x%p\n", vk->misc_devid, vk);
> + kfree(vk);
> +}
> +
> +static void bcm_vk_remove(struct pci_dev *pdev)
> +{
> + int i;
> + struct bcm_vk *vk = pci_get_drvdata(pdev);
> + struct miscdevice *misc_device = &vk->miscdev;
> +
> + bcm_vk_hb_deinit(vk);
> + bcm_vk_tty_exit(vk);
> +
> + /* unregister panic notifier */
> + atomic_notifier_chain_unregister(&panic_notifier_list,
> + &vk->panic_nb);
> +
> + bcm_vk_msg_remove(vk);
> +
> + if (vk->tdma_vaddr)
> + dma_free_coherent(&pdev->dev, nr_scratch_pages * PAGE_SIZE,
> + vk->tdma_vaddr, vk->tdma_addr);
> +
> + /* remove if name is set which means misc dev registered */
> + if (misc_device->name) {
> + misc_deregister(misc_device);
> + kfree(misc_device->name);
> + ida_simple_remove(&bcm_vk_ida, vk->misc_devid);
> + }
> + for (i = 0; i < vk->num_irqs; i++)
> + devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), vk);
> +
> + pci_disable_msix(pdev);
> + pci_disable_msi(pdev);
> +
> + cancel_work_sync(&vk->wq_work);
> + destroy_workqueue(vk->wq_thread);
> +
> + for (i = 0; i < MAX_BAR; i++) {
> + if (vk->bar[i])
> + pci_iounmap(pdev, vk->bar[i]);
> + }
> +
> + dev_info(&pdev->dev, "BCM-VK:%d released\n", vk->misc_devid);
> +
> + pci_release_regions(pdev);
> + pci_disable_device(pdev);
> +
> + kref_put(&vk->kref, bcm_vk_release_data);
> +}
> +
> +static const struct pci_device_id bcm_vk_ids[] = {
> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VALKYRIE), },
> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VIPER), },
> + { }
> +};
> +MODULE_DEVICE_TABLE(pci, bcm_vk_ids);
> +
> +static struct pci_driver pci_driver = {
> + .name = DRV_MODULE_NAME,
> + .id_table = bcm_vk_ids,
> + .probe = bcm_vk_probe,
> + .remove = bcm_vk_remove,
> +};
> +module_pci_driver(pci_driver);
> +
> +MODULE_DESCRIPTION("Broadcom VK Host Driver");
> +MODULE_AUTHOR("Scott Branden <[email protected]>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION("1.0");
> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
> new file mode 100644
> index 000000000000..651fba2149e2
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
> @@ -0,0 +1,1359 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/fs.h>
> +#include <linux/hash.h>
> +#include <linux/interrupt.h>
> +#include <linux/list.h>
> +#include <linux/sizes.h>
> +#include <linux/spinlock.h>
> +#include <linux/timer.h>
> +
> +#include "bcm_vk.h"
> +#include "bcm_vk_msg.h"
> +#include "bcm_vk_sg.h"
> +
> +#if defined(CONFIG_BCM_VK_H2VK_VERIFY_AND_RETRY)
> +/*
> + * Turn on the following to verify the data passed down to VK is good, and
> + * if not, do retry. This is a debug/workaround on FPGA PCIe timing issues
> + * but may be found useful for debugging other PCIe hardware issues.
> + */
> +static void bcm_vk_h2vk_verify_idx(struct device *dev,
> + const char *tag,
> + volatile uint32_t *idx,
> + uint32_t expected)
> +{
> + unsigned int count = 0;
> +
> + while (*idx != expected) {
> + count++;
> + dev_err(dev, "[%d] %s exp %d idx %d\n",
> + count, tag, expected, *idx);
> +
> + /* write again */
> + *idx = expected;
> + }
> +}
> +
> +static void bcm_vk_h2vk_verify_blk(struct device *dev,
> + const struct vk_msg_blk *src,
> + volatile struct vk_msg_blk *dst)
> +
> +{
> + struct vk_msg_blk rd_bck;
> + unsigned int count = 0;
> +
> + rd_bck = *dst;
> + while (memcmp(&rd_bck, src, sizeof(rd_bck)) != 0) {
> + count++;
> + dev_err(dev,
> + "[%d]Src Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
> + count,
> + src->function_id,
> + src->size,
> + src->queue_id,
> + src->msg_id,
> + src->context_id,
> + src->args[0],
> + src->args[1]);
> + dev_err(dev,
> + "[%d]Rdb Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
> + count,
> + rd_bck.function_id,
> + rd_bck.size,
> + rd_bck.queue_id,
> + rd_bck.msg_id,
> + rd_bck.context_id,
> + rd_bck.args[0],
> + rd_bck.args[1]);
> +
> + *dst = *src;
> + rd_bck = *dst;
> + }
> +}
> +#else
> +static void bcm_vk_h2vk_verify_idx(struct device __always_unused *dev,
> + const char __always_unused *tag,
> + volatile uint32_t __always_unused *idx,
> + uint32_t __always_unused expected)
> +{
> +}
> +
> +static void bcm_vk_h2vk_verify_blk
> + (struct device __always_unused *dev,
> + const struct vk_msg_blk __always_unused *src,
> + volatile struct vk_msg_blk __always_unused *dst)
> +
> +{
> +}
> +#endif
> +
> +#if defined(CONFIG_BCM_VK_QSTATS)
> +
> +/* Use default value of 20000 rd/wr per update */
> +#if !defined(BCM_VK_QSTATS_ACC_CNT)
> +#define BCM_VK_QSTATS_ACC_CNT 20000
> +#endif
> +
> +void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
> + struct bcm_vk_qstats *qstats, uint32_t occupancy)
> +{
> + struct bcm_vk_qs_cnts *qcnts = &qstats->qcnts;
> +
> + if (occupancy > qcnts->max_occ) {
> + qcnts->max_occ = occupancy;
> + if (occupancy > qcnts->max_abs)
> + qcnts->max_abs = occupancy;
> + }
> +
> + qcnts->acc_sum += occupancy;
> + if (++qcnts->cnt >= BCM_VK_QSTATS_ACC_CNT) {
> + /* log average and clear counters */
> + dev_info(&vk->pdev->dev,
> + "%s[%d]: Max: [%3d/%3d] Acc %d num %d, Aver %d\n",
> + tag, qstats->q_num,
> + qcnts->max_occ, qcnts->max_abs,
> + qcnts->acc_sum,
> + qcnts->cnt,
> + qcnts->acc_sum / qcnts->cnt);
> +
> + qcnts->cnt = 0;
> + qcnts->max_occ = 0;
> + qcnts->acc_sum = 0;
> + }
> +}
> +#endif
> +
> +/* number of retries when enqueue message fails before returning EAGAIN */
> +#define BCM_VK_H2VK_ENQ_RETRY 10
> +#define BCM_VK_H2VK_ENQ_RETRY_DELAY_MS 50
> +
> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk)
> +{
> + return (!!atomic_read(&vk->msgq_inited));
> +}
> +
> +static void bcm_vk_set_host_alert(struct bcm_vk *vk, uint32_t bit_mask)
> +{
> + struct bcm_vk_alert *alert = &vk->host_alert;
> + unsigned long flags;
> +
> + /* use irqsave version as this maybe called inside timer interrupt */
> + spin_lock_irqsave(&vk->host_alert_lock, flags);
> + alert->notfs |= bit_mask;
> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
> +
> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
> + queue_work(vk->wq_thread, &vk->wq_work);
> +}
> +
> +/*
> + * Heartbeat related defines
> + * The heartbeat from host is a last resort. If stuck condition happens
> + * on the card, firmware is supposed to detect it. Therefore, the heartbeat
> + * values used will be more relaxed on the driver, which need to be bigger
> + * than the watchdog timeout on the card.
> + */
> +#define BCM_VK_HB_TIMER_S 3
> +#define BCM_VK_HB_TIMER_VALUE (BCM_VK_HB_TIMER_S * HZ)
> +#define BCM_VK_HB_LOST_MAX 4
> +
> +static void bcm_vk_hb_poll(struct timer_list *t)
> +{
> + uint32_t uptime_s;
> + struct bcm_vk_hb_ctrl *hb = container_of(t, struct bcm_vk_hb_ctrl,
> + timer);
> + struct bcm_vk *vk = container_of(hb, struct bcm_vk, hb_ctrl);
> +
> + if (bcm_vk_drv_access_ok(vk)) {
> + /* read uptime from register and compare */
> + uptime_s = vkread32(vk, BAR_0, BAR_OS_UPTIME);
> +
> + if (uptime_s == hb->last_uptime)
> + hb->lost_cnt++;
> +
> + dev_dbg(&vk->pdev->dev, "Last uptime %d current %d, lost %d\n",
> + hb->last_uptime, uptime_s, hb->lost_cnt);
> +
> + /*
> + * if the interface goes down without any activity, a value
> + * of 0xFFFFFFFF will be continuously read, and the detection
> + * will be happened eventually.
> + */
> + hb->last_uptime = uptime_s;
> + } else {
> + /* reset heart beat lost cnt */
> + hb->lost_cnt = 0;
> + }
> +
> + /* next, check if heartbeat exceeds limit */
> + if (hb->lost_cnt > BCM_VK_HB_LOST_MAX) {
> + dev_err(&vk->pdev->dev, "Heartbeat Misses %d times, %d s!\n",
> + BCM_VK_HB_LOST_MAX,
> + BCM_VK_HB_LOST_MAX * BCM_VK_HB_TIMER_S);
> +
> + bcm_vk_blk_drv_access(vk);
> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_HB_FAIL);
> + }
> + /* re-arm timer */
> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
> +}
> +
> +void bcm_vk_hb_init(struct bcm_vk *vk)
> +{
> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
> +
> + timer_setup(&hb->timer, bcm_vk_hb_poll, 0);
> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
> +}
> +
> +void bcm_vk_hb_deinit(struct bcm_vk *vk)
> +{
> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
> +
> + del_timer(&hb->timer);
> +}
> +
> +static void bcm_vk_msgid_bitmap_clear(struct bcm_vk *vk,
> + unsigned int start,
> + unsigned int nbits)
> +{
> + spin_lock(&vk->msg_id_lock);
> + bitmap_clear(vk->bmap, start, nbits);
> + spin_unlock(&vk->msg_id_lock);
> +}
> +
> +/*
> + * allocate a ctx per file struct
> + */
> +static struct bcm_vk_ctx *bcm_vk_get_ctx(struct bcm_vk *vk,
> + struct task_struct *ppid)
> +{
> + uint32_t i;
> + struct bcm_vk_ctx *ctx = NULL;
> + const pid_t pid = task_pid_nr(ppid);
> + uint32_t hash_idx = hash_32(pid, VK_PID_HT_SHIFT_BIT);
> +
> + spin_lock(&vk->ctx_lock);
> +
> + /* check if it is in reset, if so, don't allow */
> + if (vk->reset_ppid) {
> + dev_err(&vk->pdev->dev,
> + "No context allowed during reset by pid %d\n",
> + task_pid_nr(vk->reset_ppid));
> +
> + goto in_reset_exit;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
> + if (!vk->ctx[i].in_use) {
> + vk->ctx[i].in_use = true;
> + ctx = &vk->ctx[i];
> + break;
> + }
> + }
> +
> + if (!ctx) {
> + dev_err(&vk->pdev->dev, "All context in use\n");
> +
> + goto all_in_use_exit;
> + }
> +
> + /* set the pid and insert it to hash table */
> + ctx->ppid = ppid;
> + ctx->hash_idx = hash_idx;
> + list_add_tail(&ctx->node, &vk->pid_ht[hash_idx].head);
> +
> + /* increase kref */
> + kref_get(&vk->kref);
> +
> + /* clear counter */
> + ctx->pend_cnt = 0;
> +all_in_use_exit:
> +in_reset_exit:
> + spin_unlock(&vk->ctx_lock);
> +
> + return ctx;
> +}
> +
> +static uint16_t bcm_vk_get_msg_id(struct bcm_vk *vk)
> +{
> + uint16_t rc = VK_MSG_ID_OVERFLOW;
> + uint16_t test_bit_count = 0;
> +
> + spin_lock(&vk->msg_id_lock);
> + while (test_bit_count < VK_MSG_ID_BITMAP_SIZE) {
> + vk->msg_id++;
> + vk->msg_id = vk->msg_id & VK_MSG_ID_BITMAP_MASK;
> + if (test_bit(vk->msg_id, vk->bmap)) {
> + test_bit_count++;
> + continue;
> + }
> + rc = vk->msg_id;
> + bitmap_set(vk->bmap, vk->msg_id, 1);
> + break;
> + }
> + spin_unlock(&vk->msg_id_lock);
> +
> + return rc;
> +}
> +
> +static int bcm_vk_free_ctx(struct bcm_vk *vk, struct bcm_vk_ctx *ctx)
> +{
> + uint32_t idx;
> + uint32_t hash_idx;
> + pid_t pid;
> + struct bcm_vk_ctx *entry;
> + int count = 0;
> +
> + if (ctx == NULL) {
> + dev_err(&vk->pdev->dev, "NULL context detected\n");
> + return -EINVAL;
> + }
> + idx = ctx->idx;
> + pid = task_pid_nr(ctx->ppid);
> +
> + spin_lock(&vk->ctx_lock);
> +
> + if (!vk->ctx[idx].in_use) {
> + dev_err(&vk->pdev->dev, "context[%d] not in use!\n", idx);
> + } else {
> + vk->ctx[idx].in_use = false;
> + vk->ctx[idx].miscdev = NULL;
> +
> + /* Remove it from hash list and see if it is the last one. */
> + list_del(&ctx->node);
> + hash_idx = ctx->hash_idx;
> + list_for_each_entry(entry, &vk->pid_ht[hash_idx].head, node) {
> + if (task_pid_nr(entry->ppid) == pid)
> + count++;
> + }
> + }
> +
> + spin_unlock(&vk->ctx_lock);
> +
> + return count;
> +}
> +
> +static void bcm_vk_free_wkent(struct device *dev, struct bcm_vk_wkent *entry)
> +{
> + bcm_vk_sg_free(dev, entry->dma, VK_DMA_MAX_ADDRS);
> +
> + kfree(entry->vk2h_msg);
> + kfree(entry);
> +}
> +
> +static void bcm_vk_drain_all_pend(struct device *dev,
> + struct bcm_vk_msg_chan *chan,
> + struct bcm_vk_ctx *ctx)
> +{
> + uint32_t num;
> + struct bcm_vk_wkent *entry, *tmp;
> + struct bcm_vk *vk;
> + struct list_head del_q;
> +
> + if (ctx)
> + vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
> +
> + INIT_LIST_HEAD(&del_q);
> + spin_lock(&chan->pendq_lock);
> + for (num = 0; num < chan->q_nr; num++) {
> + list_for_each_entry_safe(entry, tmp, &chan->pendq[num], node) {
> + if (ctx == NULL) {
> + list_del(&entry->node);
> + list_add_tail(&entry->node, &del_q);
> + } else if (entry->ctx->idx == ctx->idx) {
> + struct vk_msg_blk *msg;
> + int bit_set;
> + bool responded;
> +
> + /* if it is specific ctx, log for any stuck */
> + msg = entry->h2vk_msg;
> + bit_set = test_bit(msg->msg_id, vk->bmap);
> + responded = entry->vk2h_msg ? true : false;
> + dev_info(dev,
> + "Drained: fid %u size %u msg 0x%x(seq-%x) ctx 0x%x[fd-%d] args:[0x%x 0x%x] resp %s, bmap %d\n",
> + msg->function_id, msg->size,
> + msg->msg_id, entry->seq_num,
> + msg->context_id, entry->ctx->idx,
> + msg->args[0], msg->args[1],
> + responded ? "T" : "F", bit_set);
> + list_del(&entry->node);
> + list_add_tail(&entry->node, &del_q);
> + if (responded)
> + ctx->pend_cnt--;
> + else if (bit_set)
> + bcm_vk_msgid_bitmap_clear(vk,
> + msg->msg_id,
> + 1);
> + }
> + }
> + }
> + spin_unlock(&chan->pendq_lock);
> +
> + /* batch clean up */
> + num = 0;
> + list_for_each_entry_safe(entry, tmp, &del_q, node) {
> + list_del(&entry->node);
> + bcm_vk_free_wkent(dev, entry);
> + num++;
> + }
> + if (num)
> + dev_info(dev, "Total drained items %d\n", num);
> +}
> +
> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk)
> +{
> + uint32_t rdy_marker = 0;
> + uint32_t fw_status;
> +
> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
> +
> + if ((fw_status & VK_FWSTS_READY) == VK_FWSTS_READY)
> + rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
> +
> + return (rdy_marker == VK_BAR1_MSGQ_RDY_MARKER);
> +}
> +
> +/*
> + * Function to sync up the messages queue info that is provided by BAR1
> + */
> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync)
> +{
> + struct bcm_vk_msgq *msgq = NULL;
> + struct device *dev = &vk->pdev->dev;
> + uint32_t msgq_off;
> + uint32_t num_q;
> + struct bcm_vk_msg_chan *chan_list[] = {&vk->h2vk_msg_chan,
> + &vk->vk2h_msg_chan};
> + struct bcm_vk_msg_chan *chan = NULL;
> + int i, j;
> + int ret = 0;
> +
> + /*
> + * If the driver is loaded at startup where vk OS is not up yet,
> + * the msgq-info may not be available until a later time. In
> + * this case, we skip and the sync function is supposed to be
> + * called again.
> + */
> + if (!bcm_vk_msgq_marker_valid(vk)) {
> + dev_info(dev, "BAR1 msgq marker not initialized.\n");
> + return ret;

This is "return 0;" which seems wrong.

> + }
> +
> + msgq_off = vkread32(vk, BAR_1, VK_BAR1_MSGQ_CTRL_OFF);
> +
> + /* each side is always half the total */
> + num_q = vk->h2vk_msg_chan.q_nr = vk->vk2h_msg_chan.q_nr =
> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_NR) / 2;
> +
> + /* first msgq location */
> + msgq = (struct bcm_vk_msgq *)(vk->bar[BAR_1] + msgq_off);
> +
> + /*
> + * if this function is called when it is already inited,
> + * something is wrong
> + */
> + if (bcm_vk_drv_access_ok(vk) && (!force_sync)) {

Delete extra parens.

> + dev_err(dev, "Msgq info already in sync\n");
> + ret = -EPERM;
> + goto already_inited;

Please do a direct "return -EPERM;"

> + }
> +
> + for (i = 0; i < ARRAY_SIZE(chan_list); i++) {
> + chan = chan_list[i];
> + memset(chan->sync_qinfo, 0, sizeof(chan->sync_qinfo));
> +
> + for (j = 0; j < num_q; j++) {
> + chan->msgq[j] = msgq;
> +
> + dev_info(dev,
> + "MsgQ[%d] type %d num %d, @ 0x%x, rd_idx %d wr_idx %d, size %d, nxt 0x%x\n",
> + j,
> + chan->msgq[j]->type,
> + chan->msgq[j]->num,
> + chan->msgq[j]->start,
> + chan->msgq[j]->rd_idx,
> + chan->msgq[j]->wr_idx,
> + chan->msgq[j]->size,
> + chan->msgq[j]->nxt);

It would be shorter to replace chan->msgq[j]->type with msgq->type.

> +
> + /* formulate and record static info */
> + chan->sync_qinfo[j].q_start =
> + vk->bar[BAR_1] + chan->msgq[j]->start;

Maybe set "qinfo = &chan->sync_qinfo[j];" at the start of the loop so
that these fit on one line.

qinfo->q_start = vk->bar[BAR_1] + msgq->start;

> + chan->sync_qinfo[j].q_size = chan->msgq[j]->size;
> + /* set low threshold as 50% or 1/2 */
> + chan->sync_qinfo[j].q_low =
> + chan->sync_qinfo[j].q_size >> 1;
> + chan->sync_qinfo[j].q_mask =
> + chan->sync_qinfo[j].q_size - 1;
> +
> + msgq = (struct bcm_vk_msgq *)
> + ((char *)msgq + sizeof(*msgq) + msgq->nxt);
> +
> + rmb(); /* do a read mb to guarantee */
> + }
> + }
> + atomic_set(&vk->msgq_inited, 1);
> +
> +already_inited:
> + return ret;
> +}
> +
> +static int bcm_vk_msg_chan_init(struct bcm_vk_msg_chan *chan)
> +{
> + int rc = 0;
> + uint32_t i;
> +
> + mutex_init(&chan->msgq_mutex);
> + spin_lock_init(&chan->pendq_lock);
> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
> + INIT_LIST_HEAD(&chan->pendq[i]);
> +#if defined(CONFIG_BCM_VK_QSTATS)
> + chan->qstats[i].q_num = i;
> +#endif
> + }
> +
> + return rc;


return 0;

> +}
> +
> +static void bcm_vk_append_pendq(struct bcm_vk_msg_chan *chan, uint16_t q_num,
> + struct bcm_vk_wkent *entry)
> +{
> + spin_lock(&chan->pendq_lock);
> + list_add_tail(&entry->node, &chan->pendq[q_num]);
> + if (entry->vk2h_msg)
> + entry->ctx->pend_cnt++;
> + spin_unlock(&chan->pendq_lock);
> +}
> +
> +static uint32_t bcm_vk_append_ib_sgl(struct bcm_vk *vk,
> + struct bcm_vk_wkent *entry,
> + struct _vk_data *data,
> + unsigned int num_planes)
> +{
> + unsigned int i;
> + unsigned int item_cnt = 0;
> + struct device *dev = &vk->pdev->dev;
> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
> + struct vk_msg_blk *msg = &entry->h2vk_msg[0];
> + struct bcm_vk_msgq *msgq;
> + struct bcm_vk_sync_qinfo *qinfo;
> + uint32_t ib_sgl_size = 0;
> + uint8_t *buf = (uint8_t *)&entry->h2vk_msg[entry->h2vk_blks];
> + uint32_t avail;
> +
> + /* check if high watermark is hit, and if so, skip */
> + msgq = chan->msgq[msg->queue_id];
> + qinfo = &chan->sync_qinfo[msg->queue_id];
> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
> + if (avail < qinfo->q_low) {
> + dev_dbg(dev, "Skip inserting inband SGL, [0x%x/0x%x]\n",
> + avail, qinfo->q_size);
> + return ib_sgl_size;

return 0;

> + }
> +
> + for (i = 0; i < num_planes; i++) {
> + if (data[i].address &&
> + (ib_sgl_size + data[i].size) <= vk->ib_sgl_size) {
> +
> + item_cnt++;
> + memcpy(buf, entry->dma[i].sglist, data[i].size);
> + ib_sgl_size += data[i].size;
> + buf += data[i].size;
> + }
> + }
> +
> + dev_dbg(dev, "Num %u sgl items appended, size 0x%x, room 0x%x\n",
> + item_cnt, ib_sgl_size, vk->ib_sgl_size);
> +
> + /* round up size */
> + ib_sgl_size = (ib_sgl_size + VK_MSGQ_BLK_SIZE - 1)
> + >> VK_MSGQ_BLK_SZ_SHIFT;
> +
> + return ib_sgl_size;
> +}
> +
> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num,
> + uint32_t db_val)
> +{
> + /* press door bell based on q_num */
> + vkwrite32(vk,
> + db_val,
> + BAR_0,
> + VK_BAR0_REGSEG_DB_BASE + q_num * VK_BAR0_REGSEG_DB_REG_GAP);
> +}
> +
> +static int bcm_h2vk_msg_enqueue(struct bcm_vk *vk, struct bcm_vk_wkent *entry)
> +{
> + static uint32_t seq_num;
> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
> + struct device *dev = &vk->pdev->dev;
> + struct vk_msg_blk *src = &entry->h2vk_msg[0];
> +
> + volatile struct vk_msg_blk *dst;
> + struct bcm_vk_msgq *msgq;
> + struct bcm_vk_sync_qinfo *qinfo;
> + uint32_t q_num = src->queue_id;
> + uint32_t wr_idx; /* local copy */
> + uint32_t i;
> + uint32_t avail;
> + uint32_t retry;
> +
> + if (entry->h2vk_blks != src->size + 1) {
> + dev_err(dev, "number of blks %d not matching %d MsgId[0x%x]: func %d ctx 0x%x\n",
> + entry->h2vk_blks,
> + src->size + 1,
> + src->msg_id,
> + src->function_id,
> + src->context_id);
> + return -EMSGSIZE;
> + }
> +
> + msgq = chan->msgq[q_num];
> + qinfo = &chan->sync_qinfo[q_num];
> +
> + rmb(); /* start with a read barrier */
> + mutex_lock(&chan->msgq_mutex);
> +
> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
> +
> +#if defined(CONFIG_BCM_VK_QSTATS)
> + bcm_vk_update_qstats(vk, "h2vk", &chan->qstats[q_num],
> + qinfo->q_size - avail);
> +#endif
> + /* if not enough space, return EAGAIN and let app handles it */
> + retry = 0;
> + while ((avail < entry->h2vk_blks)
> + && (retry++ < BCM_VK_H2VK_ENQ_RETRY)) {
> + mutex_unlock(&chan->msgq_mutex);
> +
> + msleep(BCM_VK_H2VK_ENQ_RETRY_DELAY_MS);
> + mutex_lock(&chan->msgq_mutex);
> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
> + }
> + if (retry > BCM_VK_H2VK_ENQ_RETRY) {
> + mutex_unlock(&chan->msgq_mutex);
> + return -EAGAIN;
> + }
> +
> + /* at this point, mutex is taken and there is enough space */
> + entry->seq_num = seq_num++; /* update debug seq number */
> + wr_idx = msgq->wr_idx;
> +
> + if (wr_idx >= qinfo->q_size) {
> + dev_crit(dev, "Invalid wr_idx 0x%x => max 0x%x!",
> + wr_idx, qinfo->q_size);
> + bcm_vk_blk_drv_access(vk);
> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_PCIE_DWN);
> + goto idx_err;
> + }
> +
> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
> + for (i = 0; i < entry->h2vk_blks; i++) {
> + *dst = *src;
> +
> + bcm_vk_h2vk_verify_blk(dev, src, dst);
> +
> + src++;
> + wr_idx = VK_MSGQ_INC(qinfo, wr_idx, 1);
> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
> + }
> +
> + /* flush the write pointer */
> + msgq->wr_idx = wr_idx;
> + wmb(); /* flush */
> +
> + bcm_vk_h2vk_verify_idx(dev, "wr_idx", &msgq->wr_idx, wr_idx);
> +
> + /* log new info for debugging */
> + dev_dbg(dev,
> + "MsgQ[%d] [Rd Wr] = [%d %d] blks inserted %d - Q = [u-%d a-%d]/%d\n",
> + msgq->num,
> + msgq->rd_idx, msgq->wr_idx, entry->h2vk_blks,
> + VK_MSGQ_OCCUPIED(msgq, qinfo),
> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
> + msgq->size);
> + /*
> + * press door bell based on queue number. 1 is added to the wr_idx
> + * to avoid the value of 0 appearing on the VK side to distinguish
> + * from initial value.
> + */
> + bcm_h2vk_doorbell(vk, q_num, wr_idx + 1);
> +idx_err:
> + mutex_unlock(&chan->msgq_mutex);
> + return 0;
> +}
> +
> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type,
> + pid_t pid)
> +{
> + int rc = 0;
> + struct bcm_vk_wkent *entry;
> + struct device *dev = &vk->pdev->dev;
> +
> + /*
> + * check if the marker is still good. Sometimes, the PCIe interface may
> + * have gone done, and if so and we ship down thing based on broken
> + * values, kernel may panic.
> + */
> + if (!bcm_vk_msgq_marker_valid(vk)) {
> + dev_info(dev, "PCIe comm chan - invalid marker (0x%x)!\n",
> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY));
> + return -EINVAL;
> + }
> +
> + entry = kzalloc(sizeof(struct bcm_vk_wkent) +
> + sizeof(struct vk_msg_blk), GFP_KERNEL);
> + if (!entry)
> + return -ENOMEM;
> +
> + /* just fill up non-zero data */
> + entry->h2vk_msg[0].function_id = VK_FID_SHUTDOWN;
> + entry->h2vk_msg[0].queue_id = 0; /* use highest queue */
> + entry->h2vk_blks = 1; /* always 1 block */
> +
> + entry->h2vk_msg[0].args[0] = shut_type;
> + entry->h2vk_msg[0].args[1] = pid;
> +
> + rc = bcm_h2vk_msg_enqueue(vk, entry);
> + if (rc)
> + dev_err(dev,
> + "Sending shutdown message to q %d for pid %d fails.\n",
> + entry->h2vk_msg[0].queue_id, pid);
> +
> + kfree(entry);
> +
> + return rc;
> +}
> +
> +int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
> +{
> + int rc = 0;
> + pid_t pid = task_pid_nr(ppid);
> + struct device *dev = &vk->pdev->dev;
> +
> + /*
> + * don't send down or do anything if message queue is not initialized
> + * and if it is the reset session, clear it.
> + */
> + if (!bcm_vk_drv_access_ok(vk)) {
> +
> + if (vk->reset_ppid == ppid)
> + vk->reset_ppid = NULL;
> + return -EPERM;
> + }
> +
> + dev_dbg(dev, "No more sessions, shut down pid %d\n", pid);
> +
> + /* only need to do it if it is not the reset process */
> + if (vk->reset_ppid != ppid)
> + rc = bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_PID, pid);
> + else
> + /* reset the pointer if it is exiting last session */
> + vk->reset_ppid = NULL;
> +
> + return rc;
> +}
> +
> +static struct bcm_vk_wkent *bcm_vk_find_pending(struct bcm_vk *vk,
> + struct bcm_vk_msg_chan *chan,
> + uint16_t q_num,
> + uint16_t msg_id)
> +{
> + bool found = false;
> + struct bcm_vk_wkent *entry;
> +
> + spin_lock(&chan->pendq_lock);
> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
> +
> + if (entry->h2vk_msg[0].msg_id == msg_id) {
> + list_del(&entry->node);
> + found = true;
> + bcm_vk_msgid_bitmap_clear(vk, msg_id, 1);
> + break;
> + }
> + }
> + spin_unlock(&chan->pendq_lock);
> + return ((found) ? entry : NULL);
> +}
> +
> +static uint32_t bcm_vk2h_msg_dequeue(struct bcm_vk *vk)
> +{
> + struct device *dev = &vk->pdev->dev;
> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
> + struct vk_msg_blk *data;
> + volatile struct vk_msg_blk *src;
> + struct vk_msg_blk *dst;
> + struct bcm_vk_msgq *msgq;
> + struct bcm_vk_sync_qinfo *qinfo;
> + struct bcm_vk_wkent *entry;
> + uint32_t rd_idx;
> + uint32_t q_num, j;
> + uint32_t num_blks;
> + uint32_t total = 0;
> +
> + /*
> + * drain all the messages from the queues, and find its pending
> + * entry in the h2vk queue, based on msg_id & q_num, and move the
> + * entry to the vk2h pending queue, waiting for user space
> + * program to extract
> + */
> + mutex_lock(&chan->msgq_mutex);
> + rmb(); /* start with a read barrier */
> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
> + msgq = chan->msgq[q_num];
> + qinfo = &chan->sync_qinfo[q_num];
> +
> + while (!VK_MSGQ_EMPTY(msgq)) {
> +
> + /*
> + * Make a local copy and get pointer to src blk
> + * The rd_idx is masked before getting the pointer to
> + * avoid out of bound access in case the interface goes
> + * down. It will end up pointing to the last block in
> + * the buffer, but subsequent src->size check would be
> + * able to catch this.
> + */
> + rd_idx = msgq->rd_idx;
> + src = VK_MSGQ_BLK_ADDR(qinfo,
> + rd_idx & VK_MSGQ_SIZE_MASK(qinfo));
> +
> + if ((rd_idx >= qinfo->q_size)
> + || (src->size > (qinfo->q_size - 1))) {
> + dev_crit(dev,
> + "Invalid rd_idx 0x%x or size 0x%x => max 0x%x!",
> + rd_idx, src->size, qinfo->q_size);
> + bcm_vk_blk_drv_access(vk);
> + bcm_vk_set_host_alert(
> + vk, ERR_LOG_HOST_PCIE_DWN);
> + goto idx_err;
> + }
> +
> +#if defined(CONFIG_BCM_VK_QSTATS)
> + bcm_vk_update_qstats(vk, "vk2h", &chan->qstats[q_num],
> + VK_MSGQ_OCCUPIED(msgq, qinfo));
> +#endif
> + num_blks = src->size + 1;
> + data = kzalloc(num_blks * VK_MSGQ_BLK_SIZE, GFP_KERNEL);
> + if (data) {
> + /* copy messages and linearize it */
> + dst = data;
> + for (j = 0; j < num_blks; j++) {
> + *dst = *src;
> +
> + dst++;
> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, 1);
> + src = VK_MSGQ_BLK_ADDR(qinfo, rd_idx);
> + }
> + total++;
> + } else {
> + dev_crit(dev, "Error allocating memory\n");
> + /* just keep draining..... */
> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, num_blks);
> + }
> +
> + /* flush rd pointer after a message is dequeued */
> + msgq->rd_idx = rd_idx;
> + mb(); /* do both rd/wr as we are extracting data out */
> +
> + bcm_vk_h2vk_verify_idx(dev, "rd_idx",
> + &msgq->rd_idx, rd_idx);
> +
> + /* log new info for debugging */
> + dev_dbg(dev,
> + "MsgQ[%d] [Rd Wr] = [%d %d] blks extracted %d - Q = [u-%d a-%d]/%d\n",
> + msgq->num,
> + msgq->rd_idx, msgq->wr_idx, num_blks,
> + VK_MSGQ_OCCUPIED(msgq, qinfo),
> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
> + msgq->size);
> +
> + /*
> + * No need to search if it is an autonomous one-way
> + * message from driver, as these messages do not bear
> + * a h2vk pending item. Currently, only the shutdown
> + * message falls into this category.
> + */
> + if (data->function_id == VK_FID_SHUTDOWN) {
> + kfree(data);
> + continue;
> + }
> +
> + /* lookup original message in h2vk direction */
> + entry = bcm_vk_find_pending(vk,
> + &vk->h2vk_msg_chan,
> + q_num,
> + data->msg_id);
> +
> + /*
> + * if there is message to does not have prior send,
> + * this is the location to add here
> + */
> + if (entry) {
> + entry->vk2h_blks = num_blks;
> + entry->vk2h_msg = data;
> + bcm_vk_append_pendq(&vk->vk2h_msg_chan,
> + q_num, entry);
> +
> + } else {
> + dev_crit(dev,
> + "Could not find MsgId[0x%x] for resp func %d bmap %d\n",
> + data->msg_id, data->function_id,
> + test_bit(data->msg_id, vk->bmap));
> + kfree(data);
> + }
> +
> + }
> + }
> +idx_err:
> + mutex_unlock(&chan->msgq_mutex);
> + dev_dbg(dev, "total %d drained from queues\n", total);
> +
> + return total;
> +}
> +
> +/*
> + * deferred work queue for draining and auto download.
> + */
> +static void bcm_vk_wq_handler(struct work_struct *work)
> +{
> + struct bcm_vk *vk = container_of(work, struct bcm_vk, wq_work);
> + struct device *dev = &vk->pdev->dev;
> + uint32_t tot;
> +
> + /* check wq offload bit map to perform various operations */
> + if (test_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload)) {
> + /* clear bit right the way for notification */
> + clear_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload);
> + bcm_vk_handle_notf(vk);
> + }
> + if (test_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload)) {
> + bcm_vk_auto_load_all_images(vk);
> +
> + /*
> + * at the end of operation, clear AUTO bit and pending
> + * bit
> + */
> + clear_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
> + }
> +
> + /* next, try to drain */
> + tot = bcm_vk2h_msg_dequeue(vk);
> +
> + if (tot == 0)
> + dev_dbg(dev, "Spurious trigger for workqueue\n");
> +}
> +
> +/*
> + * init routine for all required data structures
> + */
> +static int bcm_vk_data_init(struct bcm_vk *vk)
> +{
> + int rc = 0;
> + int i;
> +
> + spin_lock_init(&vk->ctx_lock);
> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
> + vk->ctx[i].in_use = false;
> + vk->ctx[i].idx = i; /* self identity */
> + vk->ctx[i].miscdev = NULL;
> + }
> + spin_lock_init(&vk->msg_id_lock);
> + spin_lock_init(&vk->host_alert_lock);
> + vk->msg_id = 0;
> +
> + /* initialize hash table */
> + for (i = 0; i < VK_PID_HT_SZ; i++)
> + INIT_LIST_HEAD(&vk->pid_ht[i].head);
> +
> + INIT_WORK(&vk->wq_work, bcm_vk_wq_handler);
> + return rc;

return 0;

> +}
> +
> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id)
> +{
> + struct bcm_vk *vk = dev_id;
> +
> + if (!bcm_vk_drv_access_ok(vk)) {
> + dev_err(&vk->pdev->dev,
> + "Interrupt %d received when msgq not inited\n", irq);
> + goto skip_schedule_work;
> + }
> +
> + queue_work(vk->wq_thread, &vk->wq_work);
> +
> +skip_schedule_work:
> + return IRQ_HANDLED;
> +}
> +
> +int bcm_vk_open(struct inode *inode, struct file *p_file)
> +{
> + struct bcm_vk_ctx *ctx;
> + struct miscdevice *miscdev = (struct miscdevice *)p_file->private_data;
> + struct bcm_vk *vk = container_of(miscdev, struct bcm_vk, miscdev);
> + struct device *dev = &vk->pdev->dev;
> + int rc = 0;
> +
> + /* get a context and set it up for file */
> + ctx = bcm_vk_get_ctx(vk, current);
> + if (!ctx) {
> + dev_err(dev, "Error allocating context\n");
> + rc = -ENOMEM;
> + } else {
> +
> + /*
> + * set up context and replace private data with context for
> + * other methods to use. Reason for the context is because
> + * it is allowed for multiple sessions to open the sysfs, and
> + * for each file open, when upper layer query the response,
> + * only those that are tied to a specific open should be
> + * returned. The context->idx will be used for such binding
> + */
> + ctx->miscdev = miscdev;
> + p_file->private_data = ctx;
> + dev_dbg(dev, "ctx_returned with idx %d, pid %d\n",
> + ctx->idx, task_pid_nr(ctx->ppid));
> + }
> + return rc;
> +}
> +
> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
> + loff_t *f_pos)
> +{
> + ssize_t rc = -ENOMSG;
> + struct bcm_vk_ctx *ctx = p_file->private_data;
> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
> + miscdev);
> + struct device *dev = &vk->pdev->dev;
> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
> + struct bcm_vk_wkent *entry = NULL;
> + uint32_t q_num;
> + uint32_t rsp_length;
> + bool found = false;
> +
> + if (!bcm_vk_drv_access_ok(vk))
> + return -EPERM;
> +
> + dev_dbg(dev, "Buf count %ld\n", count);
> + found = false;
> +
> + /*
> + * search through the pendq on the vk2h chan, and return only those
> + * that belongs to the same context. Search is always from the high to
> + * the low priority queues
> + */
> + spin_lock(&chan->pendq_lock);
> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
> + if (entry->ctx->idx == ctx->idx) {
> + if (count >=
> + (entry->vk2h_blks * VK_MSGQ_BLK_SIZE)) {
> + list_del(&entry->node);
> + ctx->pend_cnt--;
> + found = true;
> + } else {
> + /* buffer not big enough */
> + rc = -EMSGSIZE;
> + }
> + goto bcm_vk_read_loop_exit;
> + }
> + }
> + }
> + bcm_vk_read_loop_exit:
> + spin_unlock(&chan->pendq_lock);
> +
> + if (found) {
> + /* retrieve the passed down msg_id */
> + entry->vk2h_msg[0].msg_id = entry->usr_msg_id;
> + rsp_length = entry->vk2h_blks * VK_MSGQ_BLK_SIZE;
> + if (copy_to_user(buf, entry->vk2h_msg, rsp_length) == 0)
> + rc = rsp_length;

The error code should be -EFAULT if this message fails (vs -ENOMSG).


> +
> + bcm_vk_free_wkent(dev, entry);
> + } else if (rc == -EMSGSIZE) {
> + struct vk_msg_blk tmp_msg = entry->vk2h_msg[0];
> +
> + /*
> + * in this case, return just the first block, so
> + * that app knows what size it is looking for.
> + */
> + tmp_msg.msg_id = entry->usr_msg_id;
> + tmp_msg.size = entry->vk2h_blks - 1;
> + if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) {
> + dev_err(dev, "Error return 1st block in -EMSGSIZE\n");
> + rc = -EFAULT;
> + }
> + }
> + return rc;
> +}
> +
> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
> + size_t count, loff_t *f_pos)
> +{
> + ssize_t rc = -EPERM;
> + struct bcm_vk_ctx *ctx = p_file->private_data;
> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
> + miscdev);
> + struct bcm_vk_msgq *msgq;
> + struct device *dev = &vk->pdev->dev;
> + struct bcm_vk_wkent *entry;
> + uint32_t sgl_extra_blks;
> +
> + if (!bcm_vk_drv_access_ok(vk))
> + return -EPERM;
> +
> + dev_dbg(dev, "Msg count %ld\n", count);
> +
> + /* first, do sanity check where count should be multiple of basic blk */
> + if (count & (VK_MSGQ_BLK_SIZE - 1)) {
> + dev_err(dev, "Failure with size %ld not multiple of %ld\n",
> + count, VK_MSGQ_BLK_SIZE);
> + rc = -EBADR;

#define EBADR 53 /* Invalid request descriptor */

This should just be -EINVAL;


> + goto bcm_vk_write_err;

These label names are too long. They don't need the function prefix.

> + }
> +
> + /* allocate the work entry + buffer for size count and inband sgl */
> + entry = kzalloc(sizeof(struct bcm_vk_wkent) + count + vk->ib_sgl_size,
> + GFP_KERNEL);
> + if (!entry) {
> + rc = -ENOMEM;
> + goto bcm_vk_write_err;
> + }
> +
> + /* now copy msg from user space, and then formulate the wk ent */
> + if (copy_from_user(&entry->h2vk_msg[0], buf, count))
> + goto bcm_vk_write_free_ent;

This is returning -EPERM instead of -EFAULT.

> +
> + entry->h2vk_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
> + entry->ctx = ctx;
> +
> + /* do a check on the blk size which could not exceed queue space */
> + msgq = vk->h2vk_msg_chan.msgq[entry->h2vk_msg[0].queue_id];
> + if (entry->h2vk_blks + (vk->ib_sgl_size >> VK_MSGQ_BLK_SZ_SHIFT)
> + > (msgq->size - 1)) {
> + dev_err(dev, "Blk size %d exceed max queue size allowed %d\n",
> + entry->h2vk_blks, msgq->size - 1);
> + rc = -EOVERFLOW;

This should probably just be -EINVAL as well.


> + goto bcm_vk_write_free_ent;
> + }
> +
> + /* Use internal message id */
> + entry->usr_msg_id = entry->h2vk_msg[0].msg_id;
> + rc = bcm_vk_get_msg_id(vk);
> + if (rc == VK_MSG_ID_OVERFLOW) {
> + dev_err(dev, "msg_id overflow\n");
> + rc = -EOVERFLOW;
> + goto bcm_vk_write_free_ent;
> + }
> + entry->h2vk_msg[0].msg_id = rc;
> +
> + dev_dbg(dev,
> + "Message ctx id %d, usr_msg_id 0x%x sent msg_id 0x%x\n",
> + ctx->idx, entry->usr_msg_id,
> + entry->h2vk_msg[0].msg_id);
> +
> + /* Convert any pointers to sg list */
> + if (entry->h2vk_msg[0].function_id == VK_FID_TRANS_BUF) {
> + unsigned int num_planes;
> + int dir;
> + struct _vk_data *data;
> +
> + /*
> + * check if we are in reset, if so, no buffer transfer is
> + * allowed and return error.
> + */
> + if (vk->reset_ppid) {
> + dev_dbg(dev, "No Transfer allowed during reset, pid %d.\n",
> + task_pid_nr(ctx->ppid));
> + rc = -EACCES;
> + goto bcm_vk_write_free_msgid;
> + }
> +
> + num_planes = entry->h2vk_msg[0].args[0] & VK_CMD_PLANES_MASK;
> + if ((entry->h2vk_msg[0].args[0] & VK_CMD_MASK)
> + == VK_CMD_DOWNLOAD) {
> + /* Memory transfer from vk device */

There are a bunch of pretty obvious comments that should just be
deleted.

> + dir = DMA_FROM_DEVICE;
> + } else {
> + /* Memory transfer to vk device */
> + dir = DMA_TO_DEVICE;
> + }
> +
> + /* Calculate vk_data location */
> + /* Go to end of the message */
> + data = (struct _vk_data *)
> + &(entry->h2vk_msg[entry->h2vk_msg[0].size + 1]);
> + /* Now back up to the start of the pointers */
> + data -= num_planes;
> +
> + /* Convert user addresses to DMA SG List */
> + rc = bcm_vk_sg_alloc(dev, entry->dma, dir, data, num_planes);
> + if (rc)
> + goto bcm_vk_write_free_msgid;
> +
> + /* try to embed inband sgl */
> + sgl_extra_blks = bcm_vk_append_ib_sgl(vk, entry, data,
> + num_planes);
> + entry->h2vk_blks += sgl_extra_blks;
> + entry->h2vk_msg[0].size += sgl_extra_blks;
> + }
> +
> + /*
> + * store wk ent to pending queue until a response is got. This needs to
^^
vk?

> + * be done before enqueuing the message
> + */
> + bcm_vk_append_pendq(&vk->h2vk_msg_chan, entry->h2vk_msg[0].queue_id,
> + entry);
> +
> + rc = bcm_h2vk_msg_enqueue(vk, entry);
> + if (rc) {
> + dev_err(dev, "Fail to enqueue msg to h2vk queue\n");
> +
> + /* remove message from pending list */
> + entry = bcm_vk_find_pending(vk,
> + &vk->h2vk_msg_chan,
> + entry->h2vk_msg[0].queue_id,
> + entry->h2vk_msg[0].msg_id);


Can bcm_vk_find_pending() return NULL here (leading to an Oops when we
do entry->h2vk_msg[0].msg_id and why can't we just use the previous
value of entry? The comment doesn't help me.

> + goto bcm_vk_write_free_ent;
> + }
> +
> + return count;
> +
> +bcm_vk_write_free_msgid:
> + bcm_vk_msgid_bitmap_clear(vk, entry->h2vk_msg[0].msg_id, 1);
> +bcm_vk_write_free_ent:
> + kfree(entry);
> +bcm_vk_write_err:
> + return rc;
> +}
> +
> +int bcm_vk_release(struct inode *inode, struct file *p_file)
> +{
> + int ret;
> + struct bcm_vk_ctx *ctx = p_file->private_data;
> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
> + struct device *dev = &vk->pdev->dev;
> + struct task_struct *ppid = ctx->ppid;
> + pid_t pid = task_pid_nr(ppid);
> +
> + dev_dbg(dev, "Draining with context idx %d pid %d\n",
> + ctx->idx, pid);
> +
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, ctx);
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, ctx);
> +
> + ret = bcm_vk_free_ctx(vk, ctx);

This returns a positive number.

> + if (ret == 0)
> + ret = bcm_vk_handle_last_sess(vk, ppid);
> +
> + /* free memory if it is the last reference */
> + kref_put(&vk->kref, bcm_vk_release_data);
> +
> + return ret;

Which we propogate to here. Probably this should just be "return 0;".

> +}
> +
> +int bcm_vk_msg_init(struct bcm_vk *vk)
> +{
> + struct device *dev = &vk->pdev->dev;
> + int err = 0;
> +
> + if (bcm_vk_data_init(vk)) {
> + dev_err(dev, "Error initializing internal data structures\n");
> + err = -EINVAL;
> + goto err_out;
> + }
> +
> + if (bcm_vk_msg_chan_init(&vk->h2vk_msg_chan) ||
> + bcm_vk_msg_chan_init(&vk->vk2h_msg_chan)) {
> + dev_err(dev, "Error initializing communication channel\n");
> + err = -EIO;
> + goto err_out;
> + }
> +
> + /* create dedicated workqueue */
> + vk->wq_thread = create_singlethread_workqueue(vk->miscdev.name);
> + if (!vk->wq_thread) {
> + dev_err(dev, "Fail to create workqueue thread\n");
> + err = -ENOMEM;
> + goto err_out;
> + }
> +
> + /* read msgq info */
> + if (bcm_vk_sync_msgq(vk, false)) {
> + dev_err(dev, "Error reading comm msg Q info\n");
> + err = -EIO;
> + goto err_out;
> + }
> +
> +err_out:
> + return err;

This should probably clean up the work queue on error?

> +}
> +
> +void bcm_vk_msg_remove(struct bcm_vk *vk)
> +{
> + bcm_vk_blk_drv_access(vk);
> +
> + /* drain all pending items */
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
> +}
> +
> +void bcm_vk_trigger_reset(struct bcm_vk *vk)
> +{
> + uint32_t i;
> + u32 value;
> +
> + /* clean up before pressing the door bell */
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
> + vkwrite32(vk, 0, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
> + /* make tag '\0' terminated */
> + vkwrite32(vk, 0, BAR_1, VK_BAR1_BOOT1_VER_TAG);
> +
> + for (i = 0; i < VK_BAR1_DAUTH_MAX; i++) {
> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_STORE_ADDR(i));
> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_VALID_ADDR(i));
> + }
> + for (i = 0; i < VK_BAR1_SOTP_REVID_MAX; i++)
> + vkwrite32(vk, 0, BAR_1, VK_BAR1_SOTP_REVID_ADDR(i));
> +
> + memset(&vk->card_info, 0, sizeof(vk->card_info));
> + memset(&vk->alert_cnts, 0, sizeof(vk->alert_cnts));
> +
> + /*
> + * When boot request fails, the CODE_PUSH_OFFSET stays persistent.
> + * Allowing us to debug the failure. When we call reset,
> + * we should clear CODE_PUSH_OFFSET so ROM does not execute
> + * boot again (and fails again) and instead waits for a new
> + * codepush.
> + */
> + value = vkread32(vk, BAR_0, BAR_CODEPUSH_SBL);
> + value &= ~CODEPUSH_MASK;
> + vkwrite32(vk, value, BAR_0, BAR_CODEPUSH_SBL);
> +
> + /* reset fw_status with proper reason, and press db */
> + vkwrite32(vk, VK_FWSTS_RESET_MBOX_DB, BAR_0, VK_BAR_FWSTS);
> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_SOFT);
> +
> + /* clear the uptime register after reset pressed and alert record */
> + vkwrite32(vk, 0, BAR_0, BAR_OS_UPTIME);
> + memset(&vk->host_alert, 0, sizeof(vk->host_alert));
> + memset(&vk->peer_alert, 0, sizeof(vk->peer_alert));
> +#if defined(CONFIG_BCM_VK_QSTATS)
> + /* clear qstats */
> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
> + memset(&vk->h2vk_msg_chan.qstats[i].qcnts, 0,
> + sizeof(vk->h2vk_msg_chan.qstats[i].qcnts));
> + memset(&vk->vk2h_msg_chan.qstats[i].qcnts, 0,
> + sizeof(vk->vk2h_msg_chan.qstats[i].qcnts));
> + }
> +#endif
> + /* clear 4096 bits of bitmap */
> + bitmap_clear(vk->bmap, 0, VK_MSG_ID_BITMAP_SIZE);
> +}
> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.h b/drivers/misc/bcm-vk/bcm_vk_msg.h
> new file mode 100644
> index 000000000000..076307b2f1c4
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.h
> @@ -0,0 +1,210 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#ifndef BCM_VK_MSG_H
> +#define BCM_VK_MSG_H
> +
> +#include <uapi/linux/misc/bcm_vk.h>
> +#include "bcm_vk_sg.h"
> +
> +/* Single message queue control structure */
> +struct bcm_vk_msgq {
> + uint16_t type; /* queue type */
> + uint16_t num; /* queue number */
> + uint32_t start; /* offset in BAR1 where the queue memory starts */
> + volatile uint32_t rd_idx; /* read idx */
> + volatile uint32_t wr_idx; /* write idx */
> + uint32_t size; /*
> + * size, which is in number of 16byte blocks,
> + * to align with the message data structure.
> + */
> + uint32_t nxt; /*
> + * nxt offset to the next msg queue struct.
> + * This is to provide flexibity for alignment purposes.
> + */
> +};
> +
> +/*
> + * Structure to record static info from the msgq sync. We keep local copy
> + * for some of these variables for both performance + checking purpose.
> + */
> +struct bcm_vk_sync_qinfo {
> + void __iomem *q_start;
> + uint32_t q_size;
> + uint32_t q_mask;
> + uint32_t q_low;
> +};
> +
> +#define VK_MSGQ_MAX_NR 4 /* Maximum number of message queues */
> +
> +/*
> + * some useful message queue macros
> + */
> +
> +/* vk_msg_blk is 16 bytes fixed */
> +#define VK_MSGQ_BLK_SIZE (sizeof(struct vk_msg_blk))
> +/* shift for fast division of basic msg blk size */
> +#define VK_MSGQ_BLK_SZ_SHIFT 4
> +
> +#define VK_MSGQ_EMPTY(_msgq) ((_msgq)->rd_idx == (_msgq)->wr_idx)
> +
> +#define VK_MSGQ_SIZE_MASK(_qinfo) ((_qinfo)->q_mask)
> +
> +#define VK_MSGQ_INC(_qinfo, _idx, _inc) \
> + (((_idx) + (_inc)) & VK_MSGQ_SIZE_MASK(_qinfo))
> +
> +#define VK_MSGQ_BLK_ADDR(_qinfo, _idx) \
> + (volatile struct vk_msg_blk *)((_qinfo)->q_start + \
> + (VK_MSGQ_BLK_SIZE * (_idx)))
> +
> +#define VK_MSGQ_OCCUPIED(_msgq, _qinfo) \
> + (((_msgq)->wr_idx - (_msgq)->rd_idx) & VK_MSGQ_SIZE_MASK(_qinfo))
> +
> +#define VK_MSGQ_AVAIL_SPACE(_msgq, _qinfo) \
> + ((_qinfo)->q_size - VK_MSGQ_OCCUPIED(_msgq, _qinfo) - 1)
> +
> +/* context per session opening of sysfs */
> +struct bcm_vk_ctx {
> + struct list_head node; /* use for linkage in Hash Table */
> + uint idx;
> + bool in_use;
> + struct task_struct *ppid;
> + uint32_t hash_idx;
> + struct miscdevice *miscdev;
> + int pend_cnt; /* number of items pending to be read from host */
> +};
> +
> +/* pid hash table entry */
> +struct bcm_vk_ht_entry {
> + struct list_head head;
> +};
> +
> +#define VK_DMA_MAX_ADDRS 4 /* Max 4 DMA Addresses */
> +/* structure for house keeping a single work entry */
> +struct bcm_vk_wkent {
> +
> + struct list_head node; /* for linking purpose */
> + struct bcm_vk_ctx *ctx;
> +
> + /* Store up to 4 dma pointers */
> + struct bcm_vk_dma dma[VK_DMA_MAX_ADDRS];
> +
> + uint32_t vk2h_blks; /* response */
> + struct vk_msg_blk *vk2h_msg;
> +
> + /*
> + * put the h2vk_msg at the end so that we could simply append h2vk msg
> + * to the end of the allocated block
> + */
> + uint32_t usr_msg_id;
> + uint32_t h2vk_blks;
> + uint32_t seq_num;
> + struct vk_msg_blk h2vk_msg[0];
> +};
> +
> +/* queue stats counters */
> +struct bcm_vk_qs_cnts {
> + uint32_t cnt; /* general counter, used to limit output */
> + uint32_t acc_sum;
> + uint32_t max_occ; /* max during a sampling period */
> + uint32_t max_abs; /* the abs max since reset */
> +};
> +
> +/* stats structure */
> +struct bcm_vk_qstats {
> + uint32_t q_num;
> + struct bcm_vk_qs_cnts qcnts;
> +};
> +
> +/* control channel structure for either h2vk or vk2h communication */
> +struct bcm_vk_msg_chan {
> + uint32_t q_nr;
> + struct mutex msgq_mutex;
> + /* pointing to BAR locations */
> + struct bcm_vk_msgq *msgq[VK_MSGQ_MAX_NR];
> +
> + spinlock_t pendq_lock;
> + /* for temporary storing pending items, one for each queue */
> + struct list_head pendq[VK_MSGQ_MAX_NR];
> + /* static queue info from the sync */
> + struct bcm_vk_sync_qinfo sync_qinfo[VK_MSGQ_MAX_NR];
> +#if defined(CONFIG_BCM_VK_QSTATS)
> + /* qstats */
> + struct bcm_vk_qstats qstats[VK_MSGQ_MAX_NR];
> +#endif
> +};
> +
> +/* total number of supported ctx, 32 ctx each for 5 components */
> +#define VK_CMPT_CTX_MAX (32 * 5)
> +
> +/* hash table defines to store the opened FDs */
> +#define VK_PID_HT_SHIFT_BIT 7 /* 128 */
> +#define VK_PID_HT_SZ (1 << VK_PID_HT_SHIFT_BIT)
> +
> +/* The following are offsets of DDR info provided by the vk card */
> +#define VK_BAR0_SEG_SIZE (4 * SZ_1K) /* segment size for BAR0 */
> +
> +/* shutdown types supported */
> +#define VK_SHUTDOWN_PID 1
> +#define VK_SHUTDOWN_GRACEFUL 2
> +
> +/*
> + * first door bell reg, ie for queue = 0. Only need the first one, as
> + * we will use the queue number to derive the others
> + */
> +#define VK_BAR0_REGSEG_DB_BASE 0x484
> +#define VK_BAR0_REGSEG_DB_REG_GAP 8 /*
> + * DB register gap,
> + * DB1 at 0x48c and DB2 at 0x494
> + */
> +
> +/* reset register and specific values */
> +#define VK_BAR0_RESET_DB_NUM 3
> +#define VK_BAR0_RESET_DB_SOFT 0xFFFFFFFF
> +#define VK_BAR0_RESET_DB_HARD 0xFFFFFFFD
> +
> +/* BAR1 message q definition */
> +
> +/* indicate if msgq ctrl in BAR1 is populated */
> +#define VK_BAR1_MSGQ_DEF_RDY 0x60c0
> +/* ready marker value for the above location, normal boot2 */
> +#define VK_BAR1_MSGQ_RDY_MARKER 0xbeefcafe
> +/* ready marker value for the above location, normal boot2 */
> +#define VK_BAR1_DIAG_RDY_MARKER 0xdeadcafe
> +/* number of msgqs in BAR1 */
> +#define VK_BAR1_MSGQ_NR 0x60c4
> +/* BAR1 queue control structure offset */
> +#define VK_BAR1_MSGQ_CTRL_OFF 0x60c8
> +/* BAR1 ucode and boot1 version tag */
> +#define VK_BAR1_UCODE_VER_TAG 0x6170
> +#define VK_BAR1_BOOT1_VER_TAG 0x61b0
> +#define VK_BAR1_VER_TAG_SIZE 64
> +/* Memory to hold the DMA buffer memory address allocated for boot2 download */
> +#define VK_BAR1_DMA_BUF_OFF_HI 0x61e0
> +#define VK_BAR1_DMA_BUF_OFF_LO (VK_BAR1_DMA_BUF_OFF_HI + 4)
> +#define VK_BAR1_DMA_BUF_SZ (VK_BAR1_DMA_BUF_OFF_HI + 8)
> +/* Scratch memory allocated on host for VK */
> +#define VK_BAR1_SCRATCH_OFF_LO 0x61f0
> +#define VK_BAR1_SCRATCH_OFF_HI (VK_BAR1_SCRATCH_OFF_LO + 4)
> +#define VK_BAR1_SCRATCH_SZ_ADDR (VK_BAR1_SCRATCH_OFF_LO + 8)
> +#define VK_BAR1_SCRATCH_DEF_NR_PAGES 32
> + /* BAR1 SOTP AUTH and REVID info */
> +#define VK_BAR1_DAUTH_BASE_ADDR 0x6200
> +#define VK_BAR1_DAUTH_STORE_SIZE 0x48
> +#define VK_BAR1_DAUTH_VALID_SIZE 0x8
> +#define VK_BAR1_DAUTH_MAX 4
> +#define VK_BAR1_DAUTH_STORE_ADDR(x) \
> + (VK_BAR1_DAUTH_BASE_ADDR + \
> + (x) * (VK_BAR1_DAUTH_STORE_SIZE + VK_BAR1_DAUTH_VALID_SIZE))
> +#define VK_BAR1_DAUTH_VALID_ADDR(x) \
> + (VK_BAR1_DAUTH_STORE_ADDR(x) + VK_BAR1_DAUTH_STORE_SIZE)
> +
> +#define VK_BAR1_SOTP_REVID_BASE_ADDR 0x6340
> +#define VK_BAR1_SOTP_REVID_SIZE 0x10
> +#define VK_BAR1_SOTP_REVID_MAX 2
> +#define VK_BAR1_SOTP_REVID_ADDR(x) \
> + (VK_BAR1_SOTP_REVID_BASE_ADDR + (x) * VK_BAR1_SOTP_REVID_SIZE)
> +
> +#endif
> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.c b/drivers/misc/bcm-vk/bcm_vk_sg.c
> new file mode 100644
> index 000000000000..e5de07958f0b
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.c
> @@ -0,0 +1,273 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +#include <linux/dma-mapping.h>
> +#include <linux/mm.h>
> +#include <linux/pagemap.h>
> +#include <linux/vmalloc.h>
> +
> +#include <asm/page.h>
> +#include <asm/pgtable.h>
> +#include <asm/unaligned.h>
> +
> +#include <uapi/linux/misc/bcm_vk.h>
> +
> +#include "bcm_vk.h"
> +#include "bcm_vk_msg.h"
> +#include "bcm_vk_sg.h"
> +
> +/*
> + * Valkyrie has a hardware limitation of 16M transfer size.
> + * So limit the SGL chunks to 16M.
> + */
> +#define BCM_VK_MAX_SGL_CHUNK SZ_16M
> +
> +static int bcm_vk_dma_alloc(struct device *dev,
> + struct bcm_vk_dma *dma,
> + int dir,
> + struct _vk_data *vkdata);
> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
> +
> +/* Uncomment to dump SGLIST */
> +//#define BCM_VK_DUMP_SGLIST
> +
> +static int bcm_vk_dma_alloc(struct device *dev,
> + struct bcm_vk_dma *dma,
> + int direction,
> + struct _vk_data *vkdata)
> +{
> + dma_addr_t addr, sg_addr;
> + int err;
> + int i;
> + int offset;
> + uint32_t size;
> + uint32_t remaining_size;
> + uint32_t transfer_size;
> + uint64_t data;
> + unsigned long first, last;
> + struct _vk_data *sgdata;
> +
> + /* Get 64-bit user address */
> + data = get_unaligned(&(vkdata->address));

Extra parens.

> +
> + /* offset into first page */
> + offset = offset_in_page(data);
> +
> + /* Calculate number of pages */
> + first = (data & PAGE_MASK) >> PAGE_SHIFT;
> + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
> + dma->nr_pages = last - first + 1;
> +
> + /* Allocate DMA pages */
> + dma->pages = kmalloc_array(dma->nr_pages,
> + sizeof(struct page *),
> + GFP_KERNEL);
> + if (dma->pages == NULL)
> + return -ENOMEM;
> +
> + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
> + data, vkdata->size, dma->nr_pages);
> +
> + dma->direction = direction;
> +
> + /* Get user pages into memory */
> + err = get_user_pages_fast(data & PAGE_MASK,
> + dma->nr_pages,
> + direction == DMA_FROM_DEVICE,
> + dma->pages);
> + if (err != dma->nr_pages) {
> + dma->nr_pages = (err >= 0) ? err : 0;
> + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
> + err, dma->nr_pages);
> + return err < 0 ? err : -EINVAL;
> + }
> +
> + /* Max size of sg list is 1 per mapped page + fields at start */
> + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
> +
> + /* Allocate sglist */
> + dma->sglist = dma_alloc_coherent(dev,
> + dma->sglen,
> + &dma->handle,
> + GFP_KERNEL);


dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
GFP_KERNEL);



> + if (!dma->sglist)
> + return -ENOMEM;

No cleanup?

> +
> + dma->sglist[SGLIST_NUM_SG] = 0;
> + dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
> + remaining_size = vkdata->size;
> + sgdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
> +
> + /* Map all pages into DMA */
> + i = 0;

Set but not used.

> + size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
> + remaining_size -= size;
> + sg_addr = dma_map_page(dev,
> + dma->pages[0],
> + offset,
> + size,
> + dma->direction);
> + transfer_size = size;
> + if (unlikely(dma_mapping_error(dev, sg_addr))) {
> + __free_page(dma->pages[0]);
> + return -EIO;
> + }
> +
> + for (i = 1; i < dma->nr_pages; i++) {
> + size = min_t(size_t, PAGE_SIZE, remaining_size);
> + remaining_size -= size;
> + addr = dma_map_page(dev,
> + dma->pages[i],
> + 0,
> + size,
> + dma->direction);
> + if (unlikely(dma_mapping_error(dev, addr))) {
> + __free_page(dma->pages[i]);
> + return -EIO;
> + }
> +
> + /*
> + * Compress SG list entry when pages are contiguous
> + * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
> + */
> + if ((addr == (sg_addr + transfer_size)) &&
> + ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
> + /* pages are contiguous, add to same sg entry */
> + transfer_size += size;
> + } else {
> + /* pages are not contiguous, write sg entry */
> + sgdata->size = transfer_size;
> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
> + dma->sglist[SGLIST_NUM_SG]++;
> +
> + /* start new sg entry */
> + sgdata++;
> + sg_addr = addr;
> + transfer_size = size;
> + }
> + }
> + /* Write last sg list entry */
> + sgdata->size = transfer_size;
> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
> + dma->sglist[SGLIST_NUM_SG]++;
> +
> + /* Update pointers and size field to point to sglist */
> + put_unaligned((uint64_t)dma->handle, &(vkdata->address));
> + vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
> +
> +#ifdef BCM_VK_DUMP_SGLIST
> + dev_dbg(dev,
> + "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
> + (uint64_t)dma->sglist,
> + dma->handle,
> + dma->sglen,
> + vkdata->size);
> + for (i = 0; i < vkdata->size / sizeof(uint32_t); i++)
> + dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
> +#endif
> +
> + return 0;
> +}
> +
> +int bcm_vk_sg_alloc(struct device *dev,
> + struct bcm_vk_dma *dma,
> + int dir,
> + struct _vk_data *vkdata,
> + int num)
> +{
> + int i;
> + int rc = -EINVAL;
> +
> + /* Convert user addresses to DMA SG List */
> + for (i = 0; i < num; i++) {
> + if (vkdata[i].size && vkdata[i].address) {
> + /*
> + * If both size and address are non-zero
> + * then DMA alloc.
> + */
> + rc = bcm_vk_dma_alloc(dev,
> + &dma[i],
> + dir,
> + &vkdata[i]);
> + } else if (vkdata[i].size ||
> + vkdata[i].address) {
> + /*
> + * If one of size and address are zero
> + * there is a problem.
> + */
> + dev_err(dev,
> + "Invalid vkdata %x 0x%x 0x%llx\n",
> + i, vkdata[i].size, vkdata[i].address);
> + rc = -EINVAL;
> + } else {
> + /*
> + * If size and address are both zero
> + * don't convert, but return success.
> + */
> + rc = 0;
> + }
> +
> + if (rc)
> + goto fail_alloc;
> + }
> + return rc;
> +
> +fail_alloc:
> + while (i > 0) {
> + i--;
> + if (dma[i].sglist)
> + bcm_vk_dma_free(dev, &dma[i]);
> + }
> + return rc;
> +}
> +
> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
> +{
> + dma_addr_t addr;
> + int i;
> + int num_sg;
> + uint32_t size;
> + struct _vk_data *vkdata;
> +
> + dev_dbg(dev, "free sglist=%p sglen=0x%x\n",
> + dma->sglist, dma->sglen);
> +

This fits in 80 characters.

dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);


> + /* Unmap all pages in the sglist */
> + num_sg = dma->sglist[SGLIST_NUM_SG];
> + vkdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);

Extra parens.

> + for (i = 0; i < num_sg; i++) {
> + size = vkdata[i].size;
> + addr = get_unaligned(&(vkdata[i].address));
> +
> + dma_unmap_page(dev, addr, size, dma->direction);
> + }
> +
> + /* Free allocated sglist */
> + dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
> +
> + /* Release lock on all pages */
> + for (i = 0; i < dma->nr_pages; i++)
> + put_page(dma->pages[i]);
> +
> + /* Free allocated dma pages */
> + kfree(dma->pages);
> + dma->sglist = NULL;
> +
> + return 0;
> +}
> +
> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num)
> +{
> + int i;
> +
> + /* Unmap and free all pages and sglists */
> + for (i = 0; i < num; i++) {
> + if (dma[i].sglist)
> + bcm_vk_dma_free(dev, &dma[i]);
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.h b/drivers/misc/bcm-vk/bcm_vk_sg.h
> new file mode 100644
> index 000000000000..df88154e80b7
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#ifndef BCM_VK_SG_H
> +#define BCM_VK_SG_H
> +
> +#include <linux/dma-mapping.h>
> +
> +struct bcm_vk_dma {
> + /* for userland buffer */
> + struct page **pages;
> + int nr_pages;
> +
> + /* common */
> + dma_addr_t handle;
> + /*
> + * sglist is of the following LE format
> + * [U32] num_sg = number of sg addresses (N)
> + * [U32] totalsize = totalsize of data being transferred in sglist
> + * [U32] size[0] = size of data in address0
> + * [U32] addr_l[0] = lower 32-bits of address0
> + * [U32] addr_h[0] = higher 32-bits of address0
> + * ..
> + * [U32] size[N-1] = size of data in addressN-1
> + * [U32] addr_l[N-1] = lower 32-bits of addressN-1
> + * [U32] addr_h[N-1] = higher 32-bits of addressN-1
> + */
> + uint32_t *sglist;
> +#define SGLIST_NUM_SG 0
> +#define SGLIST_TOTALSIZE 1
> +#define SGLIST_VKDATA_START 2
> +
> + int sglen; /* Length (bytes) of sglist */
> + int direction;
> +};
> +
> +struct _vk_data {
> + uint32_t size; /* data size in bytes */
> + uint64_t address; /* Pointer to data */
> +} __packed;
> +
> +/*
> + * Scatter-gather DMA buffer API.
> + *
> + * These functions provide a simple way to create a page list and a
> + * scatter-gather list from userspace address and map the memory
> + * for DMA operation.
> + */
> +int bcm_vk_sg_alloc(struct device *dev,
> + struct bcm_vk_dma *dma,
> + int dir,
> + struct _vk_data *vkdata,
> + int num);
> +
> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num);
> +
> +#endif
> +
> diff --git a/drivers/misc/bcm-vk/bcm_vk_tty.c b/drivers/misc/bcm-vk/bcm_vk_tty.c
> new file mode 100644
> index 000000000000..3835218aaec6
> --- /dev/null
> +++ b/drivers/misc/bcm-vk/bcm_vk_tty.c
> @@ -0,0 +1,327 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2020 Broadcom.
> + */
> +
> +#include <linux/tty.h>
> +#include <linux/tty_driver.h>
> +#include <linux/tty_flip.h>
> +
> +#include "bcm_vk.h"
> +
> +/* TTYVK base offset is 0x30000 into BAR1 */
> +#define BAR1_TTYVK_BASE_OFFSET 0x300000
> +/* Each TTYVK channel (TO or FROM) is 0x10000 */
> +#define BAR1_TTYVK_CHAN_OFFSET 0x100000
> +/* Each TTYVK channel has TO and FROM, hence the * 2 */
> +#define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
> + ((index) * BAR1_TTYVK_CHAN_OFFSET * 2))
> +/* TO TTYVK channel base comes before FROM for each index */
> +#define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
> +#define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
> + BAR1_TTYVK_CHAN_OFFSET)
> +
> +struct bcm_vk_tty_chan {
> + uint32_t reserved;
> + uint32_t size;
> + uint32_t wr;
> + uint32_t rd;
> + uint32_t *data;
> +};
> +
> +#define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
> + + offsetof(struct bcm_vk_tty_chan, e))
> +#define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
> +#define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
> +#define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
> +#define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + off)
> +
> +/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
> +#define SERIAL_TIMER_VALUE (HZ / 10)
> +
> +#if defined(BCM_VK_LEGACY_API)
> +
> +/* No support in legacy case, and do a dummy init and exit */
> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
> +{
> + struct device *dev = &vk->pdev->dev;
> +
> + dev_dbg(dev, "init %s\n", name);
> + return 0;
> +}
> +
> +void bcm_vk_tty_exit(struct bcm_vk *vk)
> +{
> + struct device *dev = &vk->pdev->dev;
> +
> + dev_dbg(dev, "exit\n");
> +}
> +
> +#else
> +
> +static void bcm_vk_tty_poll(struct timer_list *t)
> +{
> + struct bcm_vk *vk = from_timer(vk, t, serial_timer);
> + struct bcm_vk_tty *vktty;
> + int card_status;
> + int ready_mask;
> + int count = 0;
> + unsigned char c;
> + int i;
> + int wr;
> +
> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
> +
> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
> + /* Check the card status that the tty channel is ready */
> + ready_mask = BIT(i);
> + if ((card_status & ready_mask) == 0)

Get rid of ready_mask and use BIT(i) directly.

> + continue;
> +
> + vktty = &vk->tty[i];
> +
> + /* Fetch the wr offset in buffer from VK */
> + wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from));
> + if (wr >= vktty->from_size) {
> + dev_err(&vk->pdev->dev,
> + "ERROR: poll ttyVK%d wr:0x%x > 0x%x\n",
> + i, wr, vktty->from_size);
> + /* Need to signal and close device in this case */
> + return;
> + }
> +
> + /*
> + * Simple read of circular buffer and
> + * insert into tty flip buffer
> + */
> + while (vk->tty[i].rd != wr) {
> + c = vkread8(vk, BAR_1,
> + VK_BAR_CHAN_DATA(vktty, from, vktty->rd));
> + vktty->rd++;
> + if (vktty->rd >= vktty->from_size)
> + vktty->rd = 0;
> + tty_insert_flip_char(&vktty->port, c, TTY_NORMAL);
> + count++;
> + }
> + }
> +
> + if (count) {
> + tty_flip_buffer_push(&vktty->port);
> +
> + /* Update read offset from shadow register to card */
> + vkwrite32(vk, vktty->rd, BAR_1, VK_BAR_CHAN_RD(vktty, from));
> + }
> +
> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
> +}
> +
> +static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file)
> +{
> + int card_status;
> + int ready_mask;
> + struct bcm_vk *vk;
> + struct bcm_vk_tty *vktty;
> + int index;
> +
> + /* initialize the pointer in case something fails */
> + tty->driver_data = NULL;
> +
> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
> + index = tty->index;
> +
> + if (index >= BCM_VK_NUM_TTY)
> + return -EINVAL;
> +
> + vktty = &vk->tty[index];
> +
> + vktty->to_offset = TO_TTYK_BASE(index);
> + vktty->from_offset = FROM_TTYK_BASE(index);
> +
> + /* Do not allow tty device to be opened if tty on card not ready */
> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
> + if (card_status == -1)
> + return -1;
> +
> + ready_mask = BIT(index);
> + if ((card_status & ready_mask) == 0)
> + return -1;
> +
> + /*
> + * Get shadow registers of the buffer sizes and the "to" write offset
> + * and "from" read offset
> + */
> + vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to));
> + vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to));
> + vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from));
> + vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from));
> +
> + if (tty->count == 1) {
> + timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0);
> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
> + }
> + return 0;
> +}
> +
> +static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file)
> +{
> + struct bcm_vk *vk;
> +
> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);


struct bcm_vk *vk = dev_get_drvdata(tty->dev);

> +
> + if (tty->count == 1)
> + del_timer_sync(&vk->serial_timer);
> +}
> +
> +static int bcm_vk_tty_write(struct tty_struct *tty,
> + const unsigned char *buffer,
> + int count)
> +{
> + int index;
> + struct bcm_vk *vk;
> + struct bcm_vk_tty *vktty;
> + int i;
> + int retval;
> +
> + index = tty->index;
> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);

The cast is unnecessary.

> + vktty = &vk->tty[index];
> +
> + /* Simple write each byte to circular buffer */
> + for (i = 0; i < count; i++) {
> + vkwrite8(vk,
> + buffer[i],
> + BAR_1,
> + VK_BAR_CHAN_DATA(vktty, to, vktty->wr));

vkwrite8(vk, buffer[i], BAR_1,
VK_BAR_CHAN_DATA(vktty, to, vktty->wr));


> + vktty->wr++;
> + if (vktty->wr >= vktty->to_size)
> + vktty->wr = 0;
> + }
> + /* Update write offset from shadow register to card */
> + /* TODO: Need to add write to doorbell here */
> + vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to));
> +
> + retval = count;
> +
> + return retval;

Delete the "retval" variable.

> +}
> +
> +static int bcm_vk_tty_write_room(struct tty_struct *tty)
> +{
> + int room;
> + struct bcm_vk *vk;
> + struct bcm_vk_tty *vktty;
> +
> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);

Delete cast.

> + vktty = &vk->tty[tty->index];
> +
> + /*
> + * Calculate how much room is left in the device
> + * Just return the size -1 of buffer. We could care about
> + * overflow but don't at this point.
> + */

This comment raises more questions than it answers. Why would we care
about overflow and why don't we at this point? Perhaps just delete the
comment.

> + room = vktty->to_size - 1;
> +
> + return room;

Delete the room variable and the vktty variable.

return vk->tty[tty->index].to_size - 1;


> +}
> +
> +static const struct tty_operations serial_ops = {
> + .open = bcm_vk_tty_open,
> + .close = bcm_vk_tty_close,
> + .write = bcm_vk_tty_write,
> + .write_room = bcm_vk_tty_write_room,
> +};
> +
> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
> +{
> + int i;
> + int err;
> + unsigned long flags;
> + struct tty_driver *tty_drv;
> + struct device *dev = &vk->pdev->dev;
> +
> + /* allocate the tty driver */
> + flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
> + tty_drv = tty_alloc_driver(BCM_VK_NUM_TTY, flags);

Delete the flags variable.

> +

Delete this blank line.

> + if (IS_ERR(tty_drv)) {
> + err = PTR_ERR(tty_drv);
> + goto err_exit;


return PTR_ERR(tty_drv);

> + }
> + /* Save struct tty_driver for uninstalling the device */
> + vk->tty_drv = tty_drv;
> +
> + /* initialize the tty driver */
> + tty_drv->driver_name = KBUILD_MODNAME;
> + tty_drv->name = kstrdup(name, GFP_KERNEL);
> + if (!tty_drv->name) {
> + err = -ENOMEM;
> + goto err_put_tty_driver;
> + }
> + tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
> + tty_drv->subtype = SERIAL_TYPE_NORMAL;
> + tty_drv->init_termios = tty_std_termios;
> + tty_set_operations(tty_drv, &serial_ops);
> +
> + /* register the tty driver */
> + err = tty_register_driver(tty_drv);
> + if (err) {
> + dev_err(dev, "tty_register_driver failed\n");
> + goto err_kfree_tty_name;
> + }
> +
> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
> + struct device *tty_dev;
> +
> + tty_port_init(&vk->tty[i].port);
> + tty_dev = tty_port_register_device(&vk->tty[i].port,
> + tty_drv,
> + i,
> + dev);

tty_dev = tty_port_register_device(&vk->tty[i].port, tty_drv,
i, dev);


> + dev_set_drvdata(tty_dev, vk);
> +
> + if (IS_ERR(tty_dev)) {

Calling dev_set_drvdata() before checking for IS_ERR().

> + int j;
> +
> + for (j = 0; j < i; j++)
> + tty_port_unregister_device(&vk->tty[j].port,
> + tty_drv,
> + j);
> + goto err_tty_unregister_driver;

No error code.

> + }

if (IS_ERR(tty_dev)) {
err = PTR_ERR(tty_dev);
goto unwind;
}

> + }
> +
> + return 0;
> +

unwind:
while (--i >= 0)
tty_port_unregister_device(&vk->tty[i].port, tty_drv, i);


> +err_tty_unregister_driver:
> + tty_unregister_driver(tty_drv);
> +
> +err_kfree_tty_name:
> + kfree(tty_drv->name);
> + tty_drv->name = NULL;
> +
> +err_put_tty_driver:
> + put_tty_driver(tty_drv);
> +
> +err_exit:
> + return err;
> +}
> +
> +void bcm_vk_tty_exit(struct bcm_vk *vk)
> +{
> + int i;
> +
> + del_timer_sync(&vk->serial_timer);
> + for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
> + tty_port_unregister_device(&vk->tty[i].port,
> + vk->tty_drv,
> + i);
> + tty_port_destroy(&vk->tty[i].port);
> + }
> + tty_unregister_driver(vk->tty_drv);
> + put_tty_driver(vk->tty_drv);
> +
> + kfree(vk->tty_drv->name);
^^^^^^^^^^^

Use after free because of the put_tty_driver(). I think test again
with CONFIG_PAGE_POISONING?

> + vk->tty_drv->name = NULL;
> +}

regards,
dan carpenter

2020-02-21 00:07:08

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Hi Randy,

On 2020-02-19 5:04 p.m., Randy Dunlap wrote:
> Hi,
>
> On 2/19/20 4:48 PM, Scott Branden wrote:
>> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
>> new file mode 100644
>> index 000000000000..c75dfb89a38d
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/Kconfig
>> @@ -0,0 +1,42 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# Broadcom VK device
>> +#
>> +config BCM_VK
>> + tristate "Support for Broadcom VK Accelerators"
>> + depends on PCI_MSI
>> + default m
> Need to justify default m. Normally we don't add drivers as enabled unless
> they are required for basic (boot) operation.
Will remove default m as not needed to boot.  Interesting other offload
engines misc/ocxl/Kconfig and misc/cxl/Kconfig have default m.
>
>> + help
>> + Select this option to enable support for Broadcom
>> + VK Accelerators. VK is used for performing
>> + specific video offload processing. This driver enables
>> + userspace programs to access these accelerators via /dev/bcm-vk.N
>> + devices.
>> +
>> + If unsure, say N.
>> +
>> +if BCM_VK
>> +
>> +config BCM_VK_H2VK_VERIFY_AND_RETRY
>> + bool "Host To VK Verifiy Data and Retry"
> Verify
>
>> + help
>> + Turn on to verify the data passed down to VK is good,
>> + and if not, do a retry until it succeeds.
> No timeout on that retry?
This is only enabled for debug purposes or fpga workarounds - no need
for a timeout.
>
>> + This is a debug/workaround on FPGA PCIe timing issues
>> + but may be found useful for debugging other PCIe hardware issues.
>> + Small performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +config BCM_VK_QSTATS
>> + bool "VK Queue Statistics"
>> + help
>> + Turn on to enable Queue Statistics.
>> + These are useful for debugging purposes.
>> + Some performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +endif
> cheers.

2020-02-21 00:15:32

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 2/7] firmware: add offset to request_firmware_into_buf

Hi Luis,

On 2020-02-19 5:22 p.m., Luis Chamberlain wrote:
> On Wed, Feb 19, 2020 at 04:48:20PM -0800, Scott Branden wrote:
>> Add offset to request_firmware_into_buf to allow for portions
>> of firmware file to be read into a buffer. Necessary where firmware
>> needs to be loaded in portions from file in memory constrained systems.
>>
>> Signed-off-by: Scott Branden <[email protected]>
> Thanks for following up Scott! However you failed to address the
> feedback last time by Takashi, so until then, this remains a blocker for
> this.
>
> http://lkml.kernel.org/r/[email protected]
>
> Luis
I responded to the email query.  Hopefully this addresses your concern.

Regards,
 Scott

2020-02-21 00:23:00

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On 2/20/20 4:06 PM, Scott Branden wrote:
> Hi Randy,
>
> On 2020-02-19 5:04 p.m., Randy Dunlap wrote:
>> Hi,
>>
>> On 2/19/20 4:48 PM, Scott Branden wrote:
>>> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
>>> new file mode 100644
>>> index 000000000000..c75dfb89a38d
>>> --- /dev/null
>>> +++ b/drivers/misc/bcm-vk/Kconfig
>>> @@ -0,0 +1,42 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +#
>>> +# Broadcom VK device
>>> +#
>>> +config BCM_VK
>>> +    tristate "Support for Broadcom VK Accelerators"
>>> +    depends on PCI_MSI
>>> +    default m
>> Need to justify default m. Normally we don't add drivers as enabled unless
>> they are required for basic (boot) operation.
> Will remove default m as not needed to boot.  Interesting other offload engines misc/ocxl/Kconfig and misc/cxl/Kconfig have default m.

Thanks.

Uh, yes, that is interesting. They both depend on PPC_POWERNV,
which I know nothing about.

>>> +    help
>>> +      Select this option to enable support for Broadcom
>>> +      VK Accelerators.  VK is used for performing
>>> +      specific video offload processing.  This driver enables
>>> +      userspace programs to access these accelerators via /dev/bcm-vk.N
>>> +      devices.
>>> +
>>> +      If unsure, say N.

--
~Randy

2020-02-21 01:18:58

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI

Hi Greg,

Thanks for the review.  Comments inline.

On 2020-02-19 11:50 p.m., Greg Kroah-Hartman wrote:
> On Wed, Feb 19, 2020 at 04:48:23PM -0800, Scott Branden wrote:
>> Add user space api for bcm-vk driver.
>>
>> Signed-off-by: Scott Branden <[email protected]>
>> ---
>> include/uapi/linux/misc/bcm_vk.h | 117 +++++++++++++++++++++++++++++++
>> 1 file changed, 117 insertions(+)
>> create mode 100644 include/uapi/linux/misc/bcm_vk.h
>>
>> diff --git a/include/uapi/linux/misc/bcm_vk.h b/include/uapi/linux/misc/bcm_vk.h
>> new file mode 100644
>> index 000000000000..56a2178e06f5
>> --- /dev/null
>> +++ b/include/uapi/linux/misc/bcm_vk.h
>> @@ -0,0 +1,117 @@
>> +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef __UAPI_LINUX_MISC_BCM_VK_H
>> +#define __UAPI_LINUX_MISC_BCM_VK_H
>> +
>> +#include <linux/ioctl.h>
>> +#include <linux/types.h>
>> +
>> +struct vk_image {
>> + __u32 type; /* Type of image */
>> +#define VK_IMAGE_TYPE_BOOT1 1 /* 1st stage (load to SRAM) */
>> +#define VK_IMAGE_TYPE_BOOT2 2 /* 2nd stage (load to DDR) */
>> + char filename[64]; /* Filename of image */
> __u8?
I don't understand why char is not appropriate for a filename.
Would like to understand why __u8 is correct to use here vs. char.
>
>> +};
>> +
>> +/* default firmware images names */
>> +#define VK_BOOT1_DEF_VALKYRIE_FILENAME "vk-boot1.bin"
>> +#define VK_BOOT2_DEF_VALKYRIE_FILENAME "vk-boot2.bin"
>> +
>> +#define VK_BOOT1_DEF_VIPER_FILENAME "vp-boot1.bin"
>> +#define VK_BOOT2_DEF_VIPER_FILENAME "vp-boot2.bin"
> Why do these need to be in a uapi .h file? Shouldn't they just be part
> of the normal MODULE_FIRMWARE() macro in the driver itself?
ioctl VK_IOCTL_LOAD_IMAGE passes in type of image to load and filename.
These are the default names used if the images are autoloaded by the driver.
But if userspace app wishes to load (or reload) the default images then
it needs to know the name of the file to pass in ioctl.
I guess I could change the API at this point to lookup the default
filename if NULL filename passed into ioctl.
>> +struct vk_access {
>> + __u8 barno; /* BAR number to use */
>> + __u8 type; /* Type of access */
>> +#define VK_ACCESS_READ 0
>> +#define VK_ACCESS_WRITE 1
>> + __u32 len; /* length of data */
> Horrible padding issues, are you sure this all works properly?
Haven't had any issues.
>
>> + __u64 offset; /* offset in BAR */
>> + __u32 *data; /* where to read/write data to */
> Are you _SURE_ you want a pointer here? How do you handle the compat
> issues with 32/64 user/kernel space?
Don't care about 32-bit user space for this driver.
I don't think there isn't even enough memory in such systems for the
number of streams of video buffers needed for transcoding.
This driver is only used in high end 64-bit x86 servers.
But, VK_IOCTL_ACCESS_BAR can go away entirely if standard user space
approach already exists as you imply.
>> +};
> And isn't this just a normal PCI write thing? Can't you do it from
> userspace using the existing userspace PCI accesses? Why do you need a
> special ioctl for it?
This follows how pci_endpoint_test reads and writes BARS via ioctl.
It also abstracts the accesses all into the device node being opened.

I am not familiar with userspace PCI accesses.  Would this be through
some sys entries?
>
>> +
>> +struct vk_reset {
>> + __u32 arg1;
>> + __u32 arg2;
>> +};
>> +
>> +#define VK_MAGIC 0x5E
>> +
>> +/* Load image to Valkyrie */
>> +#define VK_IOCTL_LOAD_IMAGE _IOW(VK_MAGIC, 0x2, struct vk_image)
>> +
>> +/* Read data from Valkyrie */
>> +#define VK_IOCTL_ACCESS_BAR _IOWR(VK_MAGIC, 0x3, struct vk_access)
>> +
>> +/* Send Reset to Valkyrie */
>> +#define VK_IOCTL_RESET _IOW(VK_MAGIC, 0x4, struct vk_reset)
>> +
>> +/*
>> + * message block - basic unit in the message where a message's size is always
>> + * N x sizeof(basic_block)
>> + */
>> +struct vk_msg_blk {
>> + __u8 function_id;
>> +#define VK_FID_TRANS_BUF 5
>> +#define VK_FID_SHUTDOWN 8
>> + __u8 size;
>> + __u16 queue_id:4;
>> + __u16 msg_id:12;
> Do not use bitfields in ioctls, they will not work properly on all
> systems. Use masks and shifts instead.
I don't like the bitfields either - structure inherited from firmware code.
Will work on getting these removed.
>
>> + __u32 context_id;
>> + __u32 args[2];
>> +#define VK_CMD_PLANES_MASK 0x000F /* number of planes to up/download */
>> +#define VK_CMD_UPLOAD 0x0400 /* memory transfer to vk */
>> +#define VK_CMD_DOWNLOAD 0x0500 /* memory transfer from vk */
>> +#define VK_CMD_MASK 0x0F00 /* command mask */
>> +};
>> +
>> +#define VK_BAR_FWSTS 0x41C
>> +/* VK_FWSTS definitions */
>> +#define VK_FWSTS_RELOCATION_ENTRY BIT(0)
>> +#define VK_FWSTS_RELOCATION_EXIT BIT(1)
>> +#define VK_FWSTS_INIT_START BIT(2)
>> +#define VK_FWSTS_ARCH_INIT_DONE BIT(3)
>> +#define VK_FWSTS_PRE_KNL1_INIT_DONE BIT(4)
>> +#define VK_FWSTS_PRE_KNL2_INIT_DONE BIT(5)
>> +#define VK_FWSTS_POST_KNL_INIT_DONE BIT(6)
>> +#define VK_FWSTS_INIT_DONE BIT(7)
>> +#define VK_FWSTS_APP_INIT_START BIT(8)
>> +#define VK_FWSTS_APP_INIT_DONE BIT(9)
> I do not think that BIT() is exported to userspace properly, is it
> really ok here?
Works fine.  Also in uapi/linux/rtc.h.
>
>> +#define VK_FWSTS_MASK 0xFFFFFFFF
>> +#define VK_FWSTS_READY (VK_FWSTS_INIT_START | \
>> + VK_FWSTS_ARCH_INIT_DONE | \
>> + VK_FWSTS_PRE_KNL1_INIT_DONE | \
>> + VK_FWSTS_PRE_KNL2_INIT_DONE | \
>> + VK_FWSTS_POST_KNL_INIT_DONE | \
>> + VK_FWSTS_INIT_DONE | \
>> + VK_FWSTS_APP_INIT_START | \
>> + VK_FWSTS_APP_INIT_DONE)
>> +/* Deinit */
>> +#define VK_FWSTS_APP_DEINIT_START BIT(23)
>> +#define VK_FWSTS_APP_DEINIT_DONE BIT(24)
>> +#define VK_FWSTS_DRV_DEINIT_START BIT(25)
>> +#define VK_FWSTS_DRV_DEINIT_DONE BIT(26)
>> +#define VK_FWSTS_RESET_DONE BIT(27)
>> +#define VK_FWSTS_DEINIT_TRIGGERED (VK_FWSTS_APP_DEINIT_START | \
>> + VK_FWSTS_APP_DEINIT_DONE | \
>> + VK_FWSTS_DRV_DEINIT_START | \
>> + VK_FWSTS_DRV_DEINIT_DONE)
>> +/* Last nibble for reboot reason */
>> +#define VK_FWSTS_RESET_REASON_SHIFT 28
>> +#define VK_FWSTS_RESET_REASON_MASK (0xF << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_SYS_PWRUP (0x0 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_MBOX_DB (0x1 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_M7_WDOG (0x2 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_TEMP (0x3 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_PCI_FLR (0x4 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_PCI_HOT (0x5 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_PCI_WARM (0x6 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_PCI_COLD (0x7 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_L1 (0x8 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_L0 (0x9 << VK_FWSTS_RESET_REASON_SHIFT)
>> +#define VK_FWSTS_RESET_UNKNOWN (0xF << VK_FWSTS_RESET_REASON_SHIFT)
> What are all of these #defines doing in an uapi file? How is userspace
> going to use them?
There are actually 2 linux user spaces that use this header.
One is the x86 host with the bcm-vk PCI driver.
The x86 host user space could use them to check the firmware status and
find out what state VK is in.

The other user space is a coprocessor inside the VK SOC.
The app running in user space needs to know the state of the FWSTS in
order to proceed.
It includes this header in its user space app (even though it doesn't
user the linux driver, it needs access to the same FWSTS register
directly).
> thanks,
>
> greg k-h

2020-02-21 08:36:52

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI

On Fri, Feb 21, 2020 at 2:16 AM Scott Branden
<[email protected]> wrote:


> >> +struct vk_access {
> >> + __u8 barno; /* BAR number to use */
> >> + __u8 type; /* Type of access */
> >> +#define VK_ACCESS_READ 0
> >> +#define VK_ACCESS_WRITE 1
> >> + __u32 len; /* length of data */
> > Horrible padding issues, are you sure this all works properly?
> Haven't had any issues.
> >
> >> + __u64 offset; /* offset in BAR */
> >> + __u32 *data; /* where to read/write data to */
> > Are you _SURE_ you want a pointer here? How do you handle the compat
> > issues with 32/64 user/kernel space?
> Don't care about 32-bit user space for this driver.
> I don't think there isn't even enough memory in such systems for the
> number of streams of video buffers needed for transcoding.
> This driver is only used in high end 64-bit x86 servers.

Please see Documentation/core-api/ioctl.rst

All ioctl interfaces should be written in a portable way that works with
compat user space and avoids all padding in order to not leak kernel
data into user space.

If the driver is passing video buffers for transcoding, shouldn't the driver
use the existing drivers/media interfaces for that? If it needs features
that are not present there, they can probably be added.

Arnd

2020-02-21 09:22:44

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI

On Thu, Feb 20, 2020 at 05:15:58PM -0800, Scott Branden wrote:
> Hi Greg,
>
> Thanks for the review.? Comments inline.
>
> On 2020-02-19 11:50 p.m., Greg Kroah-Hartman wrote:
> > On Wed, Feb 19, 2020 at 04:48:23PM -0800, Scott Branden wrote:
> > > Add user space api for bcm-vk driver.
> > >
> > > Signed-off-by: Scott Branden <[email protected]>
> > > ---
> > > include/uapi/linux/misc/bcm_vk.h | 117 +++++++++++++++++++++++++++++++
> > > 1 file changed, 117 insertions(+)
> > > create mode 100644 include/uapi/linux/misc/bcm_vk.h
> > >
> > > diff --git a/include/uapi/linux/misc/bcm_vk.h b/include/uapi/linux/misc/bcm_vk.h
> > > new file mode 100644
> > > index 000000000000..56a2178e06f5
> > > --- /dev/null
> > > +++ b/include/uapi/linux/misc/bcm_vk.h
> > > @@ -0,0 +1,117 @@
> > > +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
> > > +/*
> > > + * Copyright 2018-2020 Broadcom.
> > > + */
> > > +
> > > +#ifndef __UAPI_LINUX_MISC_BCM_VK_H
> > > +#define __UAPI_LINUX_MISC_BCM_VK_H
> > > +
> > > +#include <linux/ioctl.h>
> > > +#include <linux/types.h>
> > > +
> > > +struct vk_image {
> > > + __u32 type; /* Type of image */
> > > +#define VK_IMAGE_TYPE_BOOT1 1 /* 1st stage (load to SRAM) */
> > > +#define VK_IMAGE_TYPE_BOOT2 2 /* 2nd stage (load to DDR) */
> > > + char filename[64]; /* Filename of image */
> > __u8?
> I don't understand why char is not appropriate for a filename.
> Would like to understand why __u8 is correct to use here vs. char.

Why is __u8 not correct? It's the data type we use for ioctls.

> > > +};
> > > +
> > > +/* default firmware images names */
> > > +#define VK_BOOT1_DEF_VALKYRIE_FILENAME "vk-boot1.bin"
> > > +#define VK_BOOT2_DEF_VALKYRIE_FILENAME "vk-boot2.bin"
> > > +
> > > +#define VK_BOOT1_DEF_VIPER_FILENAME "vp-boot1.bin"
> > > +#define VK_BOOT2_DEF_VIPER_FILENAME "vp-boot2.bin"
> > Why do these need to be in a uapi .h file? Shouldn't they just be part
> > of the normal MODULE_FIRMWARE() macro in the driver itself?
> ioctl VK_IOCTL_LOAD_IMAGE passes in type of image to load and filename.
> These are the default names used if the images are autoloaded by the driver.

Then put them in the driver, not in the user api file.

> But if userspace app wishes to load (or reload) the default images then it
> needs to know the name of the file to pass in ioctl.

That's up to userspace.

> I guess I could change the API at this point to lookup the default filename
> if NULL filename passed into ioctl.

Yes please.

> > > +struct vk_access {
> > > + __u8 barno; /* BAR number to use */
> > > + __u8 type; /* Type of access */
> > > +#define VK_ACCESS_READ 0
> > > +#define VK_ACCESS_WRITE 1
> > > + __u32 len; /* length of data */
> > Horrible padding issues, are you sure this all works properly?
> Haven't had any issues.

Use pahole to see the holes you have in here and please fix that up.

> > > + __u64 offset; /* offset in BAR */
> > > + __u32 *data; /* where to read/write data to */
> > Are you _SURE_ you want a pointer here? How do you handle the compat
> > issues with 32/64 user/kernel space?
> Don't care about 32-bit user space for this driver.

We all do, see the link that Arnd sent you.

> I don't think there isn't even enough memory in such systems for the number
> of streams of video buffers needed for transcoding.

32bit systems have lots of memory.

> This driver is only used in high end 64-bit x86 servers.

For today, what about in 2 years?

> But, VK_IOCTL_ACCESS_BAR can go away entirely if standard user space
> approach already exists as you imply.

Yes, please use that interface, as you should never duplicate existing
functionality.

> > > +};
> > And isn't this just a normal PCI write thing? Can't you do it from
> > userspace using the existing userspace PCI accesses? Why do you need a
> > special ioctl for it?
> This follows how pci_endpoint_test reads and writes BARS via ioctl.
> It also abstracts the accesses all into the device node being opened.
>
> I am not familiar with userspace PCI accesses.? Would this be through some
> sys entries?

Yes, it can read PCI config space that way, and if you use the uio
interface, you can read PCI memory.

thanks,

greg k-h

2020-02-21 18:20:23

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver



On 2020-02-19 11:47 p.m., Greg Kroah-Hartman wrote:
> On Wed, Feb 19, 2020 at 04:48:24PM -0800, Scott Branden wrote:
>> Add Broadcom VK driver offload engine.
>> This driver interfaces to the VK PCIe offload engine to perform
>> should offload functions as video transcoding on multiple streams
>> in parallel. VK device is booted from files loaded using
>> request_firmware_into_buf mechanism. After booted card status is updated
>> and messages can then be sent to the card.
>> Such messages contain scatter gather list of addresses
>> to pull data from the host to perform operations on.
> Why is this a tty driver?
We have a tty driver here as there are (multiple) console interfaces to
the card.
The only viable interface to the card is through the PCIe bus.  We can't
hook up cables to the card to get at the consoles.
As such we implemented a tty driver to access the console via a circular
buffer in PCIe BAR space.

It is extremely useful.  You get console access to virtual serial ports
connected to each processor inside the VK SoC.
Future enhancement is to connect the tty driver to use an MSIX interrupt
rather than polling.
Once that is done lrz/sz transfers will be much quicker.  I'd also look
at if I could use ser2net to get network access
for the processors on the VK SoC over this interface as well.
>
> Have you worked with the V4L developers to tie this into the proper
> in-kernel apis for this type of functionality?
We looked at the V4L model doesn't have any support for anything we are
doing in this driver.
We also want a driver that doesn't care about video.  It could be
offloading crypto or other operations.
We talked with Olof about all of this previously and he said leave it as
a misc driver for now.
He was going to discuss at linux plumbers conference that we need some
sort of offload engine model that such devices could fit into.
> Using a tty driver seems like the totally incorrect way to do this, what
> am I missing?
tty driver is used to provide console access to the processors running
on vk.
Data is sent using the bcm_vk_msg interface by read/write operations
from user space.
VK then gets the messages and DMA's the data to/from host memory when
needed to process.
>
> Also, do not make up random error values, you return "-1" a lot here,
> that is not ok. Please fix up to return the correct -Ewhatever values
> instead.
OK.
>
> thanks,
>
> greg k-h

2020-02-21 18:28:32

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 5/7] bcm-vk: add bcm_vk UAPI



On 2020-02-21 12:34 a.m., Arnd Bergmann wrote:
> On Fri, Feb 21, 2020 at 2:16 AM Scott Branden
> <[email protected]> wrote:
>
>
>>>> +struct vk_access {
>>>> + __u8 barno; /* BAR number to use */
>>>> + __u8 type; /* Type of access */
>>>> +#define VK_ACCESS_READ 0
>>>> +#define VK_ACCESS_WRITE 1
>>>> + __u32 len; /* length of data */
>>> Horrible padding issues, are you sure this all works properly?
>> Haven't had any issues.
>>>> + __u64 offset; /* offset in BAR */
>>>> + __u32 *data; /* where to read/write data to */
>>> Are you _SURE_ you want a pointer here? How do you handle the compat
>>> issues with 32/64 user/kernel space?
>> Don't care about 32-bit user space for this driver.
>> I don't think there isn't even enough memory in such systems for the
>> number of streams of video buffers needed for transcoding.
>> This driver is only used in high end 64-bit x86 servers.
> Please see Documentation/core-api/ioctl.rst
>
> All ioctl interfaces should be written in a portable way that works with
> compat user space and avoids all padding in order to not leak kernel
> data into user space.
>
> If the driver is passing video buffers for transcoding, shouldn't the driver
> use the existing drivers/media interfaces for that? If it needs features
> that are not present there, they can probably be added.
It doesn't utilize any media interfaces.  It is just an offload engine. 
Really, it could be offloading anything.
There is no infrastructure for other drivers in place that perform such
transcoding operations.
Perhaps I shouldn't mention it is doing video in this driver.
>
> Arnd

2020-02-21 18:30:16

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver



On 2020-02-20 2:43 a.m., Dan Carpenter wrote:
> I'm not sure what git tree you're building on top of. This patchset
> doesn't apply to linux-next which is the only thing I tested...
I am building against master branch.
Will look at differences with linux-next once comments addressed.
> On Wed, Feb 19, 2020 at 04:48:24PM -0800, Scott Branden wrote:
>> Add Broadcom VK driver offload engine.
>> This driver interfaces to the VK PCIe offload engine to perform
>> should offload functions as video transcoding on multiple streams
>> in parallel. VK device is booted from files loaded using
>> request_firmware_into_buf mechanism. After booted card status is updated
>> and messages can then be sent to the card.
>> Such messages contain scatter gather list of addresses
>> to pull data from the host to perform operations on.
>>
>> Signed-off-by: Scott Branden <[email protected]>
>> Signed-off-by: Desmond Yan <[email protected]>
>> Signed-off-by: James Hu <[email protected]>
>> ---
>> drivers/misc/Kconfig | 1 +
>> drivers/misc/Makefile | 1 +
>> drivers/misc/bcm-vk/Kconfig | 42 +
>> drivers/misc/bcm-vk/Makefile | 11 +
>> drivers/misc/bcm-vk/bcm_vk.h | 357 ++++++++
>> drivers/misc/bcm-vk/bcm_vk_dev.c | 1197 ++++++++++++++++++++++++++
>> drivers/misc/bcm-vk/bcm_vk_msg.c | 1359 ++++++++++++++++++++++++++++++
>> drivers/misc/bcm-vk/bcm_vk_msg.h | 210 +++++
>> drivers/misc/bcm-vk/bcm_vk_sg.c | 273 ++++++
>> drivers/misc/bcm-vk/bcm_vk_sg.h | 60 ++
>> drivers/misc/bcm-vk/bcm_vk_tty.c | 327 +++++++
>> 11 files changed, 3838 insertions(+)
>> create mode 100644 drivers/misc/bcm-vk/Kconfig
>> create mode 100644 drivers/misc/bcm-vk/Makefile
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_dev.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_tty.c
>>
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index 7f0d48f406e3..39c74ed6a846 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -478,6 +478,7 @@ source "drivers/misc/genwqe/Kconfig"
>> source "drivers/misc/echo/Kconfig"
>> source "drivers/misc/cxl/Kconfig"
>> source "drivers/misc/ocxl/Kconfig"
>> +source "drivers/misc/bcm-vk/Kconfig"
>> source "drivers/misc/cardreader/Kconfig"
>> source "drivers/misc/habanalabs/Kconfig"
>> endmenu
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index c1860d35dc7e..d9a3b2791560 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -53,6 +53,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
>> obj-$(CONFIG_CXL_BASE) += cxl/
>> obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
>> obj-$(CONFIG_OCXL) += ocxl/
>> +obj-$(CONFIG_BCM_VK) += bcm-vk/
>> obj-y += cardreader/
>> obj-$(CONFIG_PVPANIC) += pvpanic.o
>> obj-$(CONFIG_HABANA_AI) += habanalabs/
>> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
>> new file mode 100644
>> index 000000000000..c75dfb89a38d
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/Kconfig
>> @@ -0,0 +1,42 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# Broadcom VK device
>> +#
>> +config BCM_VK
>> + tristate "Support for Broadcom VK Accelerators"
>> + depends on PCI_MSI
>> + default m
>> + help
>> + Select this option to enable support for Broadcom
>> + VK Accelerators. VK is used for performing
>> + specific video offload processing. This driver enables
>> + userspace programs to access these accelerators via /dev/bcm-vk.N
>> + devices.
>> +
>> + If unsure, say N.
>> +
>> +if BCM_VK
>> +
>> +config BCM_VK_H2VK_VERIFY_AND_RETRY
>> + bool "Host To VK Verifiy Data and Retry"
>> + help
>> + Turn on to verify the data passed down to VK is good,
>> + and if not, do a retry until it succeeds.
>> + This is a debug/workaround on FPGA PCIe timing issues
>> + but may be found useful for debugging other PCIe hardware issues.
>> + Small performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +config BCM_VK_QSTATS
>> + bool "VK Queue Statistics"
>> + help
>> + Turn on to enable Queue Statistics.
>> + These are useful for debugging purposes.
>> + Some performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +endif
>> diff --git a/drivers/misc/bcm-vk/Makefile b/drivers/misc/bcm-vk/Makefile
>> new file mode 100644
>> index 000000000000..05cb213ee826
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/Makefile
>> @@ -0,0 +1,11 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +#
>> +# Makefile for Broadcom VK driver
>> +#
>> +
>> +obj-$(CONFIG_BCM_VK) += bcm_vk.o
>> +bcm_vk-objs := \
>> + bcm_vk_dev.o \
>> + bcm_vk_msg.o \
>> + bcm_vk_sg.o \
>> + bcm_vk_tty.o
>> diff --git a/drivers/misc/bcm-vk/bcm_vk.h b/drivers/misc/bcm-vk/bcm_vk.h
>> new file mode 100644
>> index 000000000000..203ca741bf88
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk.h
>> @@ -0,0 +1,357 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_H
>> +#define BCM_VK_H
>> +
>> +#include <linux/atomic.h>
>> +#include <linux/pci.h>
>> +#include <linux/irq.h>
>> +#include <linux/miscdevice.h>
>> +#include <linux/mutex.h>
>> +#include <linux/sched/signal.h>
>> +#include <linux/tty.h>
>> +#include <linux/uaccess.h>
>> +#include <linux/version.h>
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk_msg.h"
>> +
>> +#define DRV_MODULE_NAME "bcm-vk"
>> +
>> +/*
>> + * Load Image is completed in two stages:
>> + *
>> + * 1) When the VK device boot-up, M7 CPU runs and executes the BootROM.
>> + * The Secure Boot Loader (SBL) as part of the BootROM will run
>> + * to open up ITCM for host to push BOOT1 image.
>> + * SBL will authenticate the image before jumping to BOOT1 image.
>> + *
>> + * 2) Because BOOT1 image is a secured image, we also called it the
>> + * Secure Boot Image (SBI). At second stage, SBI will initialize DDR
>> + * and wait for host to push BOOT2 image to DDR.
>> + * SBI will authenticate the image before jumping to BOOT2 image.
>> + *
>> + */
>> +/* Location of registers of interest in BAR0 */
>> +
>> +/* Request register for Secure Boot Loader (SBL) download */
>> +#define BAR_CODEPUSH_SBL 0x400
>> +/* Start of ITCM */
>> +#define CODEPUSH_BOOT1_ENTRY 0x00400000
>> +#define CODEPUSH_MASK 0xFFFFF000
>> +#define CODEPUSH_BOOTSTART BIT(0)
>> +
>> +/* Boot Status register */
>> +#define BAR_BOOT_STATUS 0x404
>> +
>> +#define SRAM_OPEN BIT(16)
>> +#define DDR_OPEN BIT(17)
>> +
>> +/* Firmware loader progress status definitions */
>> +#define FW_LOADER_ACK_SEND_MORE_DATA BIT(18)
>> +#define FW_LOADER_ACK_IN_PROGRESS BIT(19)
>> +#define FW_LOADER_ACK_RCVD_ALL_DATA BIT(20)
>> +
>> +/* definitions for boot status register */
>> +#define BOOT_STATE_MASK 0xFFF3FFFF
>> +#define BROM_STATUS_NOT_RUN 0x2
>> +#define BROM_NOT_RUN (SRAM_OPEN | BROM_STATUS_NOT_RUN)
>> +#define BROM_STATUS_COMPLETE 0x6
>> +#define BROM_RUNNING (SRAM_OPEN | BROM_STATUS_COMPLETE)
>> +#define BOOT1_STATUS_COMPLETE 0x6
>> +#define BOOT1_RUNNING (DDR_OPEN | BOOT1_STATUS_COMPLETE)
>> +#define BOOT2_STATUS_COMPLETE 0x6
>> +#define BOOT2_RUNNING (FW_LOADER_ACK_RCVD_ALL_DATA | \
>> + BOOT2_STATUS_COMPLETE)
>> +
>> +/* Boot request for Secure Boot Image (SBI) */
>> +#define BAR_CODEPUSH_SBI 0x408
>> +/* 64M mapped to BAR2 */
>> +#define CODEPUSH_BOOT2_ENTRY 0x60000000
>> +
>> +#define BAR_CARD_STATUS 0x410
>> +/* CARD_STATUS definitions */
>> +#define CARD_STATUS_TTYVK0_READY BIT(0)
>> +#define CARD_STATUS_TTYVK1_READY BIT(1)
>> +
>> +#define BAR_METADATA_VERSION 0x440
>> +#define BAR_OS_UPTIME 0x444
>> +#define BAR_CHIP_ID 0x448
>> +
>> +#define BAR_CARD_TEMPERATURE 0x45C
>> +/* defines for all temperature sensor */
>> +#define BCM_VK_TEMP_FIELD_MASK 0xFF
>> +#define BCM_VK_CPU_TEMP_SHIFT 0
>> +#define BCM_VK_DDR0_TEMP_SHIFT 8
>> +#define BCM_VK_DDR1_TEMP_SHIFT 16
>> +
>> +#define BAR_CARD_VOLTAGE 0x460
>> +/* defines for voltage rail conversion */
>> +#define BCM_VK_VOLT_RAIL_MASK 0xFFFF
>> +#define BCM_VK_3P3_VOLT_REG_SHIFT 16
>> +
>> +#define BAR_CARD_ERR_LOG 0x464
>> +/* Error log register bit definition - register for error alerts */
>> +#define ERR_LOG_UECC BIT(0)
>> +#define ERR_LOG_SSIM_BUSY BIT(1)
>> +#define ERR_LOG_AFBC_BUSY BIT(2)
>> +#define ERR_LOG_HIGH_TEMP_ERR BIT(3)
>> +#define ERR_LOG_WDOG_TIMEOUT BIT(4)
>> +#define ERR_LOG_SYS_FAULT BIT(5)
>> +#define ERR_LOG_MEM_ALLOC_FAIL BIT(8)
>> +#define ERR_LOG_LOW_TEMP_WARN BIT(9)
>> +#define ERR_LOG_ECC BIT(10)
>> +/* Alert bit definitions detectd on host */
>> +#define ERR_LOG_HOST_HB_FAIL BIT(14)
>> +#define ERR_LOG_HOST_PCIE_DWN BIT(15)
>> +
>> +#define BAR_CARD_ERR_MEM 0x468
>> +/* defines for mem err, all fields have same width */
>> +#define BCM_VK_MEM_ERR_FIELD_MASK 0xFF
>> +#define BCM_VK_ECC_MEM_ERR_SHIFT 0
>> +#define BCM_VK_UECC_MEM_ERR_SHIFT 8
>> +/* threshold of event occurrence and logs start to come out */
>> +#define BCM_VK_ECC_THRESHOLD 10
>> +#define BCM_VK_UECC_THRESHOLD 1
>> +
>> +#define BAR_CARD_PWR_AND_THRE 0x46C
>> +/* defines for power and temp threshold, all fields have same width */
>> +#define BCM_VK_PWR_AND_THRE_FIELD_MASK 0xFF
>> +#define BCM_VK_LOW_TEMP_THRE_SHIFT 0
>> +#define BCM_VK_HIGH_TEMP_THRE_SHIFT 8
>> +#define BCM_VK_PWR_STATE_SHIFT 16
>> +
>> +#define BAR_CARD_STATIC_INFO 0x470
>> +
>> +#define BAR_BOOTSRC_SELECT 0xC78
>> +/* BOOTSRC definitions */
>> +#define BOOTSRC_SOFT_ENABLE BIT(14)
>> +
>> +/* Card OS Firmware version size */
>> +#define BAR_FIRMWARE_TAG_SIZE 50
>> +#define FIRMWARE_STATUS_PRE_INIT_DONE 0x1F
>> +
>> +/* VK MSG_ID defines */
>> +#define VK_MSG_ID_BITMAP_SIZE 4096
>> +#define VK_MSG_ID_BITMAP_MASK (VK_MSG_ID_BITMAP_SIZE - 1)
>> +#define VK_MSG_ID_OVERFLOW 0xFFFF
>> +
>> +/* VK device supports a maximum of 3 bars */
>> +#define MAX_BAR 3
>> +
>> +/* default number of msg blk for inband SGL */
>> +#define BCM_VK_DEF_IB_SGL_BLK_LEN 16
>> +#define BCM_VK_IB_SGL_BLK_MAX 24
>> +
>> +enum pci_barno {
>> + BAR_0 = 0,
>> + BAR_1,
>> + BAR_2
>> +};
>> +
>> +#define BCM_VK_NUM_TTY 2
>> +
>> +struct bcm_vk_tty {
>> + struct tty_port port;
>> + uint32_t to_offset; /* bar offset to use */
>> + uint32_t to_size; /* to VK buffer size */
>> + uint32_t wr; /* write offset shadow */
>> + uint32_t from_offset; /* bar offset to use */
>> + uint32_t from_size; /* from VK buffer size */
>> + uint32_t rd; /* read offset shadow */
>> +};
>> +
>> +/* VK device max power state, supports 3, full, reduced and low */
>> +#define MAX_OPP 3
>> +#define MAX_CARD_INFO_TAG_SIZE 64
>> +
>> +struct bcm_vk_card_info {
>> + uint32_t version;
>> + char os_tag[MAX_CARD_INFO_TAG_SIZE];
>> + char cmpt_tag[MAX_CARD_INFO_TAG_SIZE];
>> + uint32_t cpu_freq_mhz;
>> + uint32_t cpu_scale[MAX_OPP];
>> + uint32_t ddr_freq_mhz;
>> + uint32_t ddr_size_MB;
>> + uint32_t video_core_freq_mhz;
>> +};
>> +
>> +/*
>> + * Control structure of logging messages from the card. This
>> + * buffer is for logmsg that comes from vk
>> + */
>> +struct bcm_vk_peer_log {
>> + uint32_t rd_idx;
>> + uint32_t wr_idx;
>> + uint32_t buf_size;
>> + uint32_t mask;
>> + char data[0];
>> +};
>> +/* max size per line of peer log */
>> +#define BCM_VK_PEER_LOG_LINE_MAX 256
>> +
>> +struct bcm_vk_hb_ctrl {
>> + struct timer_list timer;
>> + uint32_t last_uptime;
>> + uint32_t lost_cnt;
>> +};
>> +
>> +struct bcm_vk_alert {
>> + uint16_t flags;
>> + uint16_t notfs;
>> +};
>> +
>> +/* some alert counters that the driver will keep track */
>> +struct bcm_vk_alert_cnts {
>> + uint16_t ecc;
>> + uint16_t uecc;
>> +};
>> +
>> +struct bcm_vk {
>> + struct pci_dev *pdev;
>> + void __iomem *bar[MAX_BAR];
>> + int num_irqs;
>> +
>> + struct bcm_vk_card_info card_info;
>> +
>> + /* mutex to protect the ioctls */
>> + struct mutex mutex;
>> + struct miscdevice miscdev;
>> + int misc_devid; /* dev id allocated */
>> +
>> + struct tty_driver *tty_drv;
>> + struct timer_list serial_timer;
>> + struct bcm_vk_tty tty[BCM_VK_NUM_TTY];
>> +
>> + /* Reference-counting to handle file operations */
>> + struct kref kref;
>> +
>> + spinlock_t msg_id_lock;
>> + uint16_t msg_id;
>> + DECLARE_BITMAP(bmap, VK_MSG_ID_BITMAP_SIZE);
>> + spinlock_t ctx_lock;
>> + struct bcm_vk_ctx ctx[VK_CMPT_CTX_MAX];
>> + struct bcm_vk_ht_entry pid_ht[VK_PID_HT_SZ];
>> + struct task_struct *reset_ppid; /* process that issue reset */
>> +
>> + atomic_t msgq_inited; /* indicate if info has been synced with vk */
>> + struct bcm_vk_msg_chan h2vk_msg_chan;
>> + struct bcm_vk_msg_chan vk2h_msg_chan;
> These two names are almost the same except the letters are jumbled up.
> Please, figure out better names because the human mind sees them as
> exactly the same at first glance.
>
> https://www.mnn.com/lifestyle/arts-culture/stories/why-your-brain-can-read-jumbled-letters
>
>
>> +
>> + struct workqueue_struct *wq_thread;
>> + struct work_struct wq_work; /* work queue for deferred job */
>> + unsigned long wq_offload[1]; /* various flags on wq requested */
>> + void *tdma_vaddr; /* test dma segment virtual addr */
>> + dma_addr_t tdma_addr; /* test dma segment bus addr */
>> +
>> + struct notifier_block panic_nb;
>> + uint32_t ib_sgl_size; /* size allocated for inband sgl insertion */
>> +
>> + /* heart beat mechanism control structure */
>> + struct bcm_vk_hb_ctrl hb_ctrl;
>> + /* house-keeping variable of error logs */
>> + spinlock_t host_alert_lock; /* protection to access host_alert struct */
>> + struct bcm_vk_alert host_alert;
>> + struct bcm_vk_alert peer_alert; /* bits set by the card */
>> + struct bcm_vk_alert_cnts alert_cnts;
>> +
>> + /* offset of the peer log control in BAR2 */
>> + uint32_t peerlog_off;
>> +};
>> +
>> +/* wq offload work items bits definitions */
>> +enum bcm_vk_wq_offload_flags {
>> + BCM_VK_WQ_DWNLD_PEND = 0,
>> + BCM_VK_WQ_DWNLD_AUTO = 1,
>> + BCM_VK_WQ_NOTF_PEND = 2,
>> +};
>> +
>> +/* a macro to get an individual field with mask and shift */
>> +#define BCM_VK_EXTRACT_FIELD(_field, _reg, _mask, _shift) \
>> + (_field = (((_reg) >> (_shift)) & (_mask)))
>> +
>> +/* structure that is used to faciliate displaying of register content */
>> +struct bcm_vk_entry {
>> + const uint32_t mask;
>> + const uint32_t exp_val;
>> + const char *str;
>> +};
>> +
>> +/* alerts that could be generated from peer */
>> +#define BCM_VK_PEER_ERR_NUM 9
>> +extern struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM];
>> +/* alerts detected by the host */
>> +#define BCM_VK_HOST_ERR_NUM 2
>> +extern struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM];
>> +
>> +/*
>> + * check if PCIe interface is down on read. Use it when it is
>> + * certain that _val should never be all ones.
>> + */
>> +#define BCM_VK_INTF_IS_DOWN(val) ((val) == 0xFFFFFFFF)
>> +
>> +static inline u32 vkread32(struct bcm_vk *vk,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + u32 value;
>> +
>> + value = ioread32(vk->bar[bar] + offset);
>> + return value;
>
> return ioread32(vk->bar[bar] + offset);
>
>> +}
>> +
>> +static inline void vkwrite32(struct bcm_vk *vk,
>> + u32 value,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + iowrite32(value, vk->bar[bar] + offset);
>> +}
>> +
>> +static inline u8 vkread8(struct bcm_vk *vk,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + u8 value;
>> +
>> + value = ioread8(vk->bar[bar] + offset);
>> + return value;
> return ioread8(vk->bar[bar] + offset);
>
>> +}
>> +
>> +static inline void vkwrite8(struct bcm_vk *vk,
>> + u8 value,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + iowrite8(value, vk->bar[bar] + offset);
>> +}
>> +
>> +int bcm_vk_open(struct inode *inode, struct file *p_file);
>> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
>> + loff_t *f_pos);
>> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
>> + size_t count, loff_t *f_pos);
>> +int bcm_vk_release(struct inode *inode, struct file *p_file);
>> +void bcm_vk_release_data(struct kref *kref);
>> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id);
>> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id);
>> +int bcm_vk_msg_init(struct bcm_vk *vk);
>> +void bcm_vk_msg_remove(struct bcm_vk *vk);
>> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync);
>> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk);
>> +void bcm_vk_blk_drv_access(struct bcm_vk *vk);
>> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type, pid_t pid);
>> +void bcm_vk_trigger_reset(struct bcm_vk *vk);
>> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num, uint32_t db_val);
>> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk);
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name);
>> +void bcm_vk_tty_exit(struct bcm_vk *vk);
>> +void bcm_vk_hb_init(struct bcm_vk *vk);
>> +void bcm_vk_hb_deinit(struct bcm_vk *vk);
>> +void bcm_vk_handle_notf(struct bcm_vk *vk);
>> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk);
>> +
>> +#endif
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
>> new file mode 100644
>> index 000000000000..d8f59e898ca8
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
>> @@ -0,0 +1,1197 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/firmware.h>
>> +#include <linux/fs.h>
>> +#include <linux/idr.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/module.h>
>> +#include <linux/pci.h>
>> +#include <linux/pci_regs.h>
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk.h"
>> +
>> +#define PCI_DEVICE_ID_VALKYRIE 0x5E87
>> +#define PCI_DEVICE_ID_VIPER 0x5E88
>> +
>> +static DEFINE_IDA(bcm_vk_ida);
>> +
>> +struct load_image_tab {
>> + const uint32_t image_type;
>> + const char *image_name;
> Indented too far.
>
>> +};
>> +
>> +enum soc_idx {
>> + VALKYRIE = 0,
>> + VIPER
>> +};
>> +
>> +#define NUM_BOOT_STAGES 2
>> +const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
>> + [VALKYRIE] = {
>> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VALKYRIE_FILENAME},
>> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VALKYRIE_FILENAME}
>> + },
>> + [VIPER] = {
>> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VIPER_FILENAME},
>> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VIPER_FILENAME}
>> + }
>> +};
>> +
>> +/* Location of memory base addresses of interest in BAR1 */
>> +/* Load Boot1 to start of ITCM */
>> +#define BAR1_CODEPUSH_BASE_BOOT1 0x100000
>> +
>> +/* Allow minimum 1s for Load Image timeout responses */
>> +#define LOAD_IMAGE_TIMEOUT_MS (1 * MSEC_PER_SEC)
>> +
>> +/* Image startup timeouts */
>> +#define BOOT1_STARTUP_TIMEOUT_MS (5 * MSEC_PER_SEC)
>> +#define BOOT2_STARTUP_TIMEOUT_MS (10 * MSEC_PER_SEC)
>> +
>> +/* 1ms wait for checking the transfer complete status */
>> +#define TXFR_COMPLETE_TIMEOUT_MS 1
>> +
>> +/* MSIX usages */
>> +#define VK_MSIX_MSGQ_MAX 3
>> +#define VK_MSIX_NOTF_MAX 1
>> +#define VK_MSIX_IRQ_MAX (VK_MSIX_MSGQ_MAX + VK_MSIX_NOTF_MAX)
>> +
>> +/* Number of bits set in DMA mask*/
>> +#define BCM_VK_DMA_BITS 64
>> +
>> +/* deinit time for the card os after receiving doorbell */
>> +#define BCM_VK_DEINIT_TIME_MS (2 * MSEC_PER_SEC)
>> +
>> +/*
>> + * module parameters
>> + */
>> +static bool auto_load = true;
>> +module_param(auto_load, bool, 0444);
>> +MODULE_PARM_DESC(auto_load,
>> + "Load images automatically at PCIe probe time.\n");
>> +static uint nr_scratch_pages = VK_BAR1_SCRATCH_DEF_NR_PAGES;
>> +module_param(nr_scratch_pages, uint, 0444);
>> +MODULE_PARM_DESC(nr_scratch_pages,
>> + "Number of pre allocated DMAable coherent pages.\n");
>> +static uint nr_ib_sgl_blk = BCM_VK_DEF_IB_SGL_BLK_LEN;
>> +module_param(nr_ib_sgl_blk, uint, 0444);
>> +MODULE_PARM_DESC(nr_ib_sgl_blk,
>> + "Number of in-band msg blks for short SGL.\n");
>> +
>> +/*
>> + * alerts that could be generated from peer
>> + */
>> +struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM] = {
>> + {ERR_LOG_UECC, ERR_LOG_UECC, "uecc"},
>> + {ERR_LOG_SSIM_BUSY, ERR_LOG_SSIM_BUSY, "ssim_busy"},
>> + {ERR_LOG_AFBC_BUSY, ERR_LOG_AFBC_BUSY, "afbc_busy"},
>> + {ERR_LOG_HIGH_TEMP_ERR, ERR_LOG_HIGH_TEMP_ERR, "high_temp"},
>> + {ERR_LOG_WDOG_TIMEOUT, ERR_LOG_WDOG_TIMEOUT, "wdog_timeout"},
>> + {ERR_LOG_SYS_FAULT, ERR_LOG_SYS_FAULT, "sys_fault"},
>> + {ERR_LOG_MEM_ALLOC_FAIL, ERR_LOG_MEM_ALLOC_FAIL, "malloc_fail warn"},
>> + {ERR_LOG_LOW_TEMP_WARN, ERR_LOG_LOW_TEMP_WARN, "low_temp warn"},
>> + {ERR_LOG_ECC, ERR_LOG_ECC, "ecc"},
>> +};
>> +/* alerts detected by the host */
>> +struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM] = {
>> + {ERR_LOG_HOST_PCIE_DWN, ERR_LOG_HOST_PCIE_DWN, "PCIe_down"},
>> + {ERR_LOG_HOST_HB_FAIL, ERR_LOG_HOST_HB_FAIL, "hb_fail"},
>> +};
>> +
>> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id)
>> +{
>> + struct bcm_vk *vk = dev_id;
>> +
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> + dev_err(&vk->pdev->dev,
>> + "Interrupt %d received when msgq not inited\n", irq);
>> + goto skip_schedule_work;
>> + }
>> +
>> + /* if notification is not pending, set bit and schedule work */
>> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> +skip_schedule_work:
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void bcm_vk_log_notf(struct bcm_vk *vk,
>> + struct bcm_vk_alert *alert,
>> + struct bcm_vk_entry const *entry_tab,
>> + const uint32_t table_size)
>> +{
>> + uint32_t i;
>> + uint32_t masked_val, latched_val;
>> + struct bcm_vk_entry const *entry;
>> + uint32_t reg;
>> + uint16_t ecc_mem_err, uecc_mem_err;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + for (i = 0; i < table_size; i++) {
>> + entry = &entry_tab[i];
>> + masked_val = entry->mask & alert->notfs;
>> + latched_val = entry->mask & alert->flags;
>> +
>> + if (masked_val == ERR_LOG_UECC) {
>> + /*
>> + * if there is difference between stored cnt and it
>> + * is greater than threshold, log it.
>> + */
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
>> + BCM_VK_EXTRACT_FIELD(uecc_mem_err, reg,
>> + BCM_VK_MEM_ERR_FIELD_MASK,
>> + BCM_VK_UECC_MEM_ERR_SHIFT);
>> + if ((uecc_mem_err != vk->alert_cnts.uecc) &&
>> + (uecc_mem_err >= BCM_VK_UECC_THRESHOLD))
>> + dev_info(dev,
>> + "ALERT! %s.%d uecc RAISED - ErrCnt %d\n",
>> + DRV_MODULE_NAME, vk->misc_devid,
>> + uecc_mem_err);
>> + vk->alert_cnts.uecc = uecc_mem_err;
>> + } else if (masked_val == ERR_LOG_ECC) {
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
>> + BCM_VK_EXTRACT_FIELD(ecc_mem_err, reg,
>> + BCM_VK_MEM_ERR_FIELD_MASK,
>> + BCM_VK_ECC_MEM_ERR_SHIFT);
>> + if ((ecc_mem_err != vk->alert_cnts.ecc) &&
>> + (ecc_mem_err >= BCM_VK_ECC_THRESHOLD))
>> + dev_info(dev, "ALERT! %s.%d ecc RAISED - ErrCnt %d\n",
>> + DRV_MODULE_NAME, vk->misc_devid,
>> + ecc_mem_err);
>> + vk->alert_cnts.ecc = ecc_mem_err;
>> + } else if (masked_val != latched_val) {
>> + /* print a log as info */
>> + dev_info(dev, "ALERT! %s.%d %s %s\n",
>> + DRV_MODULE_NAME, vk->misc_devid, entry->str,
>> + masked_val ? "RAISED" : "CLEARED");
>> + }
>> + }
>> +}
>> +
>> +static void bcm_vk_dump_peer_log(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_peer_log log, *p_ctl;
>> + char loc_buf[BCM_VK_PEER_LOG_LINE_MAX];
>> + int cnt;
>> + struct device *dev = &vk->pdev->dev;
>> + uint data_offset;
>> +
>> + p_ctl = vk->bar[BAR_2] + vk->peerlog_off;
> Have you tried running Sparse on this driver?
>
>> + log = *p_ctl;
>> + /* do a rmb() to make sure log is updated */
>> + rmb();
>> +
>> + dev_dbg(dev, "Peer PANIC: Size 0x%x(0x%x), [Rd Wr] = [%d %d]\n",
>> + log.buf_size, log.mask, log.rd_idx, log.wr_idx);
>> +
>> + cnt = 0;
>> + data_offset = vk->peerlog_off + sizeof(struct bcm_vk_peer_log);
>> + while (log.rd_idx != log.wr_idx) {
>> + loc_buf[cnt] = vkread8(vk, BAR_2, data_offset + log.rd_idx);
>> +
>> + if ((loc_buf[cnt] == '\0') ||
>> + (cnt == (BCM_VK_PEER_LOG_LINE_MAX - 1))) {
>> + dev_err(dev, "%s", loc_buf);
>> + cnt = 0;
>> + } else {
>> + cnt++;
>> + }
>> + log.rd_idx = (log.rd_idx + 1) & log.mask;
>> + }
>> + /* update rd idx at the end */
>> + vkwrite32(vk, log.rd_idx, BAR_2, vk->peerlog_off);
>> +}
>> +
>> +void bcm_vk_handle_notf(struct bcm_vk *vk)
>> +{
>> + uint32_t reg;
>> + struct bcm_vk_alert alert;
>> + bool intf_down;
>> + unsigned long flags;
>> +
>> + /* handle peer alerts and then locally detected ones */
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_LOG);
>> + intf_down = BCM_VK_INTF_IS_DOWN(reg);
>> + if (!intf_down) {
>> + vk->peer_alert.notfs = reg;
>> + bcm_vk_log_notf(vk, &vk->peer_alert, bcm_vk_peer_err,
>> + ARRAY_SIZE(bcm_vk_peer_err));
>> + vk->peer_alert.flags = vk->peer_alert.notfs;
>> + } else {
>> + /* turn off access */
>> + bcm_vk_blk_drv_access(vk);
>> + }
>> +
>> + /* check and make copy of alert with lock and then free lock */
>> + spin_lock_irqsave(&vk->host_alert_lock, flags);
>> + if (intf_down)
>> + vk->host_alert.notfs |= ERR_LOG_HOST_PCIE_DWN;
>> +
>> + alert = vk->host_alert;
>> + vk->host_alert.flags = vk->host_alert.notfs;
>> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
>> +
>> + /* call display with copy */
>> + bcm_vk_log_notf(vk, &alert, bcm_vk_host_err,
>> + ARRAY_SIZE(bcm_vk_host_err));
>> +
>> + /*
>> + * If it is a sys fault or heartbeat timeout, we would like extract
>> + * log msg from the card so that we would know what is the last fault
>> + */
>> + if ((!intf_down) &&
>> + ((vk->host_alert.flags & ERR_LOG_HOST_HB_FAIL) ||
>> + (vk->peer_alert.flags & ERR_LOG_SYS_FAULT)))
>> + bcm_vk_dump_peer_log(vk);
>> +}
>> +
>> +static inline int bcm_vk_wait(struct bcm_vk *vk, enum pci_barno bar,
>> + uint64_t offset, u32 mask, u32 value,
>> + unsigned long timeout_ms)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
>> + u32 rd_val;
>> +
>> + do {
>> + rd_val = vkread32(vk, bar, offset);
>> + dev_dbg(dev, "BAR%d Offset=0x%llx: 0x%x\n",
>> + bar, offset, rd_val);
>> +
>> + if (time_after(jiffies, timeout))
>> + return -ETIMEDOUT;
>> +
>> + cpu_relax();
>> + cond_resched();
>> + } while ((rd_val & mask) != value);
>> +
>> + return 0;
>> +}
>> +
>> +static void bcm_vk_get_card_info(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t offset;
>> + int i;
>> + uint8_t *dst;
>> + struct bcm_vk_card_info *info = &vk->card_info;
>> +
>> + /* first read the offset from spare register */
>> + offset = vkread32(vk, BAR_0, BAR_CARD_STATIC_INFO);
>> + offset &= (pci_resource_len(vk->pdev, BAR_2 * 2) - 1);
>> +
>> + /* based on the offset, read info to internal card info structure */
>> + dst = (uint8_t *)info;
>> + for (i = 0; i < sizeof(*info); i++)
>> + *dst++ = vkread8(vk, BAR_2, offset++);
>> +
>> +#define CARD_INFO_LOG_FMT "version : %x\n" \
>> + "os_tag : %s\n" \
>> + "cmpt_tag : %s\n" \
>> + "cpu_freq : %d MHz\n" \
>> + "cpu_scale : %d full, %d lowest\n" \
>> + "ddr_freq : %d MHz\n" \
>> + "ddr_size : %d MB\n" \
>> + "video_freq: %d MHz\n"
>> + dev_dbg(dev, CARD_INFO_LOG_FMT, info->version, info->os_tag,
>> + info->cmpt_tag, info->cpu_freq_mhz, info->cpu_scale[0],
>> + info->cpu_scale[MAX_OPP - 1], info->ddr_freq_mhz,
>> + info->ddr_size_MB, info->video_core_freq_mhz);
>> +
>> + /* get the peer log pointer, only need the offset */
>> + vk->peerlog_off = offset;
>> +}
>> +
>> +static int bcm_vk_sync_card_info(struct bcm_vk *vk)
>> +{
>> + uint32_t rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> +
>> + /* check for marker, but allow diags mode to skip sync */
>> + if (!bcm_vk_msgq_marker_valid(vk))
>> + return (rdy_marker == VK_BAR1_DIAG_RDY_MARKER ? 0 : -EINVAL);
>> +
>> + /*
>> + * Write down scratch addr which is used for DMA. For
>> + * signed part, BAR1 is accessible only after boot2 has come
>> + * up
>> + */
>> + if (vk->tdma_addr) {
>> + vkwrite32(vk, vk->tdma_addr >> 32, BAR_1,
>> + VK_BAR1_SCRATCH_OFF_LO);
>> + vkwrite32(vk, (uint32_t)vk->tdma_addr, BAR_1,
>> + VK_BAR1_SCRATCH_OFF_HI);
>> + vkwrite32(vk, nr_scratch_pages * PAGE_SIZE, BAR_1,
>> + VK_BAR1_SCRATCH_SZ_ADDR);
>> + }
>> +
>> + /* get static card info, only need to read once */
>> + bcm_vk_get_card_info(vk);
>> +
>> + return 0;
>> +}
>> +
>> +void bcm_vk_blk_drv_access(struct bcm_vk *vk)
>> +{
>> + int i;
>> +
>> + /*
>> + * kill all the apps except for the process that is resetting.
>> + * If not called during reset, reset_ppid == NULL, and all will be
>> + * killed.
>> + */
>> + spin_lock(&vk->ctx_lock);
>> +
>> + /* set msgq_inited to 0 so that all rd/wr will be blocked */
>> + atomic_set(&vk->msgq_inited, 0);
>> +
>> + for (i = 0; i < VK_PID_HT_SZ; i++) {
>> + struct bcm_vk_ctx *ctx;
>> +
>> + list_for_each_entry(ctx, &vk->pid_ht[i].head, node) {
>> + if (ctx->ppid != vk->reset_ppid) {
>> + dev_dbg(&vk->pdev->dev,
>> + "Send kill signal to pid %d\n",
>> + task_pid_nr(ctx->ppid));
>> + kill_pid(task_pid(ctx->ppid), SIGKILL, 1);
>> + }
>> + }
>> + }
>> + spin_unlock(&vk->ctx_lock);
>> +}
>> +
>> +static void bcm_vk_buf_notify(struct bcm_vk *vk, void *bufp,
>> + dma_addr_t host_buf_addr, uint32_t buf_size)
>> +{
>> + /* update the dma address to the card */
>> + vkwrite32(vk, host_buf_addr >> 32, BAR_1,
>> + VK_BAR1_DMA_BUF_OFF_HI);
>> + vkwrite32(vk, (uint32_t)host_buf_addr, BAR_1,
>> + VK_BAR1_DMA_BUF_OFF_LO);
>> + vkwrite32(vk, buf_size, BAR_1, VK_BAR1_DMA_BUF_SZ);
>> +}
>> +
>> +static int bcm_vk_load_image_by_type(struct bcm_vk *vk, u32 load_type,
>> + const char *filename)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + const struct firmware *fw;
>> + void *bufp;
>> + size_t max_buf;
>> + int ret;
>> + uint64_t offset_codepush;
>> + u32 codepush;
>> + u32 value;
>> + dma_addr_t boot2_dma_addr;
>> +
>> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
>> + /*
>> + * After POR, enable VK soft BOOTSRC so bootrom do not clear
>> + * the pushed image (the TCM memories).
>> + */
>> + value = vkread32(vk, BAR_0, BAR_BOOTSRC_SELECT);
>> + value |= BOOTSRC_SOFT_ENABLE;
>> + vkwrite32(vk, value, BAR_0, BAR_BOOTSRC_SELECT);
>> +
>> + codepush = CODEPUSH_BOOTSTART + CODEPUSH_BOOT1_ENTRY;
>> + offset_codepush = BAR_CODEPUSH_SBL;
>> +
>> + /* Write a 1 to request SRAM open bit */
>> + vkwrite32(vk, CODEPUSH_BOOTSTART, BAR_0, offset_codepush);
>> +
>> + /* Wait for VK to respond */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, SRAM_OPEN,
>> + SRAM_OPEN, LOAD_IMAGE_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "boot1 timeout\n");
>> + goto err_out;
>> + }
>> +
>> + bufp = vk->bar[BAR_1] + BAR1_CODEPUSH_BASE_BOOT1;
>> + max_buf = SZ_256K;
>> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
>> + codepush = CODEPUSH_BOOT2_ENTRY;
>> + offset_codepush = BAR_CODEPUSH_SBI;
>> +
>> + /* Wait for VK to respond */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, DDR_OPEN,
>> + DDR_OPEN, LOAD_IMAGE_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "boot2 timeout\n");
>> + goto err_out;
>> + }
>> +
>> + max_buf = SZ_4M;
>> + bufp = dma_alloc_coherent(dev,
>> + max_buf,
>> + &boot2_dma_addr, GFP_KERNEL);
>> + if (!bufp) {
>> + dev_err(dev, "Error allocating 0x%zx\n", max_buf);
>> + ret = -ENOMEM;
>> + goto err_out;
>> + }
>> +
>> + bcm_vk_buf_notify(vk, bufp, boot2_dma_addr, max_buf);
>> + } else {
>> + dev_err(dev, "Error invalid image type 0x%x\n", load_type);
>> + ret = -EINVAL;
>> + goto err_out;
>> + }
>> +
>> + ret = request_firmware_into_buf(&fw, filename, dev,
>> + bufp, max_buf, 0,
>> + KERNEL_PREAD_FLAG_PART);
>> + if (ret) {
>> + dev_err(dev, "Error %d requesting firmware file: %s\n",
>> + ret, filename);
>> + goto err_out;
>> + }
>> + dev_dbg(dev, "size=0x%zx\n", fw->size);
>> +
>> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n", codepush, offset_codepush);
>> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
>> +
>> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
>> + /* wait until done */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
>> + BOOT1_RUNNING,
>> + BOOT1_RUNNING,
>> + BOOT1_STARTUP_TIMEOUT_MS);
>> + if (ret) {
>> + dev_err(dev,
>> + "Timeout %ld ms waiting for boot1 to come up\n",
>> + BOOT1_STARTUP_TIMEOUT_MS);
>> + goto err_firmware_out;
>> + }
>> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
>> + unsigned long timeout = jiffies + msecs_to_jiffies(
>> + LOAD_IMAGE_TIMEOUT_MS);
>> +
>> + /* To send more data to VK than max_buf allowed at a time */
>> + do {
>> + /*
>> + * Check for ack from card. when Ack is received,
>> + * it means all the data is received by card.
>> + * Exit the loop after ack is received.
>> + */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
>> + FW_LOADER_ACK_RCVD_ALL_DATA,
>> + FW_LOADER_ACK_RCVD_ALL_DATA,
>> + TXFR_COMPLETE_TIMEOUT_MS);
>> + if (ret == 0) {
>> + dev_info(dev, "Exit boot2 download\n");
>> + break;
>> + }
>> +
>> + /* exit the loop, if there is no response from card */
>> + if (time_after(jiffies, timeout)) {
>> + dev_err(dev, "Error. No reply from card\n");
>> + ret = -ETIMEDOUT;
>> + goto err_firmware_out;
>> + }
>> +
>> + /* Wait for VK to open BAR space to copy new data */
>> + ret = bcm_vk_wait(vk, BAR_0, offset_codepush,
>> + codepush, 0,
>> + TXFR_COMPLETE_TIMEOUT_MS);
>> + if (ret == 0) {
>> + ret = request_firmware_into_buf(
>> + &fw,
>> + filename,
>> + dev, bufp,
>> + max_buf,
>> + fw->size,
>> + KERNEL_PREAD_FLAG_PART);
>> + if (ret) {
>> + dev_err(dev, "Error %d requesting firmware file: %s offset: 0x%zx\n",
>> + ret, filename,
>> + fw->size);
>> + goto err_firmware_out;
>> + }
>> + dev_dbg(dev, "size=0x%zx\n", fw->size);
>> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n",
>> + codepush, offset_codepush);
>> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
>> + /* reload timeout after every codepush */
>> + timeout = jiffies + msecs_to_jiffies(
>> + LOAD_IMAGE_TIMEOUT_MS);
>> + }
>> + } while (1);
>> +
>> + /* wait for fw status bits to indicate app ready */
>> + ret = bcm_vk_wait(vk, BAR_0, VK_BAR_FWSTS,
>> + VK_FWSTS_READY,
>> + VK_FWSTS_READY,
>> + BOOT2_STARTUP_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "Boot2 not ready - timeout\n");
>> + goto err_firmware_out;
>> + }
>> +
>> + /*
>> + * Next, initialize Message Q if we are loading boot2.
>> + * Do a force sync
>> + */
>> + ret = bcm_vk_sync_msgq(vk, true);
>> + if (ret) {
>> + dev_err(dev, "Boot2 Error reading comm msg Q info\n");
>> + ret = -EIO;
>> + goto err_firmware_out;
>> + }
>> +
>> + /* sync & channel other info */
>> + ret = bcm_vk_sync_card_info(vk);
>> + if (ret) {
>> + dev_err(dev, "Syncing Card Info failure\n");
>> + goto err_firmware_out;
>> + }
>> + }
>> +
>> +err_firmware_out:
>> + release_firmware(fw);
>> +
>> +err_out:
>> + if (load_type == VK_IMAGE_TYPE_BOOT2)
>> + dma_free_coherent(dev, max_buf, bufp, boot2_dma_addr);
> bufp is uninitialized. Never use the label name "err" or "out" because
> it's always wrong. This is just a combination of the two.
>
>> +
>> + return ret;
>> +}
>> +
>> +static u32 bcm_vk_next_boot_image(struct bcm_vk *vk)
>> +{
>> + uint32_t boot_status;
>> + uint32_t fw_status;
>> + u32 load_type = 0; /* default for unknown */
>> +
>> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> +
>> + if (!BCM_VK_INTF_IS_DOWN(boot_status) && (boot_status & SRAM_OPEN))
>> + load_type = VK_IMAGE_TYPE_BOOT1;
>> + else if (boot_status == BOOT1_RUNNING)
>> + load_type = VK_IMAGE_TYPE_BOOT2;
>> +
>> + /*
>> + * TO_FIX: For now, like to know what value we get everytime
>> + * for debugging.
>> + */
>> + dev_info(&vk->pdev->dev,
>> + "boot-status value for next image: 0x%x : fw-status 0x%x\n",
>> + boot_status, fw_status);
>> +
>> + return load_type;
>> +}
>> +
>> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk)
>> +{
>> + int i, id, ret = -1;
>> + struct device *dev = &vk->pdev->dev;
>> + struct pci_dev *pdev = to_pci_dev(dev);
>> + uint32_t curr_type;
>> + const char *curr_name;
>> +
>> + switch (pdev->device) {
>> + case PCI_DEVICE_ID_VALKYRIE:
>> + id = VALKYRIE;
>> + break;
>> +
>> + case PCI_DEVICE_ID_VIPER:
>> + id = VIPER;
>> + break;
>> +
>> + default:
>> + dev_err(dev, "no images for 0x%x\n", pdev->device);
>> + goto bcm_vk_auto_load_all_exit;
>> + }
>> +
>> + /* log a message to know the relative loading order */
>> + dev_info(dev, "Load All for device %d\n", vk->misc_devid);
>> +
>> + for (i = 0; i < NUM_BOOT_STAGES; i++) {
>> + curr_type = image_tab[id][i].image_type;
>> + if (bcm_vk_next_boot_image(vk) == curr_type) {
>> + curr_name = image_tab[id][i].image_name;
>> + ret = bcm_vk_load_image_by_type(vk, curr_type,
>> + curr_name);
>> + dev_info(dev, "Auto load %s, ret %d\n",
>> + curr_name, ret);
>> +
>> + if (ret) {
>> + dev_err(dev, "Error loading default %s\n",
>> + curr_name);
>> + goto bcm_vk_auto_load_all_exit;
>> + }
>> + }
>> + }
>> +
>> +bcm_vk_auto_load_all_exit:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_trigger_autoload(struct bcm_vk *vk)
>> +{
>> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0)
>> + return -EPERM;
>> +
>> + set_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> + return 0;
>> +}
>> +
>> +static long bcm_vk_load_image(struct bcm_vk *vk, struct vk_image *arg)
>> +{
>> + int ret;
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_image image;
>> + u32 next_loadable;
>> +
>> + if (copy_from_user(&image, arg, sizeof(image))) {
>> + ret = -EACCES;
>> + goto bcm_vk_load_image_exit;
>> + }
>> +
>> + /* first check if fw is at the right state for the download */
> What does this comment mean? Just delete it. The code is clear on
> its own.
>
>> + next_loadable = bcm_vk_next_boot_image(vk);
>> + if (next_loadable != image.type) {
>> + dev_err(dev, "Next expected image %u, Loading %u\n",
>> + next_loadable, image.type);
>> + ret = -EPERM;
>> + goto bcm_vk_load_image_exit;
>> + }
>> +
>> + /*
>> + * if something is pending download already. This could only happen
>> + * for now when the driver is being loaded, or if someone has issued
>> + * another download command in another shell.
>> + */
>> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0) {
>> + dev_err(dev, "Download operation already pending.\n");
>> + return -EPERM;
> Woohoo for the direct return! Just make this function use direct
> returns everywhere and delete all the do nothing returns.
>
>> + }
>> +
>> + ret = bcm_vk_load_image_by_type(vk, image.type, image.filename);
>> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
>> +
>> +bcm_vk_load_image_exit:
>> + return ret;
>> +}
>> +
>> +static long bcm_vk_access_bar(struct bcm_vk *vk, struct vk_access *arg)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_access access;
>> + long ret = -EINVAL;
>> + u32 value;
>> + long i;
>> + long num;
>> +
>> + if (copy_from_user(&access, arg, sizeof(struct vk_access))) {
>> + ret = -EACCES;
> return -EFAULT;
>
>> + goto err_out;
>> + }
>> +
>> + /* do some range checking in the barno and offset */
>> + if (access.barno >= MAX_BAR) {
>> + dev_err(dev, "invalid bar no %d\n", access.barno);
>> + goto err_out;
>> + } else if ((access.offset + access.len) >
>> + pci_resource_len(vk->pdev, access.barno * 2)) {
> There is an arithmatic overlfow. Also len should be a multiple of
> sizeof(u32).
>
> } else if (access.len % sizeof(u32) ||
> access.offset + access.len < access.offset ||
> access.offset + access.len > pci_resource_len(vk->pdev,
> access.barno * 2)) {
>
>
>> + dev_err(dev, "invalid bar offset 0x%llx, len 0x%x\n",
>> + access.offset, access.len);
>> + goto err_out;
>> + }
>> +
>> + if (access.type == VK_ACCESS_READ) {
>> + dev_dbg(dev, "read barno:%d offset:0x%llx len:0x%x\n",
>> + access.barno, access.offset, access.len);
>> + num = access.len / sizeof(u32);
>> + for (i = 0; i < num; i++) {
>> + value = vkread32(vk, access.barno,
>> + access.offset + (i * sizeof(u32)));
>> + ret = put_user(value, access.data + i);
>> + if (ret)
>> + goto err_out;
>> +
>> + dev_dbg(dev, "0x%x\n", value);
>> + }
>> + } else if (access.type == VK_ACCESS_WRITE) {
>> + dev_dbg(dev, "write barno:%d offset:0x%llx len:0x%x\n",
>> + access.barno, access.offset, access.len);
>> + num = access.len / sizeof(u32);
>> + for (i = 0; i < num; i++) {
>> + ret = get_user(value, access.data + i);
>> + if (ret)
>> + goto err_out;
>> +
>> + vkwrite32(vk, value, access.barno,
>> + access.offset + (i * sizeof(u32)));
>> + dev_dbg(dev, "0x%x\n", value);
>> + }
>> + } else {
>> + dev_dbg(dev, "invalid access type %d\n", access.type);
>> + }
>> +err_out:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_reset_successful(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + u32 fw_status, reset_reason;
>> + int ret = -EAGAIN;
>> +
>> + /*
>> + * Reset could be triggered when the card in several state:
>> + * i) in bootROM
>> + * ii) after boot1
>> + * iii) boot2 running
>> + *
>> + * i) & ii) - no status bits will be updated. If vkboot1
>> + * runs automatically after reset, it will update the reason
>> + * to be unknown reason
>> + * iii) - reboot reason match + deinit done.
>> + */
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> + /* immediate exit if interface goes down */
>> + if (BCM_VK_INTF_IS_DOWN(fw_status)) {
>> + dev_err(dev, "PCIe Intf Down!\n");
>> + goto bcm_vk_reset_exit;
>> + }
>> +
>> + /* initial check on reset reason */
> This comment doesn't add anything.
>
>> + reset_reason = (fw_status & VK_FWSTS_RESET_REASON_MASK);
>> + if ((reset_reason == VK_FWSTS_RESET_MBOX_DB)
>> + || (reset_reason == VK_FWSTS_RESET_UNKNOWN))
>> + ret = 0;
> Please use checkpatch.pl --strict.
>
> if ((reset_reason == VK_FWSTS_RESET_MBOX_DB) ||
> (reset_reason == VK_FWSTS_RESET_UNKNOWN))
> ret = 0;
>
>
>> +
>> + /*
>> + * if some of the deinit bits are set, but done
>> + * bit is not, this is a failure if triggered while boot2 is running
>> + */
>> + if ((fw_status & VK_FWSTS_DEINIT_TRIGGERED)
>> + && !(fw_status & VK_FWSTS_RESET_DONE))
>> + ret = -EAGAIN;
>> +
>> +bcm_vk_reset_exit:
>> + dev_dbg(dev, "FW status = 0x%x ret %d\n", fw_status, ret);
>> +
>> + return ret;
>> +}
>> +
>> +static long bcm_vk_reset(struct bcm_vk *vk, struct vk_reset *arg)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_reset reset;
>> + int ret = 0;
>> +
>> + if (copy_from_user(&reset, arg, sizeof(struct vk_reset))) {
>> + ret = -EACCES;
>
> return -EFAULT;
>
>> + goto err_out;
>> + }
>> + dev_info(dev, "Issue Reset\n");
>> +
>> + /*
>> + * The following is the sequence of reset:
>> + * - send card level graceful shut down
>> + * - wait enough time for VK to handle its business, stopping DMA etc
>> + * - kill host apps
>> + * - Trigger interrupt with DB
>> + */
>> + bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_GRACEFUL, 0);
>> +
>> + spin_lock(&vk->ctx_lock);
>> + if (!vk->reset_ppid) {
>> + vk->reset_ppid = current;
>> + } else {
>> + dev_err(dev, "Reset already launched by process pid %d\n",
>> + task_pid_nr(vk->reset_ppid));
>> + ret = -EACCES;
>> + }
>> + spin_unlock(&vk->ctx_lock);
>> + if (ret)
>> + goto err_out;
>> +
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_trigger_reset(vk);
>> +
>> + /*
>> + * Wait enough time for card os to deinit
>> + * and populate the reset reason.
>> + */
>> + msleep(BCM_VK_DEINIT_TIME_MS);
>> +
>> + ret = bcm_vk_reset_successful(vk);
>> +
>> +err_out:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_mmap(struct file *file, struct vm_area_struct *vma)
>> +{
>> + struct bcm_vk_ctx *ctx = file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + unsigned long pg_size;
>> +
>> + /* only BAR2 is mmap possible, which is bar num 4 due to 64bit */
>> +#define VK_MMAPABLE_BAR 4
>> +
>> + pg_size = ((pci_resource_len(vk->pdev, VK_MMAPABLE_BAR) - 1)
>> + >> PAGE_SHIFT) + 1;
>> + if (vma->vm_pgoff + vma_pages(vma) > pg_size)
>> + return -EINVAL;
>> +
>> + vma->vm_pgoff += (pci_resource_start(vk->pdev, VK_MMAPABLE_BAR)
>> + >> PAGE_SHIFT);
>> + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
>> +
>> + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
>> + vma->vm_end - vma->vm_start,
>> + vma->vm_page_prot);
>> +}
>> +
>> +static long bcm_vk_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
>> +{
>> + long ret = -EINVAL;
>> + struct bcm_vk_ctx *ctx = file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + void __user *argp = (void __user *)arg;
>> +
>> + dev_dbg(&vk->pdev->dev,
>> + "ioctl, cmd=0x%02x, arg=0x%02lx\n",
>> + cmd, arg);
>> +
>> + mutex_lock(&vk->mutex);
>> +
>> + switch (cmd) {
>> + case VK_IOCTL_LOAD_IMAGE:
>> + ret = bcm_vk_load_image(vk, argp);
>> + break;
>> +
>> + case VK_IOCTL_ACCESS_BAR:
>> + ret = bcm_vk_access_bar(vk, argp);
>> + break;
>> +
>> + case VK_IOCTL_RESET:
>> + ret = bcm_vk_reset(vk, argp);
>> + break;
>> +
>> + default:
>> + break;
>> + }
>> +
>> + mutex_unlock(&vk->mutex);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct file_operations bcm_vk_fops = {
>> + .owner = THIS_MODULE,
>> + .open = bcm_vk_open,
>> + .read = bcm_vk_read,
>> + .write = bcm_vk_write,
>> + .release = bcm_vk_release,
>> + .mmap = bcm_vk_mmap,
>> + .unlocked_ioctl = bcm_vk_ioctl,
>> +};
>> +
>> +static int bcm_vk_on_panic(struct notifier_block *nb,
>> + unsigned long e, void *p)
>> +{
>> + struct bcm_vk *vk = container_of(nb, struct bcm_vk, panic_nb);
>> +
>> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_HARD);
>> +
>> + return 0;
>> +}
>> +
>> +static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
>> +{
>> + int err;
>> + int i;
>> + int id;
>> + int irq;
>> + char name[20];
>> + struct bcm_vk *vk;
>> + struct device *dev = &pdev->dev;
>> + struct miscdevice *misc_device;
>> + uint32_t boot_status;
>> +
>> + /* allocate vk structure which is tied to kref for freeing */
>> + vk = kzalloc(sizeof(*vk), GFP_KERNEL);
>> + if (!vk)
>> + return -ENOMEM;
>> +
>> + kref_init(&vk->kref);
>> + if (nr_ib_sgl_blk > BCM_VK_IB_SGL_BLK_MAX) {
>> + dev_warn(dev, "Inband SGL blk %d limited to max %d\n",
>> + nr_ib_sgl_blk, BCM_VK_IB_SGL_BLK_MAX);
>> + nr_ib_sgl_blk = BCM_VK_IB_SGL_BLK_MAX;
>> + }
>> + vk->ib_sgl_size = nr_ib_sgl_blk * VK_MSGQ_BLK_SIZE;
>> + vk->pdev = pdev;
>> + mutex_init(&vk->mutex);
>> +
>> + err = pci_enable_device(pdev);
>> + if (err) {
>> + dev_err(dev, "Cannot enable PCI device\n");
>> + return err;
>> + }
>> +
>> + err = pci_request_regions(pdev, DRV_MODULE_NAME);
>> + if (err) {
>> + dev_err(dev, "Cannot obtain PCI resources\n");
>> + goto err_disable_pdev;
>> + }
>> +
>> + /* make sure DMA is good */
>> + err = dma_set_mask_and_coherent(&pdev->dev,
>> + DMA_BIT_MASK(BCM_VK_DMA_BITS));
>> + if (err) {
>> + dev_err(dev, "failed to set DMA mask\n");
>> + goto err_disable_pdev;
>> + }
>> +
>> + /* The tdma is a scratch area for some DMA testings. */
>> + if (nr_scratch_pages) {
>> + vk->tdma_vaddr = dma_alloc_coherent(dev,
>> + nr_scratch_pages * PAGE_SIZE,
>> + &vk->tdma_addr, GFP_KERNEL);
>> + if (!vk->tdma_vaddr) {
>> + err = -ENOMEM;
>> + goto err_disable_pdev;
>> + }
>> + }
>> +
>> + pci_set_master(pdev);
>> + pci_set_drvdata(pdev, vk);
>> +
>> + irq = pci_alloc_irq_vectors(pdev,
>> + 1,
>> + VK_MSIX_IRQ_MAX,
>> + PCI_IRQ_MSI | PCI_IRQ_MSIX);
>> +
>> + if (irq < VK_MSIX_IRQ_MAX) {
>> + dev_err(dev, "failed to get %d MSIX interrupts, ret(%d)\n",
>> + VK_MSIX_IRQ_MAX, irq);
>> + err = (irq >= 0) ? -EINVAL : irq;
>> + goto err_disable_pdev;
>> + }
>> +
>> + dev_info(dev, "Number of IRQs %d allocated.\n", irq);
>> +
>> + for (i = 0; i < MAX_BAR; i++) {
>> + /* multiple by 2 for 64 bit BAR mapping */
>> + vk->bar[i] = pci_ioremap_bar(pdev, i * 2);
>> + if (!vk->bar[i]) {
>> + dev_err(dev, "failed to remap BAR%d\n", i);
>> + goto err_iounmap;
>> + }
>> + }
>> +
>> + for (vk->num_irqs = 0;
>> + vk->num_irqs < VK_MSIX_MSGQ_MAX;
>> + vk->num_irqs++) {
>> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
>> + bcm_vk_msgq_irqhandler,
>> + IRQF_SHARED, DRV_MODULE_NAME, vk);
>> + if (err) {
>> + dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n",
>> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
>> + goto err_irq;
>> + }
>> + }
>> + /* one irq for notification from VK */
>> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
>> + bcm_vk_notf_irqhandler,
>> + IRQF_SHARED, DRV_MODULE_NAME, vk);
>> + if (err) {
>> + dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n",
>> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
>> + goto err_irq;
>> + }
>> + vk->num_irqs++;
>> +
>> + id = ida_simple_get(&bcm_vk_ida, 0, 0, GFP_KERNEL);
>> + if (id < 0) {
>> + err = id;
>> + dev_err(dev, "unable to get id\n");
>> + goto err_irq;
>> + }
>> +
>> + vk->misc_devid = id;
>> + snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
>> + misc_device = &vk->miscdev;
>> + misc_device->minor = MISC_DYNAMIC_MINOR;
>> + misc_device->name = kstrdup(name, GFP_KERNEL);
>> + if (!misc_device->name) {
>> + err = -ENOMEM;
>> + goto err_ida_remove;
>> + }
>> + misc_device->fops = &bcm_vk_fops,
>> +
>> + err = misc_register(misc_device);
>> + if (err) {
>> + dev_err(dev, "failed to register device\n");
>> + goto err_kfree_name;
>> + }
>> +
>> + err = bcm_vk_msg_init(vk);
>> + if (err) {
>> + dev_err(dev, "failed to init msg queue info\n");
>> + goto err_misc_deregister;
>> + }
>> +
>> + /* sync other info */
>> + bcm_vk_sync_card_info(vk);
>> +
>> + /* register for panic notifier */
>> + vk->panic_nb.notifier_call = bcm_vk_on_panic;
>> + atomic_notifier_chain_register(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> + snprintf(name, sizeof(name), KBUILD_MODNAME ".%d_ttyVK", id);
>> + err = bcm_vk_tty_init(vk, name);
>> + if (err)
>> + goto err_unregister_panic_notifier;
>> +
>> + /*
>> + * lets trigger an auto download. We don't want to do it serially here
>> + * because at probing time, it is not supposed to block for a long time.
>> + */
>> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
>> + if (auto_load) {
>> + if ((boot_status & BOOT_STATE_MASK) == BROM_RUNNING) {
>> + if (bcm_vk_trigger_autoload(vk))
>> + goto err_bcm_vk_tty_exit;
>> + } else {
>> + dev_info(dev,
>> + "Auto-load skipped - BROM not in proper state (0x%x)\n",
>> + boot_status);
>> + }
>> + }
>> +
>> + /* enable hb */
>> + bcm_vk_hb_init(vk);
>> +
>> + dev_info(dev, "BCM-VK:%u created, 0x%p\n", id, vk);
>> +
>> + return 0;
>> +
>> +err_bcm_vk_tty_exit:
>> + bcm_vk_tty_exit(vk);
>> +
>> +err_unregister_panic_notifier:
>> + atomic_notifier_chain_unregister(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> +err_misc_deregister:
>> + misc_deregister(misc_device);
>> +
>> +err_kfree_name:
>> + kfree(misc_device->name);
>> + misc_device->name = NULL;
>> +
>> +err_ida_remove:
>> + ida_simple_remove(&bcm_vk_ida, id);
>> +
>> +err_irq:
>> + for (i = 0; i < vk->num_irqs; i++)
>> + devm_free_irq(dev, pci_irq_vector(pdev, i), vk);
>> +
>> + pci_disable_msix(pdev);
>> + pci_disable_msi(pdev);
>> +
>> +err_iounmap:
>> + for (i = 0; i < MAX_BAR; i++) {
>> + if (vk->bar[i])
>> + pci_iounmap(pdev, vk->bar[i]);
>> + }
>> + pci_release_regions(pdev);
>> +
>> +err_disable_pdev:
>> + pci_disable_device(pdev);
>> +
>> + return err;
>> +}
>> +
>> +void bcm_vk_release_data(struct kref *kref)
>> +{
>> + struct bcm_vk *vk = container_of(kref, struct bcm_vk, kref);
>> +
>> + /* use raw print, as dev is gone */
>> + pr_info("BCM-VK:%d release data 0x%p\n", vk->misc_devid, vk);
>> + kfree(vk);
>> +}
>> +
>> +static void bcm_vk_remove(struct pci_dev *pdev)
>> +{
>> + int i;
>> + struct bcm_vk *vk = pci_get_drvdata(pdev);
>> + struct miscdevice *misc_device = &vk->miscdev;
>> +
>> + bcm_vk_hb_deinit(vk);
>> + bcm_vk_tty_exit(vk);
>> +
>> + /* unregister panic notifier */
>> + atomic_notifier_chain_unregister(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> + bcm_vk_msg_remove(vk);
>> +
>> + if (vk->tdma_vaddr)
>> + dma_free_coherent(&pdev->dev, nr_scratch_pages * PAGE_SIZE,
>> + vk->tdma_vaddr, vk->tdma_addr);
>> +
>> + /* remove if name is set which means misc dev registered */
>> + if (misc_device->name) {
>> + misc_deregister(misc_device);
>> + kfree(misc_device->name);
>> + ida_simple_remove(&bcm_vk_ida, vk->misc_devid);
>> + }
>> + for (i = 0; i < vk->num_irqs; i++)
>> + devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), vk);
>> +
>> + pci_disable_msix(pdev);
>> + pci_disable_msi(pdev);
>> +
>> + cancel_work_sync(&vk->wq_work);
>> + destroy_workqueue(vk->wq_thread);
>> +
>> + for (i = 0; i < MAX_BAR; i++) {
>> + if (vk->bar[i])
>> + pci_iounmap(pdev, vk->bar[i]);
>> + }
>> +
>> + dev_info(&pdev->dev, "BCM-VK:%d released\n", vk->misc_devid);
>> +
>> + pci_release_regions(pdev);
>> + pci_disable_device(pdev);
>> +
>> + kref_put(&vk->kref, bcm_vk_release_data);
>> +}
>> +
>> +static const struct pci_device_id bcm_vk_ids[] = {
>> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VALKYRIE), },
>> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VIPER), },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(pci, bcm_vk_ids);
>> +
>> +static struct pci_driver pci_driver = {
>> + .name = DRV_MODULE_NAME,
>> + .id_table = bcm_vk_ids,
>> + .probe = bcm_vk_probe,
>> + .remove = bcm_vk_remove,
>> +};
>> +module_pci_driver(pci_driver);
>> +
>> +MODULE_DESCRIPTION("Broadcom VK Host Driver");
>> +MODULE_AUTHOR("Scott Branden <[email protected]>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_VERSION("1.0");
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
>> new file mode 100644
>> index 000000000000..651fba2149e2
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
>> @@ -0,0 +1,1359 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/fs.h>
>> +#include <linux/hash.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/list.h>
>> +#include <linux/sizes.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/timer.h>
>> +
>> +#include "bcm_vk.h"
>> +#include "bcm_vk_msg.h"
>> +#include "bcm_vk_sg.h"
>> +
>> +#if defined(CONFIG_BCM_VK_H2VK_VERIFY_AND_RETRY)
>> +/*
>> + * Turn on the following to verify the data passed down to VK is good, and
>> + * if not, do retry. This is a debug/workaround on FPGA PCIe timing issues
>> + * but may be found useful for debugging other PCIe hardware issues.
>> + */
>> +static void bcm_vk_h2vk_verify_idx(struct device *dev,
>> + const char *tag,
>> + volatile uint32_t *idx,
>> + uint32_t expected)
>> +{
>> + unsigned int count = 0;
>> +
>> + while (*idx != expected) {
>> + count++;
>> + dev_err(dev, "[%d] %s exp %d idx %d\n",
>> + count, tag, expected, *idx);
>> +
>> + /* write again */
>> + *idx = expected;
>> + }
>> +}
>> +
>> +static void bcm_vk_h2vk_verify_blk(struct device *dev,
>> + const struct vk_msg_blk *src,
>> + volatile struct vk_msg_blk *dst)
>> +
>> +{
>> + struct vk_msg_blk rd_bck;
>> + unsigned int count = 0;
>> +
>> + rd_bck = *dst;
>> + while (memcmp(&rd_bck, src, sizeof(rd_bck)) != 0) {
>> + count++;
>> + dev_err(dev,
>> + "[%d]Src Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
>> + count,
>> + src->function_id,
>> + src->size,
>> + src->queue_id,
>> + src->msg_id,
>> + src->context_id,
>> + src->args[0],
>> + src->args[1]);
>> + dev_err(dev,
>> + "[%d]Rdb Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
>> + count,
>> + rd_bck.function_id,
>> + rd_bck.size,
>> + rd_bck.queue_id,
>> + rd_bck.msg_id,
>> + rd_bck.context_id,
>> + rd_bck.args[0],
>> + rd_bck.args[1]);
>> +
>> + *dst = *src;
>> + rd_bck = *dst;
>> + }
>> +}
>> +#else
>> +static void bcm_vk_h2vk_verify_idx(struct device __always_unused *dev,
>> + const char __always_unused *tag,
>> + volatile uint32_t __always_unused *idx,
>> + uint32_t __always_unused expected)
>> +{
>> +}
>> +
>> +static void bcm_vk_h2vk_verify_blk
>> + (struct device __always_unused *dev,
>> + const struct vk_msg_blk __always_unused *src,
>> + volatile struct vk_msg_blk __always_unused *dst)
>> +
>> +{
>> +}
>> +#endif
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> +
>> +/* Use default value of 20000 rd/wr per update */
>> +#if !defined(BCM_VK_QSTATS_ACC_CNT)
>> +#define BCM_VK_QSTATS_ACC_CNT 20000
>> +#endif
>> +
>> +void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
>> + struct bcm_vk_qstats *qstats, uint32_t occupancy)
>> +{
>> + struct bcm_vk_qs_cnts *qcnts = &qstats->qcnts;
>> +
>> + if (occupancy > qcnts->max_occ) {
>> + qcnts->max_occ = occupancy;
>> + if (occupancy > qcnts->max_abs)
>> + qcnts->max_abs = occupancy;
>> + }
>> +
>> + qcnts->acc_sum += occupancy;
>> + if (++qcnts->cnt >= BCM_VK_QSTATS_ACC_CNT) {
>> + /* log average and clear counters */
>> + dev_info(&vk->pdev->dev,
>> + "%s[%d]: Max: [%3d/%3d] Acc %d num %d, Aver %d\n",
>> + tag, qstats->q_num,
>> + qcnts->max_occ, qcnts->max_abs,
>> + qcnts->acc_sum,
>> + qcnts->cnt,
>> + qcnts->acc_sum / qcnts->cnt);
>> +
>> + qcnts->cnt = 0;
>> + qcnts->max_occ = 0;
>> + qcnts->acc_sum = 0;
>> + }
>> +}
>> +#endif
>> +
>> +/* number of retries when enqueue message fails before returning EAGAIN */
>> +#define BCM_VK_H2VK_ENQ_RETRY 10
>> +#define BCM_VK_H2VK_ENQ_RETRY_DELAY_MS 50
>> +
>> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk)
>> +{
>> + return (!!atomic_read(&vk->msgq_inited));
>> +}
>> +
>> +static void bcm_vk_set_host_alert(struct bcm_vk *vk, uint32_t bit_mask)
>> +{
>> + struct bcm_vk_alert *alert = &vk->host_alert;
>> + unsigned long flags;
>> +
>> + /* use irqsave version as this maybe called inside timer interrupt */
>> + spin_lock_irqsave(&vk->host_alert_lock, flags);
>> + alert->notfs |= bit_mask;
>> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
>> +
>> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +}
>> +
>> +/*
>> + * Heartbeat related defines
>> + * The heartbeat from host is a last resort. If stuck condition happens
>> + * on the card, firmware is supposed to detect it. Therefore, the heartbeat
>> + * values used will be more relaxed on the driver, which need to be bigger
>> + * than the watchdog timeout on the card.
>> + */
>> +#define BCM_VK_HB_TIMER_S 3
>> +#define BCM_VK_HB_TIMER_VALUE (BCM_VK_HB_TIMER_S * HZ)
>> +#define BCM_VK_HB_LOST_MAX 4
>> +
>> +static void bcm_vk_hb_poll(struct timer_list *t)
>> +{
>> + uint32_t uptime_s;
>> + struct bcm_vk_hb_ctrl *hb = container_of(t, struct bcm_vk_hb_ctrl,
>> + timer);
>> + struct bcm_vk *vk = container_of(hb, struct bcm_vk, hb_ctrl);
>> +
>> + if (bcm_vk_drv_access_ok(vk)) {
>> + /* read uptime from register and compare */
>> + uptime_s = vkread32(vk, BAR_0, BAR_OS_UPTIME);
>> +
>> + if (uptime_s == hb->last_uptime)
>> + hb->lost_cnt++;
>> +
>> + dev_dbg(&vk->pdev->dev, "Last uptime %d current %d, lost %d\n",
>> + hb->last_uptime, uptime_s, hb->lost_cnt);
>> +
>> + /*
>> + * if the interface goes down without any activity, a value
>> + * of 0xFFFFFFFF will be continuously read, and the detection
>> + * will be happened eventually.
>> + */
>> + hb->last_uptime = uptime_s;
>> + } else {
>> + /* reset heart beat lost cnt */
>> + hb->lost_cnt = 0;
>> + }
>> +
>> + /* next, check if heartbeat exceeds limit */
>> + if (hb->lost_cnt > BCM_VK_HB_LOST_MAX) {
>> + dev_err(&vk->pdev->dev, "Heartbeat Misses %d times, %d s!\n",
>> + BCM_VK_HB_LOST_MAX,
>> + BCM_VK_HB_LOST_MAX * BCM_VK_HB_TIMER_S);
>> +
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_HB_FAIL);
>> + }
>> + /* re-arm timer */
>> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
>> +}
>> +
>> +void bcm_vk_hb_init(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
>> +
>> + timer_setup(&hb->timer, bcm_vk_hb_poll, 0);
>> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
>> +}
>> +
>> +void bcm_vk_hb_deinit(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
>> +
>> + del_timer(&hb->timer);
>> +}
>> +
>> +static void bcm_vk_msgid_bitmap_clear(struct bcm_vk *vk,
>> + unsigned int start,
>> + unsigned int nbits)
>> +{
>> + spin_lock(&vk->msg_id_lock);
>> + bitmap_clear(vk->bmap, start, nbits);
>> + spin_unlock(&vk->msg_id_lock);
>> +}
>> +
>> +/*
>> + * allocate a ctx per file struct
>> + */
>> +static struct bcm_vk_ctx *bcm_vk_get_ctx(struct bcm_vk *vk,
>> + struct task_struct *ppid)
>> +{
>> + uint32_t i;
>> + struct bcm_vk_ctx *ctx = NULL;
>> + const pid_t pid = task_pid_nr(ppid);
>> + uint32_t hash_idx = hash_32(pid, VK_PID_HT_SHIFT_BIT);
>> +
>> + spin_lock(&vk->ctx_lock);
>> +
>> + /* check if it is in reset, if so, don't allow */
>> + if (vk->reset_ppid) {
>> + dev_err(&vk->pdev->dev,
>> + "No context allowed during reset by pid %d\n",
>> + task_pid_nr(vk->reset_ppid));
>> +
>> + goto in_reset_exit;
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
>> + if (!vk->ctx[i].in_use) {
>> + vk->ctx[i].in_use = true;
>> + ctx = &vk->ctx[i];
>> + break;
>> + }
>> + }
>> +
>> + if (!ctx) {
>> + dev_err(&vk->pdev->dev, "All context in use\n");
>> +
>> + goto all_in_use_exit;
>> + }
>> +
>> + /* set the pid and insert it to hash table */
>> + ctx->ppid = ppid;
>> + ctx->hash_idx = hash_idx;
>> + list_add_tail(&ctx->node, &vk->pid_ht[hash_idx].head);
>> +
>> + /* increase kref */
>> + kref_get(&vk->kref);
>> +
>> + /* clear counter */
>> + ctx->pend_cnt = 0;
>> +all_in_use_exit:
>> +in_reset_exit:
>> + spin_unlock(&vk->ctx_lock);
>> +
>> + return ctx;
>> +}
>> +
>> +static uint16_t bcm_vk_get_msg_id(struct bcm_vk *vk)
>> +{
>> + uint16_t rc = VK_MSG_ID_OVERFLOW;
>> + uint16_t test_bit_count = 0;
>> +
>> + spin_lock(&vk->msg_id_lock);
>> + while (test_bit_count < VK_MSG_ID_BITMAP_SIZE) {
>> + vk->msg_id++;
>> + vk->msg_id = vk->msg_id & VK_MSG_ID_BITMAP_MASK;
>> + if (test_bit(vk->msg_id, vk->bmap)) {
>> + test_bit_count++;
>> + continue;
>> + }
>> + rc = vk->msg_id;
>> + bitmap_set(vk->bmap, vk->msg_id, 1);
>> + break;
>> + }
>> + spin_unlock(&vk->msg_id_lock);
>> +
>> + return rc;
>> +}
>> +
>> +static int bcm_vk_free_ctx(struct bcm_vk *vk, struct bcm_vk_ctx *ctx)
>> +{
>> + uint32_t idx;
>> + uint32_t hash_idx;
>> + pid_t pid;
>> + struct bcm_vk_ctx *entry;
>> + int count = 0;
>> +
>> + if (ctx == NULL) {
>> + dev_err(&vk->pdev->dev, "NULL context detected\n");
>> + return -EINVAL;
>> + }
>> + idx = ctx->idx;
>> + pid = task_pid_nr(ctx->ppid);
>> +
>> + spin_lock(&vk->ctx_lock);
>> +
>> + if (!vk->ctx[idx].in_use) {
>> + dev_err(&vk->pdev->dev, "context[%d] not in use!\n", idx);
>> + } else {
>> + vk->ctx[idx].in_use = false;
>> + vk->ctx[idx].miscdev = NULL;
>> +
>> + /* Remove it from hash list and see if it is the last one. */
>> + list_del(&ctx->node);
>> + hash_idx = ctx->hash_idx;
>> + list_for_each_entry(entry, &vk->pid_ht[hash_idx].head, node) {
>> + if (task_pid_nr(entry->ppid) == pid)
>> + count++;
>> + }
>> + }
>> +
>> + spin_unlock(&vk->ctx_lock);
>> +
>> + return count;
>> +}
>> +
>> +static void bcm_vk_free_wkent(struct device *dev, struct bcm_vk_wkent *entry)
>> +{
>> + bcm_vk_sg_free(dev, entry->dma, VK_DMA_MAX_ADDRS);
>> +
>> + kfree(entry->vk2h_msg);
>> + kfree(entry);
>> +}
>> +
>> +static void bcm_vk_drain_all_pend(struct device *dev,
>> + struct bcm_vk_msg_chan *chan,
>> + struct bcm_vk_ctx *ctx)
>> +{
>> + uint32_t num;
>> + struct bcm_vk_wkent *entry, *tmp;
>> + struct bcm_vk *vk;
>> + struct list_head del_q;
>> +
>> + if (ctx)
>> + vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> +
>> + INIT_LIST_HEAD(&del_q);
>> + spin_lock(&chan->pendq_lock);
>> + for (num = 0; num < chan->q_nr; num++) {
>> + list_for_each_entry_safe(entry, tmp, &chan->pendq[num], node) {
>> + if (ctx == NULL) {
>> + list_del(&entry->node);
>> + list_add_tail(&entry->node, &del_q);
>> + } else if (entry->ctx->idx == ctx->idx) {
>> + struct vk_msg_blk *msg;
>> + int bit_set;
>> + bool responded;
>> +
>> + /* if it is specific ctx, log for any stuck */
>> + msg = entry->h2vk_msg;
>> + bit_set = test_bit(msg->msg_id, vk->bmap);
>> + responded = entry->vk2h_msg ? true : false;
>> + dev_info(dev,
>> + "Drained: fid %u size %u msg 0x%x(seq-%x) ctx 0x%x[fd-%d] args:[0x%x 0x%x] resp %s, bmap %d\n",
>> + msg->function_id, msg->size,
>> + msg->msg_id, entry->seq_num,
>> + msg->context_id, entry->ctx->idx,
>> + msg->args[0], msg->args[1],
>> + responded ? "T" : "F", bit_set);
>> + list_del(&entry->node);
>> + list_add_tail(&entry->node, &del_q);
>> + if (responded)
>> + ctx->pend_cnt--;
>> + else if (bit_set)
>> + bcm_vk_msgid_bitmap_clear(vk,
>> + msg->msg_id,
>> + 1);
>> + }
>> + }
>> + }
>> + spin_unlock(&chan->pendq_lock);
>> +
>> + /* batch clean up */
>> + num = 0;
>> + list_for_each_entry_safe(entry, tmp, &del_q, node) {
>> + list_del(&entry->node);
>> + bcm_vk_free_wkent(dev, entry);
>> + num++;
>> + }
>> + if (num)
>> + dev_info(dev, "Total drained items %d\n", num);
>> +}
>> +
>> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk)
>> +{
>> + uint32_t rdy_marker = 0;
>> + uint32_t fw_status;
>> +
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> +
>> + if ((fw_status & VK_FWSTS_READY) == VK_FWSTS_READY)
>> + rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> +
>> + return (rdy_marker == VK_BAR1_MSGQ_RDY_MARKER);
>> +}
>> +
>> +/*
>> + * Function to sync up the messages queue info that is provided by BAR1
>> + */
>> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync)
>> +{
>> + struct bcm_vk_msgq *msgq = NULL;
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t msgq_off;
>> + uint32_t num_q;
>> + struct bcm_vk_msg_chan *chan_list[] = {&vk->h2vk_msg_chan,
>> + &vk->vk2h_msg_chan};
>> + struct bcm_vk_msg_chan *chan = NULL;
>> + int i, j;
>> + int ret = 0;
>> +
>> + /*
>> + * If the driver is loaded at startup where vk OS is not up yet,
>> + * the msgq-info may not be available until a later time. In
>> + * this case, we skip and the sync function is supposed to be
>> + * called again.
>> + */
>> + if (!bcm_vk_msgq_marker_valid(vk)) {
>> + dev_info(dev, "BAR1 msgq marker not initialized.\n");
>> + return ret;
> This is "return 0;" which seems wrong.
>
>> + }
>> +
>> + msgq_off = vkread32(vk, BAR_1, VK_BAR1_MSGQ_CTRL_OFF);
>> +
>> + /* each side is always half the total */
>> + num_q = vk->h2vk_msg_chan.q_nr = vk->vk2h_msg_chan.q_nr =
>> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_NR) / 2;
>> +
>> + /* first msgq location */
>> + msgq = (struct bcm_vk_msgq *)(vk->bar[BAR_1] + msgq_off);
>> +
>> + /*
>> + * if this function is called when it is already inited,
>> + * something is wrong
>> + */
>> + if (bcm_vk_drv_access_ok(vk) && (!force_sync)) {
> Delete extra parens.
>
>> + dev_err(dev, "Msgq info already in sync\n");
>> + ret = -EPERM;
>> + goto already_inited;
> Please do a direct "return -EPERM;"
>
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(chan_list); i++) {
>> + chan = chan_list[i];
>> + memset(chan->sync_qinfo, 0, sizeof(chan->sync_qinfo));
>> +
>> + for (j = 0; j < num_q; j++) {
>> + chan->msgq[j] = msgq;
>> +
>> + dev_info(dev,
>> + "MsgQ[%d] type %d num %d, @ 0x%x, rd_idx %d wr_idx %d, size %d, nxt 0x%x\n",
>> + j,
>> + chan->msgq[j]->type,
>> + chan->msgq[j]->num,
>> + chan->msgq[j]->start,
>> + chan->msgq[j]->rd_idx,
>> + chan->msgq[j]->wr_idx,
>> + chan->msgq[j]->size,
>> + chan->msgq[j]->nxt);
> It would be shorter to replace chan->msgq[j]->type with msgq->type.
>
>> +
>> + /* formulate and record static info */
>> + chan->sync_qinfo[j].q_start =
>> + vk->bar[BAR_1] + chan->msgq[j]->start;
> Maybe set "qinfo = &chan->sync_qinfo[j];" at the start of the loop so
> that these fit on one line.
>
> qinfo->q_start = vk->bar[BAR_1] + msgq->start;
>
>> + chan->sync_qinfo[j].q_size = chan->msgq[j]->size;
>> + /* set low threshold as 50% or 1/2 */
>> + chan->sync_qinfo[j].q_low =
>> + chan->sync_qinfo[j].q_size >> 1;
>> + chan->sync_qinfo[j].q_mask =
>> + chan->sync_qinfo[j].q_size - 1;
>> +
>> + msgq = (struct bcm_vk_msgq *)
>> + ((char *)msgq + sizeof(*msgq) + msgq->nxt);
>> +
>> + rmb(); /* do a read mb to guarantee */
>> + }
>> + }
>> + atomic_set(&vk->msgq_inited, 1);
>> +
>> +already_inited:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_msg_chan_init(struct bcm_vk_msg_chan *chan)
>> +{
>> + int rc = 0;
>> + uint32_t i;
>> +
>> + mutex_init(&chan->msgq_mutex);
>> + spin_lock_init(&chan->pendq_lock);
>> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
>> + INIT_LIST_HEAD(&chan->pendq[i]);
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + chan->qstats[i].q_num = i;
>> +#endif
>> + }
>> +
>> + return rc;
>
> return 0;
>
>> +}
>> +
>> +static void bcm_vk_append_pendq(struct bcm_vk_msg_chan *chan, uint16_t q_num,
>> + struct bcm_vk_wkent *entry)
>> +{
>> + spin_lock(&chan->pendq_lock);
>> + list_add_tail(&entry->node, &chan->pendq[q_num]);
>> + if (entry->vk2h_msg)
>> + entry->ctx->pend_cnt++;
>> + spin_unlock(&chan->pendq_lock);
>> +}
>> +
>> +static uint32_t bcm_vk_append_ib_sgl(struct bcm_vk *vk,
>> + struct bcm_vk_wkent *entry,
>> + struct _vk_data *data,
>> + unsigned int num_planes)
>> +{
>> + unsigned int i;
>> + unsigned int item_cnt = 0;
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
>> + struct vk_msg_blk *msg = &entry->h2vk_msg[0];
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + uint32_t ib_sgl_size = 0;
>> + uint8_t *buf = (uint8_t *)&entry->h2vk_msg[entry->h2vk_blks];
>> + uint32_t avail;
>> +
>> + /* check if high watermark is hit, and if so, skip */
>> + msgq = chan->msgq[msg->queue_id];
>> + qinfo = &chan->sync_qinfo[msg->queue_id];
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> + if (avail < qinfo->q_low) {
>> + dev_dbg(dev, "Skip inserting inband SGL, [0x%x/0x%x]\n",
>> + avail, qinfo->q_size);
>> + return ib_sgl_size;
> return 0;
>
>> + }
>> +
>> + for (i = 0; i < num_planes; i++) {
>> + if (data[i].address &&
>> + (ib_sgl_size + data[i].size) <= vk->ib_sgl_size) {
>> +
>> + item_cnt++;
>> + memcpy(buf, entry->dma[i].sglist, data[i].size);
>> + ib_sgl_size += data[i].size;
>> + buf += data[i].size;
>> + }
>> + }
>> +
>> + dev_dbg(dev, "Num %u sgl items appended, size 0x%x, room 0x%x\n",
>> + item_cnt, ib_sgl_size, vk->ib_sgl_size);
>> +
>> + /* round up size */
>> + ib_sgl_size = (ib_sgl_size + VK_MSGQ_BLK_SIZE - 1)
>> + >> VK_MSGQ_BLK_SZ_SHIFT;
>> +
>> + return ib_sgl_size;
>> +}
>> +
>> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num,
>> + uint32_t db_val)
>> +{
>> + /* press door bell based on q_num */
>> + vkwrite32(vk,
>> + db_val,
>> + BAR_0,
>> + VK_BAR0_REGSEG_DB_BASE + q_num * VK_BAR0_REGSEG_DB_REG_GAP);
>> +}
>> +
>> +static int bcm_h2vk_msg_enqueue(struct bcm_vk *vk, struct bcm_vk_wkent *entry)
>> +{
>> + static uint32_t seq_num;
>> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_msg_blk *src = &entry->h2vk_msg[0];
>> +
>> + volatile struct vk_msg_blk *dst;
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + uint32_t q_num = src->queue_id;
>> + uint32_t wr_idx; /* local copy */
>> + uint32_t i;
>> + uint32_t avail;
>> + uint32_t retry;
>> +
>> + if (entry->h2vk_blks != src->size + 1) {
>> + dev_err(dev, "number of blks %d not matching %d MsgId[0x%x]: func %d ctx 0x%x\n",
>> + entry->h2vk_blks,
>> + src->size + 1,
>> + src->msg_id,
>> + src->function_id,
>> + src->context_id);
>> + return -EMSGSIZE;
>> + }
>> +
>> + msgq = chan->msgq[q_num];
>> + qinfo = &chan->sync_qinfo[q_num];
>> +
>> + rmb(); /* start with a read barrier */
>> + mutex_lock(&chan->msgq_mutex);
>> +
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + bcm_vk_update_qstats(vk, "h2vk", &chan->qstats[q_num],
>> + qinfo->q_size - avail);
>> +#endif
>> + /* if not enough space, return EAGAIN and let app handles it */
>> + retry = 0;
>> + while ((avail < entry->h2vk_blks)
>> + && (retry++ < BCM_VK_H2VK_ENQ_RETRY)) {
>> + mutex_unlock(&chan->msgq_mutex);
>> +
>> + msleep(BCM_VK_H2VK_ENQ_RETRY_DELAY_MS);
>> + mutex_lock(&chan->msgq_mutex);
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> + }
>> + if (retry > BCM_VK_H2VK_ENQ_RETRY) {
>> + mutex_unlock(&chan->msgq_mutex);
>> + return -EAGAIN;
>> + }
>> +
>> + /* at this point, mutex is taken and there is enough space */
>> + entry->seq_num = seq_num++; /* update debug seq number */
>> + wr_idx = msgq->wr_idx;
>> +
>> + if (wr_idx >= qinfo->q_size) {
>> + dev_crit(dev, "Invalid wr_idx 0x%x => max 0x%x!",
>> + wr_idx, qinfo->q_size);
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_PCIE_DWN);
>> + goto idx_err;
>> + }
>> +
>> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
>> + for (i = 0; i < entry->h2vk_blks; i++) {
>> + *dst = *src;
>> +
>> + bcm_vk_h2vk_verify_blk(dev, src, dst);
>> +
>> + src++;
>> + wr_idx = VK_MSGQ_INC(qinfo, wr_idx, 1);
>> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
>> + }
>> +
>> + /* flush the write pointer */
>> + msgq->wr_idx = wr_idx;
>> + wmb(); /* flush */
>> +
>> + bcm_vk_h2vk_verify_idx(dev, "wr_idx", &msgq->wr_idx, wr_idx);
>> +
>> + /* log new info for debugging */
>> + dev_dbg(dev,
>> + "MsgQ[%d] [Rd Wr] = [%d %d] blks inserted %d - Q = [u-%d a-%d]/%d\n",
>> + msgq->num,
>> + msgq->rd_idx, msgq->wr_idx, entry->h2vk_blks,
>> + VK_MSGQ_OCCUPIED(msgq, qinfo),
>> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
>> + msgq->size);
>> + /*
>> + * press door bell based on queue number. 1 is added to the wr_idx
>> + * to avoid the value of 0 appearing on the VK side to distinguish
>> + * from initial value.
>> + */
>> + bcm_h2vk_doorbell(vk, q_num, wr_idx + 1);
>> +idx_err:
>> + mutex_unlock(&chan->msgq_mutex);
>> + return 0;
>> +}
>> +
>> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type,
>> + pid_t pid)
>> +{
>> + int rc = 0;
>> + struct bcm_vk_wkent *entry;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /*
>> + * check if the marker is still good. Sometimes, the PCIe interface may
>> + * have gone done, and if so and we ship down thing based on broken
>> + * values, kernel may panic.
>> + */
>> + if (!bcm_vk_msgq_marker_valid(vk)) {
>> + dev_info(dev, "PCIe comm chan - invalid marker (0x%x)!\n",
>> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY));
>> + return -EINVAL;
>> + }
>> +
>> + entry = kzalloc(sizeof(struct bcm_vk_wkent) +
>> + sizeof(struct vk_msg_blk), GFP_KERNEL);
>> + if (!entry)
>> + return -ENOMEM;
>> +
>> + /* just fill up non-zero data */
>> + entry->h2vk_msg[0].function_id = VK_FID_SHUTDOWN;
>> + entry->h2vk_msg[0].queue_id = 0; /* use highest queue */
>> + entry->h2vk_blks = 1; /* always 1 block */
>> +
>> + entry->h2vk_msg[0].args[0] = shut_type;
>> + entry->h2vk_msg[0].args[1] = pid;
>> +
>> + rc = bcm_h2vk_msg_enqueue(vk, entry);
>> + if (rc)
>> + dev_err(dev,
>> + "Sending shutdown message to q %d for pid %d fails.\n",
>> + entry->h2vk_msg[0].queue_id, pid);
>> +
>> + kfree(entry);
>> +
>> + return rc;
>> +}
>> +
>> +int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
>> +{
>> + int rc = 0;
>> + pid_t pid = task_pid_nr(ppid);
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /*
>> + * don't send down or do anything if message queue is not initialized
>> + * and if it is the reset session, clear it.
>> + */
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> +
>> + if (vk->reset_ppid == ppid)
>> + vk->reset_ppid = NULL;
>> + return -EPERM;
>> + }
>> +
>> + dev_dbg(dev, "No more sessions, shut down pid %d\n", pid);
>> +
>> + /* only need to do it if it is not the reset process */
>> + if (vk->reset_ppid != ppid)
>> + rc = bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_PID, pid);
>> + else
>> + /* reset the pointer if it is exiting last session */
>> + vk->reset_ppid = NULL;
>> +
>> + return rc;
>> +}
>> +
>> +static struct bcm_vk_wkent *bcm_vk_find_pending(struct bcm_vk *vk,
>> + struct bcm_vk_msg_chan *chan,
>> + uint16_t q_num,
>> + uint16_t msg_id)
>> +{
>> + bool found = false;
>> + struct bcm_vk_wkent *entry;
>> +
>> + spin_lock(&chan->pendq_lock);
>> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
>> +
>> + if (entry->h2vk_msg[0].msg_id == msg_id) {
>> + list_del(&entry->node);
>> + found = true;
>> + bcm_vk_msgid_bitmap_clear(vk, msg_id, 1);
>> + break;
>> + }
>> + }
>> + spin_unlock(&chan->pendq_lock);
>> + return ((found) ? entry : NULL);
>> +}
>> +
>> +static uint32_t bcm_vk2h_msg_dequeue(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
>> + struct vk_msg_blk *data;
>> + volatile struct vk_msg_blk *src;
>> + struct vk_msg_blk *dst;
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + struct bcm_vk_wkent *entry;
>> + uint32_t rd_idx;
>> + uint32_t q_num, j;
>> + uint32_t num_blks;
>> + uint32_t total = 0;
>> +
>> + /*
>> + * drain all the messages from the queues, and find its pending
>> + * entry in the h2vk queue, based on msg_id & q_num, and move the
>> + * entry to the vk2h pending queue, waiting for user space
>> + * program to extract
>> + */
>> + mutex_lock(&chan->msgq_mutex);
>> + rmb(); /* start with a read barrier */
>> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
>> + msgq = chan->msgq[q_num];
>> + qinfo = &chan->sync_qinfo[q_num];
>> +
>> + while (!VK_MSGQ_EMPTY(msgq)) {
>> +
>> + /*
>> + * Make a local copy and get pointer to src blk
>> + * The rd_idx is masked before getting the pointer to
>> + * avoid out of bound access in case the interface goes
>> + * down. It will end up pointing to the last block in
>> + * the buffer, but subsequent src->size check would be
>> + * able to catch this.
>> + */
>> + rd_idx = msgq->rd_idx;
>> + src = VK_MSGQ_BLK_ADDR(qinfo,
>> + rd_idx & VK_MSGQ_SIZE_MASK(qinfo));
>> +
>> + if ((rd_idx >= qinfo->q_size)
>> + || (src->size > (qinfo->q_size - 1))) {
>> + dev_crit(dev,
>> + "Invalid rd_idx 0x%x or size 0x%x => max 0x%x!",
>> + rd_idx, src->size, qinfo->q_size);
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(
>> + vk, ERR_LOG_HOST_PCIE_DWN);
>> + goto idx_err;
>> + }
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + bcm_vk_update_qstats(vk, "vk2h", &chan->qstats[q_num],
>> + VK_MSGQ_OCCUPIED(msgq, qinfo));
>> +#endif
>> + num_blks = src->size + 1;
>> + data = kzalloc(num_blks * VK_MSGQ_BLK_SIZE, GFP_KERNEL);
>> + if (data) {
>> + /* copy messages and linearize it */
>> + dst = data;
>> + for (j = 0; j < num_blks; j++) {
>> + *dst = *src;
>> +
>> + dst++;
>> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, 1);
>> + src = VK_MSGQ_BLK_ADDR(qinfo, rd_idx);
>> + }
>> + total++;
>> + } else {
>> + dev_crit(dev, "Error allocating memory\n");
>> + /* just keep draining..... */
>> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, num_blks);
>> + }
>> +
>> + /* flush rd pointer after a message is dequeued */
>> + msgq->rd_idx = rd_idx;
>> + mb(); /* do both rd/wr as we are extracting data out */
>> +
>> + bcm_vk_h2vk_verify_idx(dev, "rd_idx",
>> + &msgq->rd_idx, rd_idx);
>> +
>> + /* log new info for debugging */
>> + dev_dbg(dev,
>> + "MsgQ[%d] [Rd Wr] = [%d %d] blks extracted %d - Q = [u-%d a-%d]/%d\n",
>> + msgq->num,
>> + msgq->rd_idx, msgq->wr_idx, num_blks,
>> + VK_MSGQ_OCCUPIED(msgq, qinfo),
>> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
>> + msgq->size);
>> +
>> + /*
>> + * No need to search if it is an autonomous one-way
>> + * message from driver, as these messages do not bear
>> + * a h2vk pending item. Currently, only the shutdown
>> + * message falls into this category.
>> + */
>> + if (data->function_id == VK_FID_SHUTDOWN) {
>> + kfree(data);
>> + continue;
>> + }
>> +
>> + /* lookup original message in h2vk direction */
>> + entry = bcm_vk_find_pending(vk,
>> + &vk->h2vk_msg_chan,
>> + q_num,
>> + data->msg_id);
>> +
>> + /*
>> + * if there is message to does not have prior send,
>> + * this is the location to add here
>> + */
>> + if (entry) {
>> + entry->vk2h_blks = num_blks;
>> + entry->vk2h_msg = data;
>> + bcm_vk_append_pendq(&vk->vk2h_msg_chan,
>> + q_num, entry);
>> +
>> + } else {
>> + dev_crit(dev,
>> + "Could not find MsgId[0x%x] for resp func %d bmap %d\n",
>> + data->msg_id, data->function_id,
>> + test_bit(data->msg_id, vk->bmap));
>> + kfree(data);
>> + }
>> +
>> + }
>> + }
>> +idx_err:
>> + mutex_unlock(&chan->msgq_mutex);
>> + dev_dbg(dev, "total %d drained from queues\n", total);
>> +
>> + return total;
>> +}
>> +
>> +/*
>> + * deferred work queue for draining and auto download.
>> + */
>> +static void bcm_vk_wq_handler(struct work_struct *work)
>> +{
>> + struct bcm_vk *vk = container_of(work, struct bcm_vk, wq_work);
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t tot;
>> +
>> + /* check wq offload bit map to perform various operations */
>> + if (test_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload)) {
>> + /* clear bit right the way for notification */
>> + clear_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload);
>> + bcm_vk_handle_notf(vk);
>> + }
>> + if (test_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload)) {
>> + bcm_vk_auto_load_all_images(vk);
>> +
>> + /*
>> + * at the end of operation, clear AUTO bit and pending
>> + * bit
>> + */
>> + clear_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
>> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
>> + }
>> +
>> + /* next, try to drain */
>> + tot = bcm_vk2h_msg_dequeue(vk);
>> +
>> + if (tot == 0)
>> + dev_dbg(dev, "Spurious trigger for workqueue\n");
>> +}
>> +
>> +/*
>> + * init routine for all required data structures
>> + */
>> +static int bcm_vk_data_init(struct bcm_vk *vk)
>> +{
>> + int rc = 0;
>> + int i;
>> +
>> + spin_lock_init(&vk->ctx_lock);
>> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
>> + vk->ctx[i].in_use = false;
>> + vk->ctx[i].idx = i; /* self identity */
>> + vk->ctx[i].miscdev = NULL;
>> + }
>> + spin_lock_init(&vk->msg_id_lock);
>> + spin_lock_init(&vk->host_alert_lock);
>> + vk->msg_id = 0;
>> +
>> + /* initialize hash table */
>> + for (i = 0; i < VK_PID_HT_SZ; i++)
>> + INIT_LIST_HEAD(&vk->pid_ht[i].head);
>> +
>> + INIT_WORK(&vk->wq_work, bcm_vk_wq_handler);
>> + return rc;
> return 0;
>
>> +}
>> +
>> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id)
>> +{
>> + struct bcm_vk *vk = dev_id;
>> +
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> + dev_err(&vk->pdev->dev,
>> + "Interrupt %d received when msgq not inited\n", irq);
>> + goto skip_schedule_work;
>> + }
>> +
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> +skip_schedule_work:
>> + return IRQ_HANDLED;
>> +}
>> +
>> +int bcm_vk_open(struct inode *inode, struct file *p_file)
>> +{
>> + struct bcm_vk_ctx *ctx;
>> + struct miscdevice *miscdev = (struct miscdevice *)p_file->private_data;
>> + struct bcm_vk *vk = container_of(miscdev, struct bcm_vk, miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + int rc = 0;
>> +
>> + /* get a context and set it up for file */
>> + ctx = bcm_vk_get_ctx(vk, current);
>> + if (!ctx) {
>> + dev_err(dev, "Error allocating context\n");
>> + rc = -ENOMEM;
>> + } else {
>> +
>> + /*
>> + * set up context and replace private data with context for
>> + * other methods to use. Reason for the context is because
>> + * it is allowed for multiple sessions to open the sysfs, and
>> + * for each file open, when upper layer query the response,
>> + * only those that are tied to a specific open should be
>> + * returned. The context->idx will be used for such binding
>> + */
>> + ctx->miscdev = miscdev;
>> + p_file->private_data = ctx;
>> + dev_dbg(dev, "ctx_returned with idx %d, pid %d\n",
>> + ctx->idx, task_pid_nr(ctx->ppid));
>> + }
>> + return rc;
>> +}
>> +
>> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
>> + loff_t *f_pos)
>> +{
>> + ssize_t rc = -ENOMSG;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
>> + miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
>> + struct bcm_vk_wkent *entry = NULL;
>> + uint32_t q_num;
>> + uint32_t rsp_length;
>> + bool found = false;
>> +
>> + if (!bcm_vk_drv_access_ok(vk))
>> + return -EPERM;
>> +
>> + dev_dbg(dev, "Buf count %ld\n", count);
>> + found = false;
>> +
>> + /*
>> + * search through the pendq on the vk2h chan, and return only those
>> + * that belongs to the same context. Search is always from the high to
>> + * the low priority queues
>> + */
>> + spin_lock(&chan->pendq_lock);
>> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
>> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
>> + if (entry->ctx->idx == ctx->idx) {
>> + if (count >=
>> + (entry->vk2h_blks * VK_MSGQ_BLK_SIZE)) {
>> + list_del(&entry->node);
>> + ctx->pend_cnt--;
>> + found = true;
>> + } else {
>> + /* buffer not big enough */
>> + rc = -EMSGSIZE;
>> + }
>> + goto bcm_vk_read_loop_exit;
>> + }
>> + }
>> + }
>> + bcm_vk_read_loop_exit:
>> + spin_unlock(&chan->pendq_lock);
>> +
>> + if (found) {
>> + /* retrieve the passed down msg_id */
>> + entry->vk2h_msg[0].msg_id = entry->usr_msg_id;
>> + rsp_length = entry->vk2h_blks * VK_MSGQ_BLK_SIZE;
>> + if (copy_to_user(buf, entry->vk2h_msg, rsp_length) == 0)
>> + rc = rsp_length;
> The error code should be -EFAULT if this message fails (vs -ENOMSG).
>
>
>> +
>> + bcm_vk_free_wkent(dev, entry);
>> + } else if (rc == -EMSGSIZE) {
>> + struct vk_msg_blk tmp_msg = entry->vk2h_msg[0];
>> +
>> + /*
>> + * in this case, return just the first block, so
>> + * that app knows what size it is looking for.
>> + */
>> + tmp_msg.msg_id = entry->usr_msg_id;
>> + tmp_msg.size = entry->vk2h_blks - 1;
>> + if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) {
>> + dev_err(dev, "Error return 1st block in -EMSGSIZE\n");
>> + rc = -EFAULT;
>> + }
>> + }
>> + return rc;
>> +}
>> +
>> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
>> + size_t count, loff_t *f_pos)
>> +{
>> + ssize_t rc = -EPERM;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
>> + miscdev);
>> + struct bcm_vk_msgq *msgq;
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_wkent *entry;
>> + uint32_t sgl_extra_blks;
>> +
>> + if (!bcm_vk_drv_access_ok(vk))
>> + return -EPERM;
>> +
>> + dev_dbg(dev, "Msg count %ld\n", count);
>> +
>> + /* first, do sanity check where count should be multiple of basic blk */
>> + if (count & (VK_MSGQ_BLK_SIZE - 1)) {
>> + dev_err(dev, "Failure with size %ld not multiple of %ld\n",
>> + count, VK_MSGQ_BLK_SIZE);
>> + rc = -EBADR;
> #define EBADR 53 /* Invalid request descriptor */
>
> This should just be -EINVAL;
>
>
>> + goto bcm_vk_write_err;
> These label names are too long. They don't need the function prefix.
>
>> + }
>> +
>> + /* allocate the work entry + buffer for size count and inband sgl */
>> + entry = kzalloc(sizeof(struct bcm_vk_wkent) + count + vk->ib_sgl_size,
>> + GFP_KERNEL);
>> + if (!entry) {
>> + rc = -ENOMEM;
>> + goto bcm_vk_write_err;
>> + }
>> +
>> + /* now copy msg from user space, and then formulate the wk ent */
>> + if (copy_from_user(&entry->h2vk_msg[0], buf, count))
>> + goto bcm_vk_write_free_ent;
> This is returning -EPERM instead of -EFAULT.
>
>> +
>> + entry->h2vk_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
>> + entry->ctx = ctx;
>> +
>> + /* do a check on the blk size which could not exceed queue space */
>> + msgq = vk->h2vk_msg_chan.msgq[entry->h2vk_msg[0].queue_id];
>> + if (entry->h2vk_blks + (vk->ib_sgl_size >> VK_MSGQ_BLK_SZ_SHIFT)
>> + > (msgq->size - 1)) {
>> + dev_err(dev, "Blk size %d exceed max queue size allowed %d\n",
>> + entry->h2vk_blks, msgq->size - 1);
>> + rc = -EOVERFLOW;
> This should probably just be -EINVAL as well.
>
>
>> + goto bcm_vk_write_free_ent;
>> + }
>> +
>> + /* Use internal message id */
>> + entry->usr_msg_id = entry->h2vk_msg[0].msg_id;
>> + rc = bcm_vk_get_msg_id(vk);
>> + if (rc == VK_MSG_ID_OVERFLOW) {
>> + dev_err(dev, "msg_id overflow\n");
>> + rc = -EOVERFLOW;
>> + goto bcm_vk_write_free_ent;
>> + }
>> + entry->h2vk_msg[0].msg_id = rc;
>> +
>> + dev_dbg(dev,
>> + "Message ctx id %d, usr_msg_id 0x%x sent msg_id 0x%x\n",
>> + ctx->idx, entry->usr_msg_id,
>> + entry->h2vk_msg[0].msg_id);
>> +
>> + /* Convert any pointers to sg list */
>> + if (entry->h2vk_msg[0].function_id == VK_FID_TRANS_BUF) {
>> + unsigned int num_planes;
>> + int dir;
>> + struct _vk_data *data;
>> +
>> + /*
>> + * check if we are in reset, if so, no buffer transfer is
>> + * allowed and return error.
>> + */
>> + if (vk->reset_ppid) {
>> + dev_dbg(dev, "No Transfer allowed during reset, pid %d.\n",
>> + task_pid_nr(ctx->ppid));
>> + rc = -EACCES;
>> + goto bcm_vk_write_free_msgid;
>> + }
>> +
>> + num_planes = entry->h2vk_msg[0].args[0] & VK_CMD_PLANES_MASK;
>> + if ((entry->h2vk_msg[0].args[0] & VK_CMD_MASK)
>> + == VK_CMD_DOWNLOAD) {
>> + /* Memory transfer from vk device */
> There are a bunch of pretty obvious comments that should just be
> deleted.
>
>> + dir = DMA_FROM_DEVICE;
>> + } else {
>> + /* Memory transfer to vk device */
>> + dir = DMA_TO_DEVICE;
>> + }
>> +
>> + /* Calculate vk_data location */
>> + /* Go to end of the message */
>> + data = (struct _vk_data *)
>> + &(entry->h2vk_msg[entry->h2vk_msg[0].size + 1]);
>> + /* Now back up to the start of the pointers */
>> + data -= num_planes;
>> +
>> + /* Convert user addresses to DMA SG List */
>> + rc = bcm_vk_sg_alloc(dev, entry->dma, dir, data, num_planes);
>> + if (rc)
>> + goto bcm_vk_write_free_msgid;
>> +
>> + /* try to embed inband sgl */
>> + sgl_extra_blks = bcm_vk_append_ib_sgl(vk, entry, data,
>> + num_planes);
>> + entry->h2vk_blks += sgl_extra_blks;
>> + entry->h2vk_msg[0].size += sgl_extra_blks;
>> + }
>> +
>> + /*
>> + * store wk ent to pending queue until a response is got. This needs to
> ^^
> vk?
>
>> + * be done before enqueuing the message
>> + */
>> + bcm_vk_append_pendq(&vk->h2vk_msg_chan, entry->h2vk_msg[0].queue_id,
>> + entry);
>> +
>> + rc = bcm_h2vk_msg_enqueue(vk, entry);
>> + if (rc) {
>> + dev_err(dev, "Fail to enqueue msg to h2vk queue\n");
>> +
>> + /* remove message from pending list */
>> + entry = bcm_vk_find_pending(vk,
>> + &vk->h2vk_msg_chan,
>> + entry->h2vk_msg[0].queue_id,
>> + entry->h2vk_msg[0].msg_id);
>
> Can bcm_vk_find_pending() return NULL here (leading to an Oops when we
> do entry->h2vk_msg[0].msg_id and why can't we just use the previous
> value of entry? The comment doesn't help me.
>
>> + goto bcm_vk_write_free_ent;
>> + }
>> +
>> + return count;
>> +
>> +bcm_vk_write_free_msgid:
>> + bcm_vk_msgid_bitmap_clear(vk, entry->h2vk_msg[0].msg_id, 1);
>> +bcm_vk_write_free_ent:
>> + kfree(entry);
>> +bcm_vk_write_err:
>> + return rc;
>> +}
>> +
>> +int bcm_vk_release(struct inode *inode, struct file *p_file)
>> +{
>> + int ret;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + struct task_struct *ppid = ctx->ppid;
>> + pid_t pid = task_pid_nr(ppid);
>> +
>> + dev_dbg(dev, "Draining with context idx %d pid %d\n",
>> + ctx->idx, pid);
>> +
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, ctx);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, ctx);
>> +
>> + ret = bcm_vk_free_ctx(vk, ctx);
> This returns a positive number.
>
>> + if (ret == 0)
>> + ret = bcm_vk_handle_last_sess(vk, ppid);
>> +
>> + /* free memory if it is the last reference */
>> + kref_put(&vk->kref, bcm_vk_release_data);
>> +
>> + return ret;
> Which we propogate to here. Probably this should just be "return 0;".
>
>> +}
>> +
>> +int bcm_vk_msg_init(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + int err = 0;
>> +
>> + if (bcm_vk_data_init(vk)) {
>> + dev_err(dev, "Error initializing internal data structures\n");
>> + err = -EINVAL;
>> + goto err_out;
>> + }
>> +
>> + if (bcm_vk_msg_chan_init(&vk->h2vk_msg_chan) ||
>> + bcm_vk_msg_chan_init(&vk->vk2h_msg_chan)) {
>> + dev_err(dev, "Error initializing communication channel\n");
>> + err = -EIO;
>> + goto err_out;
>> + }
>> +
>> + /* create dedicated workqueue */
>> + vk->wq_thread = create_singlethread_workqueue(vk->miscdev.name);
>> + if (!vk->wq_thread) {
>> + dev_err(dev, "Fail to create workqueue thread\n");
>> + err = -ENOMEM;
>> + goto err_out;
>> + }
>> +
>> + /* read msgq info */
>> + if (bcm_vk_sync_msgq(vk, false)) {
>> + dev_err(dev, "Error reading comm msg Q info\n");
>> + err = -EIO;
>> + goto err_out;
>> + }
>> +
>> +err_out:
>> + return err;
> This should probably clean up the work queue on error?
>
>> +}
>> +
>> +void bcm_vk_msg_remove(struct bcm_vk *vk)
>> +{
>> + bcm_vk_blk_drv_access(vk);
>> +
>> + /* drain all pending items */
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
>> +}
>> +
>> +void bcm_vk_trigger_reset(struct bcm_vk *vk)
>> +{
>> + uint32_t i;
>> + u32 value;
>> +
>> + /* clean up before pressing the door bell */
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> + /* make tag '\0' terminated */
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_BOOT1_VER_TAG);
>> +
>> + for (i = 0; i < VK_BAR1_DAUTH_MAX; i++) {
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_STORE_ADDR(i));
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_VALID_ADDR(i));
>> + }
>> + for (i = 0; i < VK_BAR1_SOTP_REVID_MAX; i++)
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_SOTP_REVID_ADDR(i));
>> +
>> + memset(&vk->card_info, 0, sizeof(vk->card_info));
>> + memset(&vk->alert_cnts, 0, sizeof(vk->alert_cnts));
>> +
>> + /*
>> + * When boot request fails, the CODE_PUSH_OFFSET stays persistent.
>> + * Allowing us to debug the failure. When we call reset,
>> + * we should clear CODE_PUSH_OFFSET so ROM does not execute
>> + * boot again (and fails again) and instead waits for a new
>> + * codepush.
>> + */
>> + value = vkread32(vk, BAR_0, BAR_CODEPUSH_SBL);
>> + value &= ~CODEPUSH_MASK;
>> + vkwrite32(vk, value, BAR_0, BAR_CODEPUSH_SBL);
>> +
>> + /* reset fw_status with proper reason, and press db */
>> + vkwrite32(vk, VK_FWSTS_RESET_MBOX_DB, BAR_0, VK_BAR_FWSTS);
>> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_SOFT);
>> +
>> + /* clear the uptime register after reset pressed and alert record */
>> + vkwrite32(vk, 0, BAR_0, BAR_OS_UPTIME);
>> + memset(&vk->host_alert, 0, sizeof(vk->host_alert));
>> + memset(&vk->peer_alert, 0, sizeof(vk->peer_alert));
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + /* clear qstats */
>> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
>> + memset(&vk->h2vk_msg_chan.qstats[i].qcnts, 0,
>> + sizeof(vk->h2vk_msg_chan.qstats[i].qcnts));
>> + memset(&vk->vk2h_msg_chan.qstats[i].qcnts, 0,
>> + sizeof(vk->vk2h_msg_chan.qstats[i].qcnts));
>> + }
>> +#endif
>> + /* clear 4096 bits of bitmap */
>> + bitmap_clear(vk->bmap, 0, VK_MSG_ID_BITMAP_SIZE);
>> +}
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.h b/drivers/misc/bcm-vk/bcm_vk_msg.h
>> new file mode 100644
>> index 000000000000..076307b2f1c4
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.h
>> @@ -0,0 +1,210 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_MSG_H
>> +#define BCM_VK_MSG_H
>> +
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +#include "bcm_vk_sg.h"
>> +
>> +/* Single message queue control structure */
>> +struct bcm_vk_msgq {
>> + uint16_t type; /* queue type */
>> + uint16_t num; /* queue number */
>> + uint32_t start; /* offset in BAR1 where the queue memory starts */
>> + volatile uint32_t rd_idx; /* read idx */
>> + volatile uint32_t wr_idx; /* write idx */
>> + uint32_t size; /*
>> + * size, which is in number of 16byte blocks,
>> + * to align with the message data structure.
>> + */
>> + uint32_t nxt; /*
>> + * nxt offset to the next msg queue struct.
>> + * This is to provide flexibity for alignment purposes.
>> + */
>> +};
>> +
>> +/*
>> + * Structure to record static info from the msgq sync. We keep local copy
>> + * for some of these variables for both performance + checking purpose.
>> + */
>> +struct bcm_vk_sync_qinfo {
>> + void __iomem *q_start;
>> + uint32_t q_size;
>> + uint32_t q_mask;
>> + uint32_t q_low;
>> +};
>> +
>> +#define VK_MSGQ_MAX_NR 4 /* Maximum number of message queues */
>> +
>> +/*
>> + * some useful message queue macros
>> + */
>> +
>> +/* vk_msg_blk is 16 bytes fixed */
>> +#define VK_MSGQ_BLK_SIZE (sizeof(struct vk_msg_blk))
>> +/* shift for fast division of basic msg blk size */
>> +#define VK_MSGQ_BLK_SZ_SHIFT 4
>> +
>> +#define VK_MSGQ_EMPTY(_msgq) ((_msgq)->rd_idx == (_msgq)->wr_idx)
>> +
>> +#define VK_MSGQ_SIZE_MASK(_qinfo) ((_qinfo)->q_mask)
>> +
>> +#define VK_MSGQ_INC(_qinfo, _idx, _inc) \
>> + (((_idx) + (_inc)) & VK_MSGQ_SIZE_MASK(_qinfo))
>> +
>> +#define VK_MSGQ_BLK_ADDR(_qinfo, _idx) \
>> + (volatile struct vk_msg_blk *)((_qinfo)->q_start + \
>> + (VK_MSGQ_BLK_SIZE * (_idx)))
>> +
>> +#define VK_MSGQ_OCCUPIED(_msgq, _qinfo) \
>> + (((_msgq)->wr_idx - (_msgq)->rd_idx) & VK_MSGQ_SIZE_MASK(_qinfo))
>> +
>> +#define VK_MSGQ_AVAIL_SPACE(_msgq, _qinfo) \
>> + ((_qinfo)->q_size - VK_MSGQ_OCCUPIED(_msgq, _qinfo) - 1)
>> +
>> +/* context per session opening of sysfs */
>> +struct bcm_vk_ctx {
>> + struct list_head node; /* use for linkage in Hash Table */
>> + uint idx;
>> + bool in_use;
>> + struct task_struct *ppid;
>> + uint32_t hash_idx;
>> + struct miscdevice *miscdev;
>> + int pend_cnt; /* number of items pending to be read from host */
>> +};
>> +
>> +/* pid hash table entry */
>> +struct bcm_vk_ht_entry {
>> + struct list_head head;
>> +};
>> +
>> +#define VK_DMA_MAX_ADDRS 4 /* Max 4 DMA Addresses */
>> +/* structure for house keeping a single work entry */
>> +struct bcm_vk_wkent {
>> +
>> + struct list_head node; /* for linking purpose */
>> + struct bcm_vk_ctx *ctx;
>> +
>> + /* Store up to 4 dma pointers */
>> + struct bcm_vk_dma dma[VK_DMA_MAX_ADDRS];
>> +
>> + uint32_t vk2h_blks; /* response */
>> + struct vk_msg_blk *vk2h_msg;
>> +
>> + /*
>> + * put the h2vk_msg at the end so that we could simply append h2vk msg
>> + * to the end of the allocated block
>> + */
>> + uint32_t usr_msg_id;
>> + uint32_t h2vk_blks;
>> + uint32_t seq_num;
>> + struct vk_msg_blk h2vk_msg[0];
>> +};
>> +
>> +/* queue stats counters */
>> +struct bcm_vk_qs_cnts {
>> + uint32_t cnt; /* general counter, used to limit output */
>> + uint32_t acc_sum;
>> + uint32_t max_occ; /* max during a sampling period */
>> + uint32_t max_abs; /* the abs max since reset */
>> +};
>> +
>> +/* stats structure */
>> +struct bcm_vk_qstats {
>> + uint32_t q_num;
>> + struct bcm_vk_qs_cnts qcnts;
>> +};
>> +
>> +/* control channel structure for either h2vk or vk2h communication */
>> +struct bcm_vk_msg_chan {
>> + uint32_t q_nr;
>> + struct mutex msgq_mutex;
>> + /* pointing to BAR locations */
>> + struct bcm_vk_msgq *msgq[VK_MSGQ_MAX_NR];
>> +
>> + spinlock_t pendq_lock;
>> + /* for temporary storing pending items, one for each queue */
>> + struct list_head pendq[VK_MSGQ_MAX_NR];
>> + /* static queue info from the sync */
>> + struct bcm_vk_sync_qinfo sync_qinfo[VK_MSGQ_MAX_NR];
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + /* qstats */
>> + struct bcm_vk_qstats qstats[VK_MSGQ_MAX_NR];
>> +#endif
>> +};
>> +
>> +/* total number of supported ctx, 32 ctx each for 5 components */
>> +#define VK_CMPT_CTX_MAX (32 * 5)
>> +
>> +/* hash table defines to store the opened FDs */
>> +#define VK_PID_HT_SHIFT_BIT 7 /* 128 */
>> +#define VK_PID_HT_SZ (1 << VK_PID_HT_SHIFT_BIT)
>> +
>> +/* The following are offsets of DDR info provided by the vk card */
>> +#define VK_BAR0_SEG_SIZE (4 * SZ_1K) /* segment size for BAR0 */
>> +
>> +/* shutdown types supported */
>> +#define VK_SHUTDOWN_PID 1
>> +#define VK_SHUTDOWN_GRACEFUL 2
>> +
>> +/*
>> + * first door bell reg, ie for queue = 0. Only need the first one, as
>> + * we will use the queue number to derive the others
>> + */
>> +#define VK_BAR0_REGSEG_DB_BASE 0x484
>> +#define VK_BAR0_REGSEG_DB_REG_GAP 8 /*
>> + * DB register gap,
>> + * DB1 at 0x48c and DB2 at 0x494
>> + */
>> +
>> +/* reset register and specific values */
>> +#define VK_BAR0_RESET_DB_NUM 3
>> +#define VK_BAR0_RESET_DB_SOFT 0xFFFFFFFF
>> +#define VK_BAR0_RESET_DB_HARD 0xFFFFFFFD
>> +
>> +/* BAR1 message q definition */
>> +
>> +/* indicate if msgq ctrl in BAR1 is populated */
>> +#define VK_BAR1_MSGQ_DEF_RDY 0x60c0
>> +/* ready marker value for the above location, normal boot2 */
>> +#define VK_BAR1_MSGQ_RDY_MARKER 0xbeefcafe
>> +/* ready marker value for the above location, normal boot2 */
>> +#define VK_BAR1_DIAG_RDY_MARKER 0xdeadcafe
>> +/* number of msgqs in BAR1 */
>> +#define VK_BAR1_MSGQ_NR 0x60c4
>> +/* BAR1 queue control structure offset */
>> +#define VK_BAR1_MSGQ_CTRL_OFF 0x60c8
>> +/* BAR1 ucode and boot1 version tag */
>> +#define VK_BAR1_UCODE_VER_TAG 0x6170
>> +#define VK_BAR1_BOOT1_VER_TAG 0x61b0
>> +#define VK_BAR1_VER_TAG_SIZE 64
>> +/* Memory to hold the DMA buffer memory address allocated for boot2 download */
>> +#define VK_BAR1_DMA_BUF_OFF_HI 0x61e0
>> +#define VK_BAR1_DMA_BUF_OFF_LO (VK_BAR1_DMA_BUF_OFF_HI + 4)
>> +#define VK_BAR1_DMA_BUF_SZ (VK_BAR1_DMA_BUF_OFF_HI + 8)
>> +/* Scratch memory allocated on host for VK */
>> +#define VK_BAR1_SCRATCH_OFF_LO 0x61f0
>> +#define VK_BAR1_SCRATCH_OFF_HI (VK_BAR1_SCRATCH_OFF_LO + 4)
>> +#define VK_BAR1_SCRATCH_SZ_ADDR (VK_BAR1_SCRATCH_OFF_LO + 8)
>> +#define VK_BAR1_SCRATCH_DEF_NR_PAGES 32
>> + /* BAR1 SOTP AUTH and REVID info */
>> +#define VK_BAR1_DAUTH_BASE_ADDR 0x6200
>> +#define VK_BAR1_DAUTH_STORE_SIZE 0x48
>> +#define VK_BAR1_DAUTH_VALID_SIZE 0x8
>> +#define VK_BAR1_DAUTH_MAX 4
>> +#define VK_BAR1_DAUTH_STORE_ADDR(x) \
>> + (VK_BAR1_DAUTH_BASE_ADDR + \
>> + (x) * (VK_BAR1_DAUTH_STORE_SIZE + VK_BAR1_DAUTH_VALID_SIZE))
>> +#define VK_BAR1_DAUTH_VALID_ADDR(x) \
>> + (VK_BAR1_DAUTH_STORE_ADDR(x) + VK_BAR1_DAUTH_STORE_SIZE)
>> +
>> +#define VK_BAR1_SOTP_REVID_BASE_ADDR 0x6340
>> +#define VK_BAR1_SOTP_REVID_SIZE 0x10
>> +#define VK_BAR1_SOTP_REVID_MAX 2
>> +#define VK_BAR1_SOTP_REVID_ADDR(x) \
>> + (VK_BAR1_SOTP_REVID_BASE_ADDR + (x) * VK_BAR1_SOTP_REVID_SIZE)
>> +
>> +#endif
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.c b/drivers/misc/bcm-vk/bcm_vk_sg.c
>> new file mode 100644
>> index 000000000000..e5de07958f0b
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.c
>> @@ -0,0 +1,273 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +#include <linux/dma-mapping.h>
>> +#include <linux/mm.h>
>> +#include <linux/pagemap.h>
>> +#include <linux/vmalloc.h>
>> +
>> +#include <asm/page.h>
>> +#include <asm/pgtable.h>
>> +#include <asm/unaligned.h>
>> +
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk.h"
>> +#include "bcm_vk_msg.h"
>> +#include "bcm_vk_sg.h"
>> +
>> +/*
>> + * Valkyrie has a hardware limitation of 16M transfer size.
>> + * So limit the SGL chunks to 16M.
>> + */
>> +#define BCM_VK_MAX_SGL_CHUNK SZ_16M
>> +
>> +static int bcm_vk_dma_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata);
>> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
>> +
>> +/* Uncomment to dump SGLIST */
>> +//#define BCM_VK_DUMP_SGLIST
>> +
>> +static int bcm_vk_dma_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int direction,
>> + struct _vk_data *vkdata)
>> +{
>> + dma_addr_t addr, sg_addr;
>> + int err;
>> + int i;
>> + int offset;
>> + uint32_t size;
>> + uint32_t remaining_size;
>> + uint32_t transfer_size;
>> + uint64_t data;
>> + unsigned long first, last;
>> + struct _vk_data *sgdata;
>> +
>> + /* Get 64-bit user address */
>> + data = get_unaligned(&(vkdata->address));
> Extra parens.
>
>> +
>> + /* offset into first page */
>> + offset = offset_in_page(data);
>> +
>> + /* Calculate number of pages */
>> + first = (data & PAGE_MASK) >> PAGE_SHIFT;
>> + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
>> + dma->nr_pages = last - first + 1;
>> +
>> + /* Allocate DMA pages */
>> + dma->pages = kmalloc_array(dma->nr_pages,
>> + sizeof(struct page *),
>> + GFP_KERNEL);
>> + if (dma->pages == NULL)
>> + return -ENOMEM;
>> +
>> + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
>> + data, vkdata->size, dma->nr_pages);
>> +
>> + dma->direction = direction;
>> +
>> + /* Get user pages into memory */
>> + err = get_user_pages_fast(data & PAGE_MASK,
>> + dma->nr_pages,
>> + direction == DMA_FROM_DEVICE,
>> + dma->pages);
>> + if (err != dma->nr_pages) {
>> + dma->nr_pages = (err >= 0) ? err : 0;
>> + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
>> + err, dma->nr_pages);
>> + return err < 0 ? err : -EINVAL;
>> + }
>> +
>> + /* Max size of sg list is 1 per mapped page + fields at start */
>> + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
>> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
>> +
>> + /* Allocate sglist */
>> + dma->sglist = dma_alloc_coherent(dev,
>> + dma->sglen,
>> + &dma->handle,
>> + GFP_KERNEL);
>
> dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
> GFP_KERNEL);
>
>
>
>> + if (!dma->sglist)
>> + return -ENOMEM;
> No cleanup?
>
>> +
>> + dma->sglist[SGLIST_NUM_SG] = 0;
>> + dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
>> + remaining_size = vkdata->size;
>> + sgdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
>> +
>> + /* Map all pages into DMA */
>> + i = 0;
> Set but not used.
>
>> + size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
>> + remaining_size -= size;
>> + sg_addr = dma_map_page(dev,
>> + dma->pages[0],
>> + offset,
>> + size,
>> + dma->direction);
>> + transfer_size = size;
>> + if (unlikely(dma_mapping_error(dev, sg_addr))) {
>> + __free_page(dma->pages[0]);
>> + return -EIO;
>> + }
>> +
>> + for (i = 1; i < dma->nr_pages; i++) {
>> + size = min_t(size_t, PAGE_SIZE, remaining_size);
>> + remaining_size -= size;
>> + addr = dma_map_page(dev,
>> + dma->pages[i],
>> + 0,
>> + size,
>> + dma->direction);
>> + if (unlikely(dma_mapping_error(dev, addr))) {
>> + __free_page(dma->pages[i]);
>> + return -EIO;
>> + }
>> +
>> + /*
>> + * Compress SG list entry when pages are contiguous
>> + * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
>> + */
>> + if ((addr == (sg_addr + transfer_size)) &&
>> + ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
>> + /* pages are contiguous, add to same sg entry */
>> + transfer_size += size;
>> + } else {
>> + /* pages are not contiguous, write sg entry */
>> + sgdata->size = transfer_size;
>> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
>> + dma->sglist[SGLIST_NUM_SG]++;
>> +
>> + /* start new sg entry */
>> + sgdata++;
>> + sg_addr = addr;
>> + transfer_size = size;
>> + }
>> + }
>> + /* Write last sg list entry */
>> + sgdata->size = transfer_size;
>> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
>> + dma->sglist[SGLIST_NUM_SG]++;
>> +
>> + /* Update pointers and size field to point to sglist */
>> + put_unaligned((uint64_t)dma->handle, &(vkdata->address));
>> + vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
>> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
>> +
>> +#ifdef BCM_VK_DUMP_SGLIST
>> + dev_dbg(dev,
>> + "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
>> + (uint64_t)dma->sglist,
>> + dma->handle,
>> + dma->sglen,
>> + vkdata->size);
>> + for (i = 0; i < vkdata->size / sizeof(uint32_t); i++)
>> + dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
>> +#endif
>> +
>> + return 0;
>> +}
>> +
>> +int bcm_vk_sg_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata,
>> + int num)
>> +{
>> + int i;
>> + int rc = -EINVAL;
>> +
>> + /* Convert user addresses to DMA SG List */
>> + for (i = 0; i < num; i++) {
>> + if (vkdata[i].size && vkdata[i].address) {
>> + /*
>> + * If both size and address are non-zero
>> + * then DMA alloc.
>> + */
>> + rc = bcm_vk_dma_alloc(dev,
>> + &dma[i],
>> + dir,
>> + &vkdata[i]);
>> + } else if (vkdata[i].size ||
>> + vkdata[i].address) {
>> + /*
>> + * If one of size and address are zero
>> + * there is a problem.
>> + */
>> + dev_err(dev,
>> + "Invalid vkdata %x 0x%x 0x%llx\n",
>> + i, vkdata[i].size, vkdata[i].address);
>> + rc = -EINVAL;
>> + } else {
>> + /*
>> + * If size and address are both zero
>> + * don't convert, but return success.
>> + */
>> + rc = 0;
>> + }
>> +
>> + if (rc)
>> + goto fail_alloc;
>> + }
>> + return rc;
>> +
>> +fail_alloc:
>> + while (i > 0) {
>> + i--;
>> + if (dma[i].sglist)
>> + bcm_vk_dma_free(dev, &dma[i]);
>> + }
>> + return rc;
>> +}
>> +
>> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
>> +{
>> + dma_addr_t addr;
>> + int i;
>> + int num_sg;
>> + uint32_t size;
>> + struct _vk_data *vkdata;
>> +
>> + dev_dbg(dev, "free sglist=%p sglen=0x%x\n",
>> + dma->sglist, dma->sglen);
>> +
> This fits in 80 characters.
>
> dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);
>
>
>> + /* Unmap all pages in the sglist */
>> + num_sg = dma->sglist[SGLIST_NUM_SG];
>> + vkdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
> Extra parens.
>
>> + for (i = 0; i < num_sg; i++) {
>> + size = vkdata[i].size;
>> + addr = get_unaligned(&(vkdata[i].address));
>> +
>> + dma_unmap_page(dev, addr, size, dma->direction);
>> + }
>> +
>> + /* Free allocated sglist */
>> + dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
>> +
>> + /* Release lock on all pages */
>> + for (i = 0; i < dma->nr_pages; i++)
>> + put_page(dma->pages[i]);
>> +
>> + /* Free allocated dma pages */
>> + kfree(dma->pages);
>> + dma->sglist = NULL;
>> +
>> + return 0;
>> +}
>> +
>> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num)
>> +{
>> + int i;
>> +
>> + /* Unmap and free all pages and sglists */
>> + for (i = 0; i < num; i++) {
>> + if (dma[i].sglist)
>> + bcm_vk_dma_free(dev, &dma[i]);
>> + }
>> +
>> + return 0;
>> +}
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.h b/drivers/misc/bcm-vk/bcm_vk_sg.h
>> new file mode 100644
>> index 000000000000..df88154e80b7
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.h
>> @@ -0,0 +1,60 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_SG_H
>> +#define BCM_VK_SG_H
>> +
>> +#include <linux/dma-mapping.h>
>> +
>> +struct bcm_vk_dma {
>> + /* for userland buffer */
>> + struct page **pages;
>> + int nr_pages;
>> +
>> + /* common */
>> + dma_addr_t handle;
>> + /*
>> + * sglist is of the following LE format
>> + * [U32] num_sg = number of sg addresses (N)
>> + * [U32] totalsize = totalsize of data being transferred in sglist
>> + * [U32] size[0] = size of data in address0
>> + * [U32] addr_l[0] = lower 32-bits of address0
>> + * [U32] addr_h[0] = higher 32-bits of address0
>> + * ..
>> + * [U32] size[N-1] = size of data in addressN-1
>> + * [U32] addr_l[N-1] = lower 32-bits of addressN-1
>> + * [U32] addr_h[N-1] = higher 32-bits of addressN-1
>> + */
>> + uint32_t *sglist;
>> +#define SGLIST_NUM_SG 0
>> +#define SGLIST_TOTALSIZE 1
>> +#define SGLIST_VKDATA_START 2
>> +
>> + int sglen; /* Length (bytes) of sglist */
>> + int direction;
>> +};
>> +
>> +struct _vk_data {
>> + uint32_t size; /* data size in bytes */
>> + uint64_t address; /* Pointer to data */
>> +} __packed;
>> +
>> +/*
>> + * Scatter-gather DMA buffer API.
>> + *
>> + * These functions provide a simple way to create a page list and a
>> + * scatter-gather list from userspace address and map the memory
>> + * for DMA operation.
>> + */
>> +int bcm_vk_sg_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata,
>> + int num);
>> +
>> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num);
>> +
>> +#endif
>> +
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_tty.c b/drivers/misc/bcm-vk/bcm_vk_tty.c
>> new file mode 100644
>> index 000000000000..3835218aaec6
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_tty.c
>> @@ -0,0 +1,327 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/tty.h>
>> +#include <linux/tty_driver.h>
>> +#include <linux/tty_flip.h>
>> +
>> +#include "bcm_vk.h"
>> +
>> +/* TTYVK base offset is 0x30000 into BAR1 */
>> +#define BAR1_TTYVK_BASE_OFFSET 0x300000
>> +/* Each TTYVK channel (TO or FROM) is 0x10000 */
>> +#define BAR1_TTYVK_CHAN_OFFSET 0x100000
>> +/* Each TTYVK channel has TO and FROM, hence the * 2 */
>> +#define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
>> + ((index) * BAR1_TTYVK_CHAN_OFFSET * 2))
>> +/* TO TTYVK channel base comes before FROM for each index */
>> +#define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
>> +#define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
>> + BAR1_TTYVK_CHAN_OFFSET)
>> +
>> +struct bcm_vk_tty_chan {
>> + uint32_t reserved;
>> + uint32_t size;
>> + uint32_t wr;
>> + uint32_t rd;
>> + uint32_t *data;
>> +};
>> +
>> +#define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
>> + + offsetof(struct bcm_vk_tty_chan, e))
>> +#define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
>> +#define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
>> +#define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
>> +#define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + off)
>> +
>> +/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
>> +#define SERIAL_TIMER_VALUE (HZ / 10)
>> +
>> +#if defined(BCM_VK_LEGACY_API)
>> +
>> +/* No support in legacy case, and do a dummy init and exit */
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + dev_dbg(dev, "init %s\n", name);
>> + return 0;
>> +}
>> +
>> +void bcm_vk_tty_exit(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + dev_dbg(dev, "exit\n");
>> +}
>> +
>> +#else
>> +
>> +static void bcm_vk_tty_poll(struct timer_list *t)
>> +{
>> + struct bcm_vk *vk = from_timer(vk, t, serial_timer);
>> + struct bcm_vk_tty *vktty;
>> + int card_status;
>> + int ready_mask;
>> + int count = 0;
>> + unsigned char c;
>> + int i;
>> + int wr;
>> +
>> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
>> +
>> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
>> + /* Check the card status that the tty channel is ready */
>> + ready_mask = BIT(i);
>> + if ((card_status & ready_mask) == 0)
> Get rid of ready_mask and use BIT(i) directly.
>
>> + continue;
>> +
>> + vktty = &vk->tty[i];
>> +
>> + /* Fetch the wr offset in buffer from VK */
>> + wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from));
>> + if (wr >= vktty->from_size) {
>> + dev_err(&vk->pdev->dev,
>> + "ERROR: poll ttyVK%d wr:0x%x > 0x%x\n",
>> + i, wr, vktty->from_size);
>> + /* Need to signal and close device in this case */
>> + return;
>> + }
>> +
>> + /*
>> + * Simple read of circular buffer and
>> + * insert into tty flip buffer
>> + */
>> + while (vk->tty[i].rd != wr) {
>> + c = vkread8(vk, BAR_1,
>> + VK_BAR_CHAN_DATA(vktty, from, vktty->rd));
>> + vktty->rd++;
>> + if (vktty->rd >= vktty->from_size)
>> + vktty->rd = 0;
>> + tty_insert_flip_char(&vktty->port, c, TTY_NORMAL);
>> + count++;
>> + }
>> + }
>> +
>> + if (count) {
>> + tty_flip_buffer_push(&vktty->port);
>> +
>> + /* Update read offset from shadow register to card */
>> + vkwrite32(vk, vktty->rd, BAR_1, VK_BAR_CHAN_RD(vktty, from));
>> + }
>> +
>> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
>> +}
>> +
>> +static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file)
>> +{
>> + int card_status;
>> + int ready_mask;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> + int index;
>> +
>> + /* initialize the pointer in case something fails */
>> + tty->driver_data = NULL;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
>> + index = tty->index;
>> +
>> + if (index >= BCM_VK_NUM_TTY)
>> + return -EINVAL;
>> +
>> + vktty = &vk->tty[index];
>> +
>> + vktty->to_offset = TO_TTYK_BASE(index);
>> + vktty->from_offset = FROM_TTYK_BASE(index);
>> +
>> + /* Do not allow tty device to be opened if tty on card not ready */
>> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
>> + if (card_status == -1)
>> + return -1;
>> +
>> + ready_mask = BIT(index);
>> + if ((card_status & ready_mask) == 0)
>> + return -1;
>> +
>> + /*
>> + * Get shadow registers of the buffer sizes and the "to" write offset
>> + * and "from" read offset
>> + */
>> + vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to));
>> + vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to));
>> + vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from));
>> + vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from));
>> +
>> + if (tty->count == 1) {
>> + timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0);
>> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
>> + }
>> + return 0;
>> +}
>> +
>> +static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file)
>> +{
>> + struct bcm_vk *vk;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
>
> struct bcm_vk *vk = dev_get_drvdata(tty->dev);
>
>> +
>> + if (tty->count == 1)
>> + del_timer_sync(&vk->serial_timer);
>> +}
>> +
>> +static int bcm_vk_tty_write(struct tty_struct *tty,
>> + const unsigned char *buffer,
>> + int count)
>> +{
>> + int index;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> + int i;
>> + int retval;
>> +
>> + index = tty->index;
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
> The cast is unnecessary.
>
>> + vktty = &vk->tty[index];
>> +
>> + /* Simple write each byte to circular buffer */
>> + for (i = 0; i < count; i++) {
>> + vkwrite8(vk,
>> + buffer[i],
>> + BAR_1,
>> + VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
> vkwrite8(vk, buffer[i], BAR_1,
> VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
>
>
>> + vktty->wr++;
>> + if (vktty->wr >= vktty->to_size)
>> + vktty->wr = 0;
>> + }
>> + /* Update write offset from shadow register to card */
>> + /* TODO: Need to add write to doorbell here */
>> + vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to));
>> +
>> + retval = count;
>> +
>> + return retval;
> Delete the "retval" variable.
>
>> +}
>> +
>> +static int bcm_vk_tty_write_room(struct tty_struct *tty)
>> +{
>> + int room;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
> Delete cast.
>
>> + vktty = &vk->tty[tty->index];
>> +
>> + /*
>> + * Calculate how much room is left in the device
>> + * Just return the size -1 of buffer. We could care about
>> + * overflow but don't at this point.
>> + */
> This comment raises more questions than it answers. Why would we care
> about overflow and why don't we at this point? Perhaps just delete the
> comment.
>
>> + room = vktty->to_size - 1;
>> +
>> + return room;
> Delete the room variable and the vktty variable.
>
> return vk->tty[tty->index].to_size - 1;
>
>
>> +}
>> +
>> +static const struct tty_operations serial_ops = {
>> + .open = bcm_vk_tty_open,
>> + .close = bcm_vk_tty_close,
>> + .write = bcm_vk_tty_write,
>> + .write_room = bcm_vk_tty_write_room,
>> +};
>> +
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
>> +{
>> + int i;
>> + int err;
>> + unsigned long flags;
>> + struct tty_driver *tty_drv;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /* allocate the tty driver */
>> + flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>> + tty_drv = tty_alloc_driver(BCM_VK_NUM_TTY, flags);
> Delete the flags variable.
>
>> +
> Delete this blank line.
>
>> + if (IS_ERR(tty_drv)) {
>> + err = PTR_ERR(tty_drv);
>> + goto err_exit;
>
> return PTR_ERR(tty_drv);
>
>> + }
>> + /* Save struct tty_driver for uninstalling the device */
>> + vk->tty_drv = tty_drv;
>> +
>> + /* initialize the tty driver */
>> + tty_drv->driver_name = KBUILD_MODNAME;
>> + tty_drv->name = kstrdup(name, GFP_KERNEL);
>> + if (!tty_drv->name) {
>> + err = -ENOMEM;
>> + goto err_put_tty_driver;
>> + }
>> + tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>> + tty_drv->subtype = SERIAL_TYPE_NORMAL;
>> + tty_drv->init_termios = tty_std_termios;
>> + tty_set_operations(tty_drv, &serial_ops);
>> +
>> + /* register the tty driver */
>> + err = tty_register_driver(tty_drv);
>> + if (err) {
>> + dev_err(dev, "tty_register_driver failed\n");
>> + goto err_kfree_tty_name;
>> + }
>> +
>> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
>> + struct device *tty_dev;
>> +
>> + tty_port_init(&vk->tty[i].port);
>> + tty_dev = tty_port_register_device(&vk->tty[i].port,
>> + tty_drv,
>> + i,
>> + dev);
> tty_dev = tty_port_register_device(&vk->tty[i].port, tty_drv,
> i, dev);
>
>
>> + dev_set_drvdata(tty_dev, vk);
>> +
>> + if (IS_ERR(tty_dev)) {
> Calling dev_set_drvdata() before checking for IS_ERR().
>
>> + int j;
>> +
>> + for (j = 0; j < i; j++)
>> + tty_port_unregister_device(&vk->tty[j].port,
>> + tty_drv,
>> + j);
>> + goto err_tty_unregister_driver;
> No error code.
>
>> + }
> if (IS_ERR(tty_dev)) {
> err = PTR_ERR(tty_dev);
> goto unwind;
> }
>
>> + }
>> +
>> + return 0;
>> +
> unwind:
> while (--i >= 0)
> tty_port_unregister_device(&vk->tty[i].port, tty_drv, i);
>
>
>> +err_tty_unregister_driver:
>> + tty_unregister_driver(tty_drv);
>> +
>> +err_kfree_tty_name:
>> + kfree(tty_drv->name);
>> + tty_drv->name = NULL;
>> +
>> +err_put_tty_driver:
>> + put_tty_driver(tty_drv);
>> +
>> +err_exit:
>> + return err;
>> +}
>> +
>> +void bcm_vk_tty_exit(struct bcm_vk *vk)
>> +{
>> + int i;
>> +
>> + del_timer_sync(&vk->serial_timer);
>> + for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
>> + tty_port_unregister_device(&vk->tty[i].port,
>> + vk->tty_drv,
>> + i);
>> + tty_port_destroy(&vk->tty[i].port);
>> + }
>> + tty_unregister_driver(vk->tty_drv);
>> + put_tty_driver(vk->tty_drv);
>> +
>> + kfree(vk->tty_drv->name);
> ^^^^^^^^^^^
>
> Use after free because of the put_tty_driver(). I think test again
> with CONFIG_PAGE_POISONING?
>
>> + vk->tty_drv->name = NULL;
>> +}
> regards,
> dan carpenter
>

2020-02-21 18:30:57

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

Hi Dan,

Thanks for your review and valuable comments.
Will have to investigate fully and correct anything wrong.

On 2020-02-20 12:42 a.m., Dan Carpenter wrote:
> On Wed, Feb 19, 2020 at 04:48:21PM -0800, Scott Branden wrote:
>> +static int test_dev_config_update_size_t(const char *buf,
>> + size_t size,
>> + size_t *cfg)
>> +{
>> + int ret;
>> + long new;
>> +
>> + ret = kstrtol(buf, 10, &new);
>> + if (ret)
>> + return ret;
>> +
>> + if (new > SIZE_MAX)
> This "new" variable is long and SIZE_MAX is ULONG_MAX so the condition
> can't be true.
>
>> + return -EINVAL;
>> +
>> + mutex_lock(&test_fw_mutex);
>> + *(size_t *)cfg = new;
>> + mutex_unlock(&test_fw_mutex);
>> +
>> + /* Always return full write size even if we didn't consume all */
>> + return size;
>> +}
>> +
>> +static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
>> +{
>> + size_t val;
>> +
>> + mutex_lock(&test_fw_mutex);
>> + val = cfg;
>> + mutex_unlock(&test_fw_mutex);
> Both val and cfg are stack variables so there is no need for locking.
> Probably you meant to pass a pointer to cfg?
>
>> +
>> + return snprintf(buf, PAGE_SIZE, "%zu\n", val);
>> +}
>> +
>> static ssize_t test_dev_config_show_int(char *buf, int cfg)
>> {
>> int val;
> regards,
> dan carpenter
>
>

2020-02-22 01:14:36

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

Reponses inline.

Luis - please have a look as well.

On 2020-02-21 10:30 a.m., Scott Branden wrote:
> Hi Dan,
>
> Thanks for your review and valuable comments.
> Will have to investigate fully and correct anything wrong.
>
> On 2020-02-20 12:42 a.m., Dan Carpenter wrote:
>> On Wed, Feb 19, 2020 at 04:48:21PM -0800, Scott Branden wrote:
>>> +static int test_dev_config_update_size_t(const char *buf,
>>> +                     size_t size,
>>> +                     size_t *cfg)
>>> +{
>>> +    int ret;
>>> +    long new;
>>> +
>>> +    ret = kstrtol(buf, 10, &new);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    if (new > SIZE_MAX)
>> This "new" variable is long and SIZE_MAX is ULONG_MAX so the condition
>> can't be true.
Removed the check.
>>
>>> +        return -EINVAL;
>>> +
>>> +    mutex_lock(&test_fw_mutex);
>>> +    *(size_t *)cfg = new;
>>> +    mutex_unlock(&test_fw_mutex);
>>> +
>>> +    /* Always return full write size even if we didn't consume all */
>>> +    return size;
>>> +}
>>> +
>>> +static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
>>> +{
>>> +    size_t val;
>>> +
>>> +    mutex_lock(&test_fw_mutex);
>>> +    val = cfg;
>>> +    mutex_unlock(&test_fw_mutex);
>> Both val and cfg are stack variables so there is no need for locking.
>> Probably you meant to pass a pointer to cfg?
I am following the existing code as was done for
test_dev_config_show_bool(),
test_dev_config_show_int(),
test_dev_config_show_u8()

Mutex probably not needed but I don't think I need to deviate from the
rest of the test code.

Luis, could you please explain what the rest of your code is doing?
>>
>>> +
>>> +    return snprintf(buf, PAGE_SIZE, "%zu\n", val);
>>> +}
>>> +
>>>   static ssize_t test_dev_config_show_int(char *buf, int cfg)
>>>   {
>>>       int val;
>> regards,
>> dan carpenter
>>
>>
>

2020-02-22 08:03:46

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Fri, Feb 21, 2020 at 7:19 PM Scott Branden
<[email protected]> wrote:
> On 2020-02-19 11:47 p.m., Greg Kroah-Hartman wrote:

> > Have you worked with the V4L developers to tie this into the proper
> > in-kernel apis for this type of functionality?
> We looked at the V4L model doesn't have any support for anything we are
> doing in this driver.
> We also want a driver that doesn't care about video. It could be
> offloading crypto or other operations.
> We talked with Olof about all of this previously and he said leave it as
> a misc driver for now.
> He was going to discuss at linux plumbers conference that we need some
> sort of offload engine model that such devices could fit into.

I see. Have you looked at the "uacce" driver submission? It seems
theirs is similar enough that there might be some way to share interfaces.

> > Using a tty driver seems like the totally incorrect way to do this, what
> > am I missing?
> tty driver is used to provide console access to the processors running
> on vk.
> Data is sent using the bcm_vk_msg interface by read/write operations
> from user space.
> VK then gets the messages and DMA's the data to/from host memory when
> needed to process.

In turn here, it sounds like you'd want to look at what drivers/misc/mic/
and the mellanox bluefield drivers are doing. As I understand, they have the
same requirements for console, but have a nicer approach of providing
abstract 'virtio' channels between the PCIe endpoint and the host, and
then run regular virtio based drivers (console, tty, block, filesystem,
network, ...) along with application specific ones to provide the custom
high-level protocols. This is also similar to what the drivers/pci/endpoint
(from the other end) as the drivers/ntb (pci host on both ends) frameworks
and of course the rpmsg/remoteproc framework do.

In the long run, I would want much more consolidation between the
low-level parts of all these frameworks, but moving your high-level
protocols to the same virtio method would sound like a step in the
direction towards a generialized framework and easier sharing of
the abstractions.

Arnd

2020-02-22 16:45:39

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Hi Scott,

I love your patch! Perhaps something to improve:

[auto build test WARNING on char-misc/char-misc-testing]
[also build test WARNING on kselftest/next linus/master v5.6-rc2]
[cannot apply to driver-core/driver-core-testing next-20200221]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url: https://github.com/0day-ci/linux/commits/Scott-Branden/firmware-add-partial-read-support-in-request_firmware_into_buf/20200222-032851
base: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git 06e33595e96f212811066df192ae8bf802174296
reproduce:
# apt-get install sparse
# sparse version: v0.6.1-173-ge0787745-dirty
make ARCH=x86_64 allmodconfig
make C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__'

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <[email protected]>


sparse warnings: (new ones prefixed by >>)

>> drivers/misc/bcm-vk/bcm_vk_dev.c:34:29: sparse: sparse: symbol 'image_tab' was not declared. Should it be static?
>> drivers/misc/bcm-vk/bcm_vk_dev.c:185:15: sparse: sparse: incorrect type in assignment (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:185:15: sparse: expected struct bcm_vk_peer_log *p_ctl
>> drivers/misc/bcm-vk/bcm_vk_dev.c:185:15: sparse: got void [noderef] <asn:2> *
drivers/misc/bcm-vk/bcm_vk_dev.c:415:22: sparse: sparse: incorrect type in assignment (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:415:22: sparse: expected void *bufp
drivers/misc/bcm-vk/bcm_vk_dev.c:415:22: sparse: got void [noderef] <asn:2> *
>> drivers/misc/bcm-vk/bcm_vk_dev.c:654:36: sparse: sparse: incorrect type in argument 2 (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:654:36: sparse: expected void const [noderef] <asn:1> *from
>> drivers/misc/bcm-vk/bcm_vk_dev.c:654:36: sparse: got struct vk_image *arg
drivers/misc/bcm-vk/bcm_vk_dev.c:694:37: sparse: sparse: incorrect type in argument 2 (different address spaces)
drivers/misc/bcm-vk/bcm_vk_dev.c:694:37: sparse: expected void const [noderef] <asn:1> *from
>> drivers/misc/bcm-vk/bcm_vk_dev.c:694:37: sparse: got struct vk_access *arg
drivers/misc/bcm-vk/bcm_vk_dev.c:717:31: sparse: sparse: incorrect type in argument 1 (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:717:31: sparse: expected void const volatile [noderef] <asn:1> *
>> drivers/misc/bcm-vk/bcm_vk_dev.c:717:31: sparse: got unsigned int [usertype] *
drivers/misc/bcm-vk/bcm_vk_dev.c:728:31: sparse: sparse: incorrect type in argument 1 (different address spaces)
drivers/misc/bcm-vk/bcm_vk_dev.c:728:31: sparse: expected void const volatile [noderef] <asn:1> *
drivers/misc/bcm-vk/bcm_vk_dev.c:728:31: sparse: got unsigned int [usertype] *
drivers/misc/bcm-vk/bcm_vk_dev.c:793:36: sparse: sparse: incorrect type in argument 2 (different address spaces)
drivers/misc/bcm-vk/bcm_vk_dev.c:793:36: sparse: expected void const [noderef] <asn:1> *from
>> drivers/misc/bcm-vk/bcm_vk_dev.c:793:36: sparse: got struct vk_reset *arg
drivers/misc/bcm-vk/bcm_vk_dev.c:873:45: sparse: sparse: incorrect type in argument 2 (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:873:45: sparse: expected struct vk_image *arg
>> drivers/misc/bcm-vk/bcm_vk_dev.c:873:45: sparse: got void [noderef] <asn:1> *argp
drivers/misc/bcm-vk/bcm_vk_dev.c:877:45: sparse: sparse: incorrect type in argument 2 (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:877:45: sparse: expected struct vk_access *arg
drivers/misc/bcm-vk/bcm_vk_dev.c:877:45: sparse: got void [noderef] <asn:1> *argp
drivers/misc/bcm-vk/bcm_vk_dev.c:881:40: sparse: sparse: incorrect type in argument 2 (different address spaces)
>> drivers/misc/bcm-vk/bcm_vk_dev.c:881:40: sparse: expected struct vk_reset *arg
drivers/misc/bcm-vk/bcm_vk_dev.c:881:40: sparse: got void [noderef] <asn:1> *argp
--
>> drivers/misc/bcm-vk/bcm_vk_msg.c:102:6: sparse: sparse: symbol 'bcm_vk_update_qstats' was not declared. Should it be static?
>> drivers/misc/bcm-vk/bcm_vk_msg.c:455:17: sparse: sparse: cast removes address space '<asn:2>' of expression
drivers/misc/bcm-vk/bcm_vk_msg.c:656:15: sparse: sparse: cast removes address space '<asn:2>' of expression
drivers/misc/bcm-vk/bcm_vk_msg.c:664:23: sparse: sparse: cast removes address space '<asn:2>' of expression
>> drivers/misc/bcm-vk/bcm_vk_msg.c:734:5: sparse: sparse: symbol 'bcm_vk_handle_last_sess' was not declared. Should it be static?
drivers/misc/bcm-vk/bcm_vk_msg.c:823:31: sparse: sparse: cast removes address space '<asn:2>' of expression
drivers/misc/bcm-vk/bcm_vk_msg.c:851:47: sparse: sparse: cast removes address space '<asn:2>' of expression

Please review and possibly fold the followup patch.

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]

2020-02-22 16:45:47

by kernel test robot

[permalink] [raw]
Subject: [RFC PATCH] misc: bcm-vk: image_tab[] can be static


Fixes: b6354450d21b ("misc: bcm-vk: add Broadcom VK driver")
Signed-off-by: kbuild test robot <[email protected]>
---
bcm_vk_dev.c | 2 +-
bcm_vk_msg.c | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
index d8f59e898ca8a..24cdab04c8703 100644
--- a/drivers/misc/bcm-vk/bcm_vk_dev.c
+++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
@@ -31,7 +31,7 @@ enum soc_idx {
};

#define NUM_BOOT_STAGES 2
-const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
+static const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
[VALKYRIE] = {
{VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VALKYRIE_FILENAME},
{VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VALKYRIE_FILENAME}
diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
index 651fba2149e28..9247d4bc50288 100644
--- a/drivers/misc/bcm-vk/bcm_vk_msg.c
+++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
@@ -99,8 +99,8 @@ static void bcm_vk_h2vk_verify_blk
#define BCM_VK_QSTATS_ACC_CNT 20000
#endif

-void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
- struct bcm_vk_qstats *qstats, uint32_t occupancy)
+static void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
+ struct bcm_vk_qstats *qstats, uint32_t occupancy)
{
struct bcm_vk_qs_cnts *qcnts = &qstats->qcnts;

@@ -731,7 +731,7 @@ int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type,
return rc;
}

-int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
+static int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
{
int rc = 0;
pid_t pid = task_pid_nr(ppid);

2020-02-23 10:00:54

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Sat, Feb 22, 2020 at 09:02:44AM +0100, Arnd Bergmann wrote:
> On Fri, Feb 21, 2020 at 7:19 PM Scott Branden
> <[email protected]> wrote:
> > On 2020-02-19 11:47 p.m., Greg Kroah-Hartman wrote:
>
> > > Have you worked with the V4L developers to tie this into the proper
> > > in-kernel apis for this type of functionality?
> > We looked at the V4L model doesn't have any support for anything we are
> > doing in this driver.
> > We also want a driver that doesn't care about video. It could be
> > offloading crypto or other operations.
> > We talked with Olof about all of this previously and he said leave it as
> > a misc driver for now.
> > He was going to discuss at linux plumbers conference that we need some
> > sort of offload engine model that such devices could fit into.
>
> I see. Have you looked at the "uacce" driver submission? It seems
> theirs is similar enough that there might be some way to share interfaces.
>
> > > Using a tty driver seems like the totally incorrect way to do this, what
> > > am I missing?
> > tty driver is used to provide console access to the processors running
> > on vk.
> > Data is sent using the bcm_vk_msg interface by read/write operations
> > from user space.
> > VK then gets the messages and DMA's the data to/from host memory when
> > needed to process.
>
> In turn here, it sounds like you'd want to look at what drivers/misc/mic/
> and the mellanox bluefield drivers are doing. As I understand, they have the
> same requirements for console, but have a nicer approach of providing
> abstract 'virtio' channels between the PCIe endpoint and the host, and
> then run regular virtio based drivers (console, tty, block, filesystem,
> network, ...) along with application specific ones to provide the custom
> high-level protocols. This is also similar to what the drivers/pci/endpoint
> (from the other end) as the drivers/ntb (pci host on both ends) frameworks
> and of course the rpmsg/remoteproc framework do.
>
> In the long run, I would want much more consolidation between the
> low-level parts of all these frameworks, but moving your high-level
> protocols to the same virtio method would sound like a step in the
> direction towards a generialized framework and easier sharing of
> the abstractions.

I agree, please do not override the generic tty api with something so
hardware-specific like this as it really is not a serial device here.

thanks,

greg k-h

2020-02-23 23:58:09

by Olof Johansson

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Sat, Feb 22, 2020 at 12:03 AM Arnd Bergmann <[email protected]> wrote:
>
> On Fri, Feb 21, 2020 at 7:19 PM Scott Branden
> <[email protected]> wrote:
> > On 2020-02-19 11:47 p.m., Greg Kroah-Hartman wrote:
>
> > > Have you worked with the V4L developers to tie this into the proper
> > > in-kernel apis for this type of functionality?
> > We looked at the V4L model doesn't have any support for anything we are
> > doing in this driver.
> > We also want a driver that doesn't care about video. It could be
> > offloading crypto or other operations.
> > We talked with Olof about all of this previously and he said leave it as
> > a misc driver for now.
> > He was going to discuss at linux plumbers conference that we need some
> > sort of offload engine model that such devices could fit into.
>
> I see. Have you looked at the "uacce" driver submission? It seems
> theirs is similar enough that there might be some way to share interfaces.

Uacce isn't a driver (or wasn't last time I looked at it, when it had
a different name). It's more of a framework for standardized direct HW
access from userspace, and relies on I/O virtualization to keep DMA
secure/partitioned, etc. VK is more of a classic PCIe device, it'll
handle DMA to/from the host, etc.

> > > Using a tty driver seems like the totally incorrect way to do this, what
> > > am I missing?
> > tty driver is used to provide console access to the processors running
> > on vk.
> > Data is sent using the bcm_vk_msg interface by read/write operations
> > from user space.
> > VK then gets the messages and DMA's the data to/from host memory when
> > needed to process.
>
> In turn here, it sounds like you'd want to look at what drivers/misc/mic/
> and the mellanox bluefield drivers are doing. As I understand, they have the
> same requirements for console, but have a nicer approach of providing
> abstract 'virtio' channels between the PCIe endpoint and the host, and
> then run regular virtio based drivers (console, tty, block, filesystem,
> network, ...) along with application specific ones to provide the custom
> high-level protocols.

This has more value on the device than on the host, as far as I've
seen it used (if you want to boot Linux on it and have things
exposed).

virtio isn't necessarily a match if all you really want is a character
stream for a console and don't need (or have performance requirements
beyond what virtio offers) other types of communication.

> This is also similar to what the drivers/pci/endpoint
> (from the other end) as the drivers/ntb (pci host on both ends) frameworks
> and of course the rpmsg/remoteproc framework do.

remoteproc is more about booting a tightly integrated device on an
embedded system. Also not a match here IMHO.

> In the long run, I would want much more consolidation between the
> low-level parts of all these frameworks, but moving your high-level
> protocols to the same virtio method would sound like a step in the
> direction towards a generialized framework and easier sharing of
> the abstractions.

For a simple naive console/character stream, doing something on top of
hvc might be easier -- it already does polling for you, etc.

Of course, the intent is not to ever use it as a console for the host
here, so that aspect of hvc isn't useful. But it gives you a bunch of
other stuff for free with just getchar/putchar interfaces to
implement.


-Olof

2020-02-24 11:27:12

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

On Fri, Feb 21, 2020 at 05:13:08PM -0800, Scott Branden wrote:
> > > > +static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
> > > > +{
> > > > +??? size_t val;
> > > > +
> > > > +??? mutex_lock(&test_fw_mutex);
> > > > +??? val = cfg;
> > > > +??? mutex_unlock(&test_fw_mutex);
> > > Both val and cfg are stack variables so there is no need for locking.
> > > Probably you meant to pass a pointer to cfg?
> I am following the existing code as was done for
> test_dev_config_show_bool(),
> test_dev_config_show_int(),
> test_dev_config_show_u8()

Heh. Yes. Those are buggy as well. Good eyes. Could you send a patch
to fix them?

regards,
dan caprnter

2020-02-25 19:34:08

by Luis Chamberlain

[permalink] [raw]
Subject: Re: [PATCH v2 3/7] test_firmware: add partial read support for request_firmware_into_buf

On Fri, Feb 21, 2020 at 05:13:08PM -0800, Scott Branden wrote:
> > > > +static ssize_t test_dev_config_show_size_t(char *buf, int cfg)
> > > > +{
> > > > +??? size_t val;
> > > > +
> > > > +??? mutex_lock(&test_fw_mutex);
> > > > +??? val = cfg;
> > > > +??? mutex_unlock(&test_fw_mutex);
> > > Both val and cfg are stack variables so there is no need for locking.
> > > Probably you meant to pass a pointer to cfg?
> I am following the existing code as was done for
> test_dev_config_show_bool(),
> test_dev_config_show_int(),
> test_dev_config_show_u8()
>
> Mutex probably not needed but I don't think I need to deviate from the rest
> of the test code.
>
> Luis, could you please explain what the rest of your code is doing?

The lock is indeed not needed in the functions you mentioned, so you can
also remove the other locks as a precursor patch. It would be a seperate
patch.

Luis

2020-02-25 22:38:00

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Hi Olof/All,

I'm trying to digest all the feedback of what needs to be done.
I will be fixing up all the valuable comments about general issues but
would like to know
what needs to be done about the tty interface.

The VK devices are configured to write serial data to circular buffers
in memory or out a UART.
When we configure a system using the UART we connect a cable to the host
and open a tty device.
When we don't connect a UART cable to the host we open the tty device in
our driver instead.
In this case the memory is exposed to the host via PCI BAR memory space.
The bcm-vk host driver then accesses PCI space and presents a tty
interface to the host.
We implemented a tty device to present the tty interface.
Host doesn't change anything other than opening a different devnode in
UART vs. PCI case.

Based on all the comments: what interface should we be providing in
driver instead?

On 2020-02-23 3:55 p.m., Olof Johansson wrote:
> On Sat, Feb 22, 2020 at 12:03 AM Arnd Bergmann <[email protected]> wrote:
>> On Fri, Feb 21, 2020 at 7:19 PM Scott Branden
>> <[email protected]> wrote:
>>> On 2020-02-19 11:47 p.m., Greg Kroah-Hartman wrote:
>>>> Have you worked with the V4L developers to tie this into the proper
>>>> in-kernel apis for this type of functionality?
>>> We looked at the V4L model doesn't have any support for anything we are
>>> doing in this driver.
>>> We also want a driver that doesn't care about video. It could be
>>> offloading crypto or other operations.
>>> We talked with Olof about all of this previously and he said leave it as
>>> a misc driver for now.
>>> He was going to discuss at linux plumbers conference that we need some
>>> sort of offload engine model that such devices could fit into.
>> I see. Have you looked at the "uacce" driver submission? It seems
>> theirs is similar enough that there might be some way to share interfaces.
> Uacce isn't a driver (or wasn't last time I looked at it, when it had
> a different name). It's more of a framework for standardized direct HW
> access from userspace, and relies on I/O virtualization to keep DMA
> secure/partitioned, etc. VK is more of a classic PCIe device, it'll
> handle DMA to/from the host, etc.
>
>>>> Using a tty driver seems like the totally incorrect way to do this, what
>>>> am I missing?
>>> tty driver is used to provide console access to the processors running
>>> on vk.
>>> Data is sent using the bcm_vk_msg interface by read/write operations
>>> from user space.
>>> VK then gets the messages and DMA's the data to/from host memory when
>>> needed to process.
>> In turn here, it sounds like you'd want to look at what drivers/misc/mic/
>> and the mellanox bluefield drivers are doing. As I understand, they have the
>> same requirements for console, but have a nicer approach of providing
>> abstract 'virtio' channels between the PCIe endpoint and the host, and
>> then run regular virtio based drivers (console, tty, block, filesystem,
>> network, ...) along with application specific ones to provide the custom
>> high-level protocols.
> This has more value on the device than on the host, as far as I've
> seen it used (if you want to boot Linux on it and have things
> exposed).
>
> virtio isn't necessarily a match if all you really want is a character
> stream for a console and don't need (or have performance requirements
> beyond what virtio offers) other types of communication.
>
>> This is also similar to what the drivers/pci/endpoint
>> (from the other end) as the drivers/ntb (pci host on both ends) frameworks
>> and of course the rpmsg/remoteproc framework do.
> remoteproc is more about booting a tightly integrated device on an
> embedded system. Also not a match here IMHO.
>
>> In the long run, I would want much more consolidation between the
>> low-level parts of all these frameworks, but moving your high-level
>> protocols to the same virtio method would sound like a step in the
>> direction towards a generialized framework and easier sharing of
>> the abstractions.
> For a simple naive console/character stream, doing something on top of
> hvc might be easier -- it already does polling for you, etc.
>
> Of course, the intent is not to ever use it as a console for the host
> here, so that aspect of hvc isn't useful. But it gives you a bunch of
> other stuff for free with just getchar/putchar interfaces to
> implement.
>
>
> -Olof

2020-04-17 21:52:52

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Hi Dan,

I'm almost ready to sendi out v3 of the patches.
Comments inline and a few clarifications needed inline.

On 2020-02-20 2:43 a.m., Dan Carpenter wrote:
> I'm not sure what git tree you're building on top of. This patchset
> doesn't apply to linux-next which is the only thing I tested...
>
> On Wed, Feb 19, 2020 at 04:48:24PM -0800, Scott Branden wrote:
>> Add Broadcom VK driver offload engine.
>> This driver interfaces to the VK PCIe offload engine to perform
>> should offload functions as video transcoding on multiple streams
>> in parallel. VK device is booted from files loaded using
>> request_firmware_into_buf mechanism. After booted card status is updated
>> and messages can then be sent to the card.
>> Such messages contain scatter gather list of addresses
>> to pull data from the host to perform operations on.
>>
>> Signed-off-by: Scott Branden <[email protected]>
>> Signed-off-by: Desmond Yan <[email protected]>
>> Signed-off-by: James Hu <[email protected]>
>> ---
>> drivers/misc/Kconfig | 1 +
>> drivers/misc/Makefile | 1 +
>> drivers/misc/bcm-vk/Kconfig | 42 +
>> drivers/misc/bcm-vk/Makefile | 11 +
>> drivers/misc/bcm-vk/bcm_vk.h | 357 ++++++++
>> drivers/misc/bcm-vk/bcm_vk_dev.c | 1197 ++++++++++++++++++++++++++
>> drivers/misc/bcm-vk/bcm_vk_msg.c | 1359 ++++++++++++++++++++++++++++++
>> drivers/misc/bcm-vk/bcm_vk_msg.h | 210 +++++
>> drivers/misc/bcm-vk/bcm_vk_sg.c | 273 ++++++
>> drivers/misc/bcm-vk/bcm_vk_sg.h | 60 ++
>> drivers/misc/bcm-vk/bcm_vk_tty.c | 327 +++++++
>> 11 files changed, 3838 insertions(+)
>> create mode 100644 drivers/misc/bcm-vk/Kconfig
>> create mode 100644 drivers/misc/bcm-vk/Makefile
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_dev.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_msg.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.c
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_sg.h
>> create mode 100644 drivers/misc/bcm-vk/bcm_vk_tty.c
>>
>> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
>> index 7f0d48f406e3..39c74ed6a846 100644
>> --- a/drivers/misc/Kconfig
>> +++ b/drivers/misc/Kconfig
>> @@ -478,6 +478,7 @@ source "drivers/misc/genwqe/Kconfig"
>> source "drivers/misc/echo/Kconfig"
>> source "drivers/misc/cxl/Kconfig"
>> source "drivers/misc/ocxl/Kconfig"
>> +source "drivers/misc/bcm-vk/Kconfig"
>> source "drivers/misc/cardreader/Kconfig"
>> source "drivers/misc/habanalabs/Kconfig"
>> endmenu
>> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
>> index c1860d35dc7e..d9a3b2791560 100644
>> --- a/drivers/misc/Makefile
>> +++ b/drivers/misc/Makefile
>> @@ -53,6 +53,7 @@ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
>> obj-$(CONFIG_CXL_BASE) += cxl/
>> obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o
>> obj-$(CONFIG_OCXL) += ocxl/
>> +obj-$(CONFIG_BCM_VK) += bcm-vk/
>> obj-y += cardreader/
>> obj-$(CONFIG_PVPANIC) += pvpanic.o
>> obj-$(CONFIG_HABANA_AI) += habanalabs/
>> diff --git a/drivers/misc/bcm-vk/Kconfig b/drivers/misc/bcm-vk/Kconfig
>> new file mode 100644
>> index 000000000000..c75dfb89a38d
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/Kconfig
>> @@ -0,0 +1,42 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +#
>> +# Broadcom VK device
>> +#
>> +config BCM_VK
>> + tristate "Support for Broadcom VK Accelerators"
>> + depends on PCI_MSI
>> + default m
>> + help
>> + Select this option to enable support for Broadcom
>> + VK Accelerators. VK is used for performing
>> + specific video offload processing. This driver enables
>> + userspace programs to access these accelerators via /dev/bcm-vk.N
>> + devices.
>> +
>> + If unsure, say N.
>> +
>> +if BCM_VK
>> +
>> +config BCM_VK_H2VK_VERIFY_AND_RETRY
>> + bool "Host To VK Verifiy Data and Retry"
>> + help
>> + Turn on to verify the data passed down to VK is good,
>> + and if not, do a retry until it succeeds.
>> + This is a debug/workaround on FPGA PCIe timing issues
>> + but may be found useful for debugging other PCIe hardware issues.
>> + Small performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +config BCM_VK_QSTATS
>> + bool "VK Queue Statistics"
>> + help
>> + Turn on to enable Queue Statistics.
>> + These are useful for debugging purposes.
>> + Some performance loss by enabling this debug config.
>> + For properly operating PCIe hardware no need to enable this.
>> +
>> + If unsure, say N.
>> +
>> +endif
>> diff --git a/drivers/misc/bcm-vk/Makefile b/drivers/misc/bcm-vk/Makefile
>> new file mode 100644
>> index 000000000000..05cb213ee826
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/Makefile
>> @@ -0,0 +1,11 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +#
>> +# Makefile for Broadcom VK driver
>> +#
>> +
>> +obj-$(CONFIG_BCM_VK) += bcm_vk.o
>> +bcm_vk-objs := \
>> + bcm_vk_dev.o \
>> + bcm_vk_msg.o \
>> + bcm_vk_sg.o \
>> + bcm_vk_tty.o
>> diff --git a/drivers/misc/bcm-vk/bcm_vk.h b/drivers/misc/bcm-vk/bcm_vk.h
>> new file mode 100644
>> index 000000000000..203ca741bf88
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk.h
>> @@ -0,0 +1,357 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_H
>> +#define BCM_VK_H
>> +
>> +#include <linux/atomic.h>
>> +#include <linux/pci.h>
>> +#include <linux/irq.h>
>> +#include <linux/miscdevice.h>
>> +#include <linux/mutex.h>
>> +#include <linux/sched/signal.h>
>> +#include <linux/tty.h>
>> +#include <linux/uaccess.h>
>> +#include <linux/version.h>
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk_msg.h"
>> +
>> +#define DRV_MODULE_NAME "bcm-vk"
>> +
>> +/*
>> + * Load Image is completed in two stages:
>> + *
>> + * 1) When the VK device boot-up, M7 CPU runs and executes the BootROM.
>> + * The Secure Boot Loader (SBL) as part of the BootROM will run
>> + * to open up ITCM for host to push BOOT1 image.
>> + * SBL will authenticate the image before jumping to BOOT1 image.
>> + *
>> + * 2) Because BOOT1 image is a secured image, we also called it the
>> + * Secure Boot Image (SBI). At second stage, SBI will initialize DDR
>> + * and wait for host to push BOOT2 image to DDR.
>> + * SBI will authenticate the image before jumping to BOOT2 image.
>> + *
>> + */
>> +/* Location of registers of interest in BAR0 */
>> +
>> +/* Request register for Secure Boot Loader (SBL) download */
>> +#define BAR_CODEPUSH_SBL 0x400
>> +/* Start of ITCM */
>> +#define CODEPUSH_BOOT1_ENTRY 0x00400000
>> +#define CODEPUSH_MASK 0xFFFFF000
>> +#define CODEPUSH_BOOTSTART BIT(0)
>> +
>> +/* Boot Status register */
>> +#define BAR_BOOT_STATUS 0x404
>> +
>> +#define SRAM_OPEN BIT(16)
>> +#define DDR_OPEN BIT(17)
>> +
>> +/* Firmware loader progress status definitions */
>> +#define FW_LOADER_ACK_SEND_MORE_DATA BIT(18)
>> +#define FW_LOADER_ACK_IN_PROGRESS BIT(19)
>> +#define FW_LOADER_ACK_RCVD_ALL_DATA BIT(20)
>> +
>> +/* definitions for boot status register */
>> +#define BOOT_STATE_MASK 0xFFF3FFFF
>> +#define BROM_STATUS_NOT_RUN 0x2
>> +#define BROM_NOT_RUN (SRAM_OPEN | BROM_STATUS_NOT_RUN)
>> +#define BROM_STATUS_COMPLETE 0x6
>> +#define BROM_RUNNING (SRAM_OPEN | BROM_STATUS_COMPLETE)
>> +#define BOOT1_STATUS_COMPLETE 0x6
>> +#define BOOT1_RUNNING (DDR_OPEN | BOOT1_STATUS_COMPLETE)
>> +#define BOOT2_STATUS_COMPLETE 0x6
>> +#define BOOT2_RUNNING (FW_LOADER_ACK_RCVD_ALL_DATA | \
>> + BOOT2_STATUS_COMPLETE)
>> +
>> +/* Boot request for Secure Boot Image (SBI) */
>> +#define BAR_CODEPUSH_SBI 0x408
>> +/* 64M mapped to BAR2 */
>> +#define CODEPUSH_BOOT2_ENTRY 0x60000000
>> +
>> +#define BAR_CARD_STATUS 0x410
>> +/* CARD_STATUS definitions */
>> +#define CARD_STATUS_TTYVK0_READY BIT(0)
>> +#define CARD_STATUS_TTYVK1_READY BIT(1)
>> +
>> +#define BAR_METADATA_VERSION 0x440
>> +#define BAR_OS_UPTIME 0x444
>> +#define BAR_CHIP_ID 0x448
>> +
>> +#define BAR_CARD_TEMPERATURE 0x45C
>> +/* defines for all temperature sensor */
>> +#define BCM_VK_TEMP_FIELD_MASK 0xFF
>> +#define BCM_VK_CPU_TEMP_SHIFT 0
>> +#define BCM_VK_DDR0_TEMP_SHIFT 8
>> +#define BCM_VK_DDR1_TEMP_SHIFT 16
>> +
>> +#define BAR_CARD_VOLTAGE 0x460
>> +/* defines for voltage rail conversion */
>> +#define BCM_VK_VOLT_RAIL_MASK 0xFFFF
>> +#define BCM_VK_3P3_VOLT_REG_SHIFT 16
>> +
>> +#define BAR_CARD_ERR_LOG 0x464
>> +/* Error log register bit definition - register for error alerts */
>> +#define ERR_LOG_UECC BIT(0)
>> +#define ERR_LOG_SSIM_BUSY BIT(1)
>> +#define ERR_LOG_AFBC_BUSY BIT(2)
>> +#define ERR_LOG_HIGH_TEMP_ERR BIT(3)
>> +#define ERR_LOG_WDOG_TIMEOUT BIT(4)
>> +#define ERR_LOG_SYS_FAULT BIT(5)
>> +#define ERR_LOG_MEM_ALLOC_FAIL BIT(8)
>> +#define ERR_LOG_LOW_TEMP_WARN BIT(9)
>> +#define ERR_LOG_ECC BIT(10)
>> +/* Alert bit definitions detectd on host */
>> +#define ERR_LOG_HOST_HB_FAIL BIT(14)
>> +#define ERR_LOG_HOST_PCIE_DWN BIT(15)
>> +
>> +#define BAR_CARD_ERR_MEM 0x468
>> +/* defines for mem err, all fields have same width */
>> +#define BCM_VK_MEM_ERR_FIELD_MASK 0xFF
>> +#define BCM_VK_ECC_MEM_ERR_SHIFT 0
>> +#define BCM_VK_UECC_MEM_ERR_SHIFT 8
>> +/* threshold of event occurrence and logs start to come out */
>> +#define BCM_VK_ECC_THRESHOLD 10
>> +#define BCM_VK_UECC_THRESHOLD 1
>> +
>> +#define BAR_CARD_PWR_AND_THRE 0x46C
>> +/* defines for power and temp threshold, all fields have same width */
>> +#define BCM_VK_PWR_AND_THRE_FIELD_MASK 0xFF
>> +#define BCM_VK_LOW_TEMP_THRE_SHIFT 0
>> +#define BCM_VK_HIGH_TEMP_THRE_SHIFT 8
>> +#define BCM_VK_PWR_STATE_SHIFT 16
>> +
>> +#define BAR_CARD_STATIC_INFO 0x470
>> +
>> +#define BAR_BOOTSRC_SELECT 0xC78
>> +/* BOOTSRC definitions */
>> +#define BOOTSRC_SOFT_ENABLE BIT(14)
>> +
>> +/* Card OS Firmware version size */
>> +#define BAR_FIRMWARE_TAG_SIZE 50
>> +#define FIRMWARE_STATUS_PRE_INIT_DONE 0x1F
>> +
>> +/* VK MSG_ID defines */
>> +#define VK_MSG_ID_BITMAP_SIZE 4096
>> +#define VK_MSG_ID_BITMAP_MASK (VK_MSG_ID_BITMAP_SIZE - 1)
>> +#define VK_MSG_ID_OVERFLOW 0xFFFF
>> +
>> +/* VK device supports a maximum of 3 bars */
>> +#define MAX_BAR 3
>> +
>> +/* default number of msg blk for inband SGL */
>> +#define BCM_VK_DEF_IB_SGL_BLK_LEN 16
>> +#define BCM_VK_IB_SGL_BLK_MAX 24
>> +
>> +enum pci_barno {
>> + BAR_0 = 0,
>> + BAR_1,
>> + BAR_2
>> +};
>> +
>> +#define BCM_VK_NUM_TTY 2
>> +
>> +struct bcm_vk_tty {
>> + struct tty_port port;
>> + uint32_t to_offset; /* bar offset to use */
>> + uint32_t to_size; /* to VK buffer size */
>> + uint32_t wr; /* write offset shadow */
>> + uint32_t from_offset; /* bar offset to use */
>> + uint32_t from_size; /* from VK buffer size */
>> + uint32_t rd; /* read offset shadow */
>> +};
>> +
>> +/* VK device max power state, supports 3, full, reduced and low */
>> +#define MAX_OPP 3
>> +#define MAX_CARD_INFO_TAG_SIZE 64
>> +
>> +struct bcm_vk_card_info {
>> + uint32_t version;
>> + char os_tag[MAX_CARD_INFO_TAG_SIZE];
>> + char cmpt_tag[MAX_CARD_INFO_TAG_SIZE];
>> + uint32_t cpu_freq_mhz;
>> + uint32_t cpu_scale[MAX_OPP];
>> + uint32_t ddr_freq_mhz;
>> + uint32_t ddr_size_MB;
>> + uint32_t video_core_freq_mhz;
>> +};
>> +
>> +/*
>> + * Control structure of logging messages from the card. This
>> + * buffer is for logmsg that comes from vk
>> + */
>> +struct bcm_vk_peer_log {
>> + uint32_t rd_idx;
>> + uint32_t wr_idx;
>> + uint32_t buf_size;
>> + uint32_t mask;
>> + char data[0];
>> +};
>> +/* max size per line of peer log */
>> +#define BCM_VK_PEER_LOG_LINE_MAX 256
>> +
>> +struct bcm_vk_hb_ctrl {
>> + struct timer_list timer;
>> + uint32_t last_uptime;
>> + uint32_t lost_cnt;
>> +};
>> +
>> +struct bcm_vk_alert {
>> + uint16_t flags;
>> + uint16_t notfs;
>> +};
>> +
>> +/* some alert counters that the driver will keep track */
>> +struct bcm_vk_alert_cnts {
>> + uint16_t ecc;
>> + uint16_t uecc;
>> +};
>> +
>> +struct bcm_vk {
>> + struct pci_dev *pdev;
>> + void __iomem *bar[MAX_BAR];
>> + int num_irqs;
>> +
>> + struct bcm_vk_card_info card_info;
>> +
>> + /* mutex to protect the ioctls */
>> + struct mutex mutex;
>> + struct miscdevice miscdev;
>> + int misc_devid; /* dev id allocated */
>> +
>> + struct tty_driver *tty_drv;
>> + struct timer_list serial_timer;
>> + struct bcm_vk_tty tty[BCM_VK_NUM_TTY];
>> +
>> + /* Reference-counting to handle file operations */
>> + struct kref kref;
>> +
>> + spinlock_t msg_id_lock;
>> + uint16_t msg_id;
>> + DECLARE_BITMAP(bmap, VK_MSG_ID_BITMAP_SIZE);
>> + spinlock_t ctx_lock;
>> + struct bcm_vk_ctx ctx[VK_CMPT_CTX_MAX];
>> + struct bcm_vk_ht_entry pid_ht[VK_PID_HT_SZ];
>> + struct task_struct *reset_ppid; /* process that issue reset */
>> +
>> + atomic_t msgq_inited; /* indicate if info has been synced with vk */
>> + struct bcm_vk_msg_chan h2vk_msg_chan;
>> + struct bcm_vk_msg_chan vk2h_msg_chan;
> These two names are almost the same except the letters are jumbled up.
> Please, figure out better names because the human mind sees them as
> exactly the same at first glance.
>
> https://www.mnn.com/lifestyle/arts-culture/stories/why-your-brain-can-read-jumbled-letters
renamed all h2vk and vk2h to "to_v" and "to_h" to remove brain jumbling
potentials.
>
>> +
>> + struct workqueue_struct *wq_thread;
>> + struct work_struct wq_work; /* work queue for deferred job */
>> + unsigned long wq_offload[1]; /* various flags on wq requested */
>> + void *tdma_vaddr; /* test dma segment virtual addr */
>> + dma_addr_t tdma_addr; /* test dma segment bus addr */
>> +
>> + struct notifier_block panic_nb;
>> + uint32_t ib_sgl_size; /* size allocated for inband sgl insertion */
>> +
>> + /* heart beat mechanism control structure */
>> + struct bcm_vk_hb_ctrl hb_ctrl;
>> + /* house-keeping variable of error logs */
>> + spinlock_t host_alert_lock; /* protection to access host_alert struct */
>> + struct bcm_vk_alert host_alert;
>> + struct bcm_vk_alert peer_alert; /* bits set by the card */
>> + struct bcm_vk_alert_cnts alert_cnts;
>> +
>> + /* offset of the peer log control in BAR2 */
>> + uint32_t peerlog_off;
>> +};
>> +
>> +/* wq offload work items bits definitions */
>> +enum bcm_vk_wq_offload_flags {
>> + BCM_VK_WQ_DWNLD_PEND = 0,
>> + BCM_VK_WQ_DWNLD_AUTO = 1,
>> + BCM_VK_WQ_NOTF_PEND = 2,
>> +};
>> +
>> +/* a macro to get an individual field with mask and shift */
>> +#define BCM_VK_EXTRACT_FIELD(_field, _reg, _mask, _shift) \
>> + (_field = (((_reg) >> (_shift)) & (_mask)))
>> +
>> +/* structure that is used to faciliate displaying of register content */
>> +struct bcm_vk_entry {
>> + const uint32_t mask;
>> + const uint32_t exp_val;
>> + const char *str;
>> +};
>> +
>> +/* alerts that could be generated from peer */
>> +#define BCM_VK_PEER_ERR_NUM 9
>> +extern struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM];
>> +/* alerts detected by the host */
>> +#define BCM_VK_HOST_ERR_NUM 2
>> +extern struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM];
>> +
>> +/*
>> + * check if PCIe interface is down on read. Use it when it is
>> + * certain that _val should never be all ones.
>> + */
>> +#define BCM_VK_INTF_IS_DOWN(val) ((val) == 0xFFFFFFFF)
>> +
>> +static inline u32 vkread32(struct bcm_vk *vk,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + u32 value;
>> +
>> + value = ioread32(vk->bar[bar] + offset);
>> + return value;
>
> return ioread32(vk->bar[bar] + offset);
done
>
>> +}
>> +
>> +static inline void vkwrite32(struct bcm_vk *vk,
>> + u32 value,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + iowrite32(value, vk->bar[bar] + offset);
>> +}
>> +
>> +static inline u8 vkread8(struct bcm_vk *vk,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + u8 value;
>> +
>> + value = ioread8(vk->bar[bar] + offset);
>> + return value;
> return ioread8(vk->bar[bar] + offset);
done
>
>> +}
>> +
>> +static inline void vkwrite8(struct bcm_vk *vk,
>> + u8 value,
>> + enum pci_barno bar,
>> + uint64_t offset)
>> +{
>> + iowrite8(value, vk->bar[bar] + offset);
>> +}
>> +
>> +int bcm_vk_open(struct inode *inode, struct file *p_file);
>> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
>> + loff_t *f_pos);
>> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
>> + size_t count, loff_t *f_pos);
>> +int bcm_vk_release(struct inode *inode, struct file *p_file);
>> +void bcm_vk_release_data(struct kref *kref);
>> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id);
>> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id);
>> +int bcm_vk_msg_init(struct bcm_vk *vk);
>> +void bcm_vk_msg_remove(struct bcm_vk *vk);
>> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync);
>> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk);
>> +void bcm_vk_blk_drv_access(struct bcm_vk *vk);
>> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type, pid_t pid);
>> +void bcm_vk_trigger_reset(struct bcm_vk *vk);
>> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num, uint32_t db_val);
>> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk);
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name);
>> +void bcm_vk_tty_exit(struct bcm_vk *vk);
>> +void bcm_vk_hb_init(struct bcm_vk *vk);
>> +void bcm_vk_hb_deinit(struct bcm_vk *vk);
>> +void bcm_vk_handle_notf(struct bcm_vk *vk);
>> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk);
>> +
>> +#endif
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
>> new file mode 100644
>> index 000000000000..d8f59e898ca8
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
>> @@ -0,0 +1,1197 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/firmware.h>
>> +#include <linux/fs.h>
>> +#include <linux/idr.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/module.h>
>> +#include <linux/pci.h>
>> +#include <linux/pci_regs.h>
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk.h"
>> +
>> +#define PCI_DEVICE_ID_VALKYRIE 0x5E87
>> +#define PCI_DEVICE_ID_VIPER 0x5E88
>> +
>> +static DEFINE_IDA(bcm_vk_ida);
>> +
>> +struct load_image_tab {
>> + const uint32_t image_type;
>> + const char *image_name;
> Indented too far.
removed a tab
>
>> +};
>> +
>> +enum soc_idx {
>> + VALKYRIE = 0,
>> + VIPER
>> +};
>> +
>> +#define NUM_BOOT_STAGES 2
>> +const struct load_image_tab image_tab[][NUM_BOOT_STAGES] = {
>> + [VALKYRIE] = {
>> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VALKYRIE_FILENAME},
>> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VALKYRIE_FILENAME}
>> + },
>> + [VIPER] = {
>> + {VK_IMAGE_TYPE_BOOT1, VK_BOOT1_DEF_VIPER_FILENAME},
>> + {VK_IMAGE_TYPE_BOOT2, VK_BOOT2_DEF_VIPER_FILENAME}
>> + }
>> +};
>> +
>> +/* Location of memory base addresses of interest in BAR1 */
>> +/* Load Boot1 to start of ITCM */
>> +#define BAR1_CODEPUSH_BASE_BOOT1 0x100000
>> +
>> +/* Allow minimum 1s for Load Image timeout responses */
>> +#define LOAD_IMAGE_TIMEOUT_MS (1 * MSEC_PER_SEC)
>> +
>> +/* Image startup timeouts */
>> +#define BOOT1_STARTUP_TIMEOUT_MS (5 * MSEC_PER_SEC)
>> +#define BOOT2_STARTUP_TIMEOUT_MS (10 * MSEC_PER_SEC)
>> +
>> +/* 1ms wait for checking the transfer complete status */
>> +#define TXFR_COMPLETE_TIMEOUT_MS 1
>> +
>> +/* MSIX usages */
>> +#define VK_MSIX_MSGQ_MAX 3
>> +#define VK_MSIX_NOTF_MAX 1
>> +#define VK_MSIX_IRQ_MAX (VK_MSIX_MSGQ_MAX + VK_MSIX_NOTF_MAX)
>> +
>> +/* Number of bits set in DMA mask*/
>> +#define BCM_VK_DMA_BITS 64
>> +
>> +/* deinit time for the card os after receiving doorbell */
>> +#define BCM_VK_DEINIT_TIME_MS (2 * MSEC_PER_SEC)
>> +
>> +/*
>> + * module parameters
>> + */
>> +static bool auto_load = true;
>> +module_param(auto_load, bool, 0444);
>> +MODULE_PARM_DESC(auto_load,
>> + "Load images automatically at PCIe probe time.\n");
>> +static uint nr_scratch_pages = VK_BAR1_SCRATCH_DEF_NR_PAGES;
>> +module_param(nr_scratch_pages, uint, 0444);
>> +MODULE_PARM_DESC(nr_scratch_pages,
>> + "Number of pre allocated DMAable coherent pages.\n");
>> +static uint nr_ib_sgl_blk = BCM_VK_DEF_IB_SGL_BLK_LEN;
>> +module_param(nr_ib_sgl_blk, uint, 0444);
>> +MODULE_PARM_DESC(nr_ib_sgl_blk,
>> + "Number of in-band msg blks for short SGL.\n");
>> +
>> +/*
>> + * alerts that could be generated from peer
>> + */
>> +struct bcm_vk_entry const bcm_vk_peer_err[BCM_VK_PEER_ERR_NUM] = {
>> + {ERR_LOG_UECC, ERR_LOG_UECC, "uecc"},
>> + {ERR_LOG_SSIM_BUSY, ERR_LOG_SSIM_BUSY, "ssim_busy"},
>> + {ERR_LOG_AFBC_BUSY, ERR_LOG_AFBC_BUSY, "afbc_busy"},
>> + {ERR_LOG_HIGH_TEMP_ERR, ERR_LOG_HIGH_TEMP_ERR, "high_temp"},
>> + {ERR_LOG_WDOG_TIMEOUT, ERR_LOG_WDOG_TIMEOUT, "wdog_timeout"},
>> + {ERR_LOG_SYS_FAULT, ERR_LOG_SYS_FAULT, "sys_fault"},
>> + {ERR_LOG_MEM_ALLOC_FAIL, ERR_LOG_MEM_ALLOC_FAIL, "malloc_fail warn"},
>> + {ERR_LOG_LOW_TEMP_WARN, ERR_LOG_LOW_TEMP_WARN, "low_temp warn"},
>> + {ERR_LOG_ECC, ERR_LOG_ECC, "ecc"},
>> +};
>> +/* alerts detected by the host */
>> +struct bcm_vk_entry const bcm_vk_host_err[BCM_VK_HOST_ERR_NUM] = {
>> + {ERR_LOG_HOST_PCIE_DWN, ERR_LOG_HOST_PCIE_DWN, "PCIe_down"},
>> + {ERR_LOG_HOST_HB_FAIL, ERR_LOG_HOST_HB_FAIL, "hb_fail"},
>> +};
>> +
>> +irqreturn_t bcm_vk_notf_irqhandler(int irq, void *dev_id)
>> +{
>> + struct bcm_vk *vk = dev_id;
>> +
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> + dev_err(&vk->pdev->dev,
>> + "Interrupt %d received when msgq not inited\n", irq);
>> + goto skip_schedule_work;
>> + }
>> +
>> + /* if notification is not pending, set bit and schedule work */
>> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> +skip_schedule_work:
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void bcm_vk_log_notf(struct bcm_vk *vk,
>> + struct bcm_vk_alert *alert,
>> + struct bcm_vk_entry const *entry_tab,
>> + const uint32_t table_size)
>> +{
>> + uint32_t i;
>> + uint32_t masked_val, latched_val;
>> + struct bcm_vk_entry const *entry;
>> + uint32_t reg;
>> + uint16_t ecc_mem_err, uecc_mem_err;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + for (i = 0; i < table_size; i++) {
>> + entry = &entry_tab[i];
>> + masked_val = entry->mask & alert->notfs;
>> + latched_val = entry->mask & alert->flags;
>> +
>> + if (masked_val == ERR_LOG_UECC) {
>> + /*
>> + * if there is difference between stored cnt and it
>> + * is greater than threshold, log it.
>> + */
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
>> + BCM_VK_EXTRACT_FIELD(uecc_mem_err, reg,
>> + BCM_VK_MEM_ERR_FIELD_MASK,
>> + BCM_VK_UECC_MEM_ERR_SHIFT);
>> + if ((uecc_mem_err != vk->alert_cnts.uecc) &&
>> + (uecc_mem_err >= BCM_VK_UECC_THRESHOLD))
>> + dev_info(dev,
>> + "ALERT! %s.%d uecc RAISED - ErrCnt %d\n",
>> + DRV_MODULE_NAME, vk->misc_devid,
>> + uecc_mem_err);
>> + vk->alert_cnts.uecc = uecc_mem_err;
>> + } else if (masked_val == ERR_LOG_ECC) {
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_MEM);
>> + BCM_VK_EXTRACT_FIELD(ecc_mem_err, reg,
>> + BCM_VK_MEM_ERR_FIELD_MASK,
>> + BCM_VK_ECC_MEM_ERR_SHIFT);
>> + if ((ecc_mem_err != vk->alert_cnts.ecc) &&
>> + (ecc_mem_err >= BCM_VK_ECC_THRESHOLD))
>> + dev_info(dev, "ALERT! %s.%d ecc RAISED - ErrCnt %d\n",
>> + DRV_MODULE_NAME, vk->misc_devid,
>> + ecc_mem_err);
>> + vk->alert_cnts.ecc = ecc_mem_err;
>> + } else if (masked_val != latched_val) {
>> + /* print a log as info */
>> + dev_info(dev, "ALERT! %s.%d %s %s\n",
>> + DRV_MODULE_NAME, vk->misc_devid, entry->str,
>> + masked_val ? "RAISED" : "CLEARED");
>> + }
>> + }
>> +}
>> +
>> +static void bcm_vk_dump_peer_log(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_peer_log log, *p_ctl;
>> + char loc_buf[BCM_VK_PEER_LOG_LINE_MAX];
>> + int cnt;
>> + struct device *dev = &vk->pdev->dev;
>> + uint data_offset;
>> +
>> + p_ctl = vk->bar[BAR_2] + vk->peerlog_off;
> Have you tried running Sparse on this driver?
have now fixed all sparse issues.  But there were no issues with this
particular function.
Could you please let me know your though on what you think the issue is
here?
>
>> + log = *p_ctl;
>> + /* do a rmb() to make sure log is updated */
>> + rmb();
>> +
>> + dev_dbg(dev, "Peer PANIC: Size 0x%x(0x%x), [Rd Wr] = [%d %d]\n",
>> + log.buf_size, log.mask, log.rd_idx, log.wr_idx);
>> +
>> + cnt = 0;
>> + data_offset = vk->peerlog_off + sizeof(struct bcm_vk_peer_log);
>> + while (log.rd_idx != log.wr_idx) {
>> + loc_buf[cnt] = vkread8(vk, BAR_2, data_offset + log.rd_idx);
>> +
>> + if ((loc_buf[cnt] == '\0') ||
>> + (cnt == (BCM_VK_PEER_LOG_LINE_MAX - 1))) {
>> + dev_err(dev, "%s", loc_buf);
>> + cnt = 0;
>> + } else {
>> + cnt++;
>> + }
>> + log.rd_idx = (log.rd_idx + 1) & log.mask;
>> + }
>> + /* update rd idx at the end */
>> + vkwrite32(vk, log.rd_idx, BAR_2, vk->peerlog_off);
>> +}
>> +
>> +void bcm_vk_handle_notf(struct bcm_vk *vk)
>> +{
>> + uint32_t reg;
>> + struct bcm_vk_alert alert;
>> + bool intf_down;
>> + unsigned long flags;
>> +
>> + /* handle peer alerts and then locally detected ones */
>> + reg = vkread32(vk, BAR_0, BAR_CARD_ERR_LOG);
>> + intf_down = BCM_VK_INTF_IS_DOWN(reg);
>> + if (!intf_down) {
>> + vk->peer_alert.notfs = reg;
>> + bcm_vk_log_notf(vk, &vk->peer_alert, bcm_vk_peer_err,
>> + ARRAY_SIZE(bcm_vk_peer_err));
>> + vk->peer_alert.flags = vk->peer_alert.notfs;
>> + } else {
>> + /* turn off access */
>> + bcm_vk_blk_drv_access(vk);
>> + }
>> +
>> + /* check and make copy of alert with lock and then free lock */
>> + spin_lock_irqsave(&vk->host_alert_lock, flags);
>> + if (intf_down)
>> + vk->host_alert.notfs |= ERR_LOG_HOST_PCIE_DWN;
>> +
>> + alert = vk->host_alert;
>> + vk->host_alert.flags = vk->host_alert.notfs;
>> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
>> +
>> + /* call display with copy */
>> + bcm_vk_log_notf(vk, &alert, bcm_vk_host_err,
>> + ARRAY_SIZE(bcm_vk_host_err));
>> +
>> + /*
>> + * If it is a sys fault or heartbeat timeout, we would like extract
>> + * log msg from the card so that we would know what is the last fault
>> + */
>> + if ((!intf_down) &&
>> + ((vk->host_alert.flags & ERR_LOG_HOST_HB_FAIL) ||
>> + (vk->peer_alert.flags & ERR_LOG_SYS_FAULT)))
>> + bcm_vk_dump_peer_log(vk);
>> +}
>> +
>> +static inline int bcm_vk_wait(struct bcm_vk *vk, enum pci_barno bar,
>> + uint64_t offset, u32 mask, u32 value,
>> + unsigned long timeout_ms)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms);
>> + u32 rd_val;
>> +
>> + do {
>> + rd_val = vkread32(vk, bar, offset);
>> + dev_dbg(dev, "BAR%d Offset=0x%llx: 0x%x\n",
>> + bar, offset, rd_val);
>> +
>> + if (time_after(jiffies, timeout))
>> + return -ETIMEDOUT;
>> +
>> + cpu_relax();
>> + cond_resched();
>> + } while ((rd_val & mask) != value);
>> +
>> + return 0;
>> +}
>> +
>> +static void bcm_vk_get_card_info(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t offset;
>> + int i;
>> + uint8_t *dst;
>> + struct bcm_vk_card_info *info = &vk->card_info;
>> +
>> + /* first read the offset from spare register */
>> + offset = vkread32(vk, BAR_0, BAR_CARD_STATIC_INFO);
>> + offset &= (pci_resource_len(vk->pdev, BAR_2 * 2) - 1);
>> +
>> + /* based on the offset, read info to internal card info structure */
>> + dst = (uint8_t *)info;
>> + for (i = 0; i < sizeof(*info); i++)
>> + *dst++ = vkread8(vk, BAR_2, offset++);
>> +
>> +#define CARD_INFO_LOG_FMT "version : %x\n" \
>> + "os_tag : %s\n" \
>> + "cmpt_tag : %s\n" \
>> + "cpu_freq : %d MHz\n" \
>> + "cpu_scale : %d full, %d lowest\n" \
>> + "ddr_freq : %d MHz\n" \
>> + "ddr_size : %d MB\n" \
>> + "video_freq: %d MHz\n"
>> + dev_dbg(dev, CARD_INFO_LOG_FMT, info->version, info->os_tag,
>> + info->cmpt_tag, info->cpu_freq_mhz, info->cpu_scale[0],
>> + info->cpu_scale[MAX_OPP - 1], info->ddr_freq_mhz,
>> + info->ddr_size_MB, info->video_core_freq_mhz);
>> +
>> + /* get the peer log pointer, only need the offset */
>> + vk->peerlog_off = offset;
>> +}
>> +
>> +static int bcm_vk_sync_card_info(struct bcm_vk *vk)
>> +{
>> + uint32_t rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> +
>> + /* check for marker, but allow diags mode to skip sync */
>> + if (!bcm_vk_msgq_marker_valid(vk))
>> + return (rdy_marker == VK_BAR1_DIAG_RDY_MARKER ? 0 : -EINVAL);
>> +
>> + /*
>> + * Write down scratch addr which is used for DMA. For
>> + * signed part, BAR1 is accessible only after boot2 has come
>> + * up
>> + */
>> + if (vk->tdma_addr) {
>> + vkwrite32(vk, vk->tdma_addr >> 32, BAR_1,
>> + VK_BAR1_SCRATCH_OFF_LO);
>> + vkwrite32(vk, (uint32_t)vk->tdma_addr, BAR_1,
>> + VK_BAR1_SCRATCH_OFF_HI);
>> + vkwrite32(vk, nr_scratch_pages * PAGE_SIZE, BAR_1,
>> + VK_BAR1_SCRATCH_SZ_ADDR);
>> + }
>> +
>> + /* get static card info, only need to read once */
>> + bcm_vk_get_card_info(vk);
>> +
>> + return 0;
>> +}
>> +
>> +void bcm_vk_blk_drv_access(struct bcm_vk *vk)
>> +{
>> + int i;
>> +
>> + /*
>> + * kill all the apps except for the process that is resetting.
>> + * If not called during reset, reset_ppid == NULL, and all will be
>> + * killed.
>> + */
>> + spin_lock(&vk->ctx_lock);
>> +
>> + /* set msgq_inited to 0 so that all rd/wr will be blocked */
>> + atomic_set(&vk->msgq_inited, 0);
>> +
>> + for (i = 0; i < VK_PID_HT_SZ; i++) {
>> + struct bcm_vk_ctx *ctx;
>> +
>> + list_for_each_entry(ctx, &vk->pid_ht[i].head, node) {
>> + if (ctx->ppid != vk->reset_ppid) {
>> + dev_dbg(&vk->pdev->dev,
>> + "Send kill signal to pid %d\n",
>> + task_pid_nr(ctx->ppid));
>> + kill_pid(task_pid(ctx->ppid), SIGKILL, 1);
>> + }
>> + }
>> + }
>> + spin_unlock(&vk->ctx_lock);
>> +}
>> +
>> +static void bcm_vk_buf_notify(struct bcm_vk *vk, void *bufp,
>> + dma_addr_t host_buf_addr, uint32_t buf_size)
>> +{
>> + /* update the dma address to the card */
>> + vkwrite32(vk, host_buf_addr >> 32, BAR_1,
>> + VK_BAR1_DMA_BUF_OFF_HI);
>> + vkwrite32(vk, (uint32_t)host_buf_addr, BAR_1,
>> + VK_BAR1_DMA_BUF_OFF_LO);
>> + vkwrite32(vk, buf_size, BAR_1, VK_BAR1_DMA_BUF_SZ);
>> +}
>> +
>> +static int bcm_vk_load_image_by_type(struct bcm_vk *vk, u32 load_type,
>> + const char *filename)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + const struct firmware *fw;
>> + void *bufp;
>> + size_t max_buf;
>> + int ret;
>> + uint64_t offset_codepush;
>> + u32 codepush;
>> + u32 value;
>> + dma_addr_t boot2_dma_addr;
>> +
>> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
>> + /*
>> + * After POR, enable VK soft BOOTSRC so bootrom do not clear
>> + * the pushed image (the TCM memories).
>> + */
>> + value = vkread32(vk, BAR_0, BAR_BOOTSRC_SELECT);
>> + value |= BOOTSRC_SOFT_ENABLE;
>> + vkwrite32(vk, value, BAR_0, BAR_BOOTSRC_SELECT);
>> +
>> + codepush = CODEPUSH_BOOTSTART + CODEPUSH_BOOT1_ENTRY;
>> + offset_codepush = BAR_CODEPUSH_SBL;
>> +
>> + /* Write a 1 to request SRAM open bit */
>> + vkwrite32(vk, CODEPUSH_BOOTSTART, BAR_0, offset_codepush);
>> +
>> + /* Wait for VK to respond */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, SRAM_OPEN,
>> + SRAM_OPEN, LOAD_IMAGE_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "boot1 timeout\n");
>> + goto err_out;
>> + }
>> +
>> + bufp = vk->bar[BAR_1] + BAR1_CODEPUSH_BASE_BOOT1;
>> + max_buf = SZ_256K;
>> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
>> + codepush = CODEPUSH_BOOT2_ENTRY;
>> + offset_codepush = BAR_CODEPUSH_SBI;
>> +
>> + /* Wait for VK to respond */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS, DDR_OPEN,
>> + DDR_OPEN, LOAD_IMAGE_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "boot2 timeout\n");
>> + goto err_out;
>> + }
>> +
>> + max_buf = SZ_4M;
>> + bufp = dma_alloc_coherent(dev,
>> + max_buf,
>> + &boot2_dma_addr, GFP_KERNEL);
>> + if (!bufp) {
>> + dev_err(dev, "Error allocating 0x%zx\n", max_buf);
>> + ret = -ENOMEM;
>> + goto err_out;
>> + }
>> +
>> + bcm_vk_buf_notify(vk, bufp, boot2_dma_addr, max_buf);
>> + } else {
>> + dev_err(dev, "Error invalid image type 0x%x\n", load_type);
>> + ret = -EINVAL;
>> + goto err_out;
>> + }
>> +
>> + ret = request_firmware_into_buf(&fw, filename, dev,
>> + bufp, max_buf, 0,
>> + KERNEL_PREAD_FLAG_PART);
>> + if (ret) {
>> + dev_err(dev, "Error %d requesting firmware file: %s\n",
>> + ret, filename);
>> + goto err_out;
>> + }
>> + dev_dbg(dev, "size=0x%zx\n", fw->size);
>> +
>> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n", codepush, offset_codepush);
>> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
>> +
>> + if (load_type == VK_IMAGE_TYPE_BOOT1) {
>> + /* wait until done */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
>> + BOOT1_RUNNING,
>> + BOOT1_RUNNING,
>> + BOOT1_STARTUP_TIMEOUT_MS);
>> + if (ret) {
>> + dev_err(dev,
>> + "Timeout %ld ms waiting for boot1 to come up\n",
>> + BOOT1_STARTUP_TIMEOUT_MS);
>> + goto err_firmware_out;
>> + }
>> + } else if (load_type == VK_IMAGE_TYPE_BOOT2) {
>> + unsigned long timeout = jiffies + msecs_to_jiffies(
>> + LOAD_IMAGE_TIMEOUT_MS);
>> +
>> + /* To send more data to VK than max_buf allowed at a time */
>> + do {
>> + /*
>> + * Check for ack from card. when Ack is received,
>> + * it means all the data is received by card.
>> + * Exit the loop after ack is received.
>> + */
>> + ret = bcm_vk_wait(vk, BAR_0, BAR_BOOT_STATUS,
>> + FW_LOADER_ACK_RCVD_ALL_DATA,
>> + FW_LOADER_ACK_RCVD_ALL_DATA,
>> + TXFR_COMPLETE_TIMEOUT_MS);
>> + if (ret == 0) {
>> + dev_info(dev, "Exit boot2 download\n");
>> + break;
>> + }
>> +
>> + /* exit the loop, if there is no response from card */
>> + if (time_after(jiffies, timeout)) {
>> + dev_err(dev, "Error. No reply from card\n");
>> + ret = -ETIMEDOUT;
>> + goto err_firmware_out;
>> + }
>> +
>> + /* Wait for VK to open BAR space to copy new data */
>> + ret = bcm_vk_wait(vk, BAR_0, offset_codepush,
>> + codepush, 0,
>> + TXFR_COMPLETE_TIMEOUT_MS);
>> + if (ret == 0) {
>> + ret = request_firmware_into_buf(
>> + &fw,
>> + filename,
>> + dev, bufp,
>> + max_buf,
>> + fw->size,
>> + KERNEL_PREAD_FLAG_PART);
>> + if (ret) {
>> + dev_err(dev, "Error %d requesting firmware file: %s offset: 0x%zx\n",
>> + ret, filename,
>> + fw->size);
>> + goto err_firmware_out;
>> + }
>> + dev_dbg(dev, "size=0x%zx\n", fw->size);
>> + dev_dbg(dev, "Signaling 0x%x to 0x%llx\n",
>> + codepush, offset_codepush);
>> + vkwrite32(vk, codepush, BAR_0, offset_codepush);
>> + /* reload timeout after every codepush */
>> + timeout = jiffies + msecs_to_jiffies(
>> + LOAD_IMAGE_TIMEOUT_MS);
>> + }
>> + } while (1);
>> +
>> + /* wait for fw status bits to indicate app ready */
>> + ret = bcm_vk_wait(vk, BAR_0, VK_BAR_FWSTS,
>> + VK_FWSTS_READY,
>> + VK_FWSTS_READY,
>> + BOOT2_STARTUP_TIMEOUT_MS);
>> + if (ret < 0) {
>> + dev_err(dev, "Boot2 not ready - timeout\n");
>> + goto err_firmware_out;
>> + }
>> +
>> + /*
>> + * Next, initialize Message Q if we are loading boot2.
>> + * Do a force sync
>> + */
>> + ret = bcm_vk_sync_msgq(vk, true);
>> + if (ret) {
>> + dev_err(dev, "Boot2 Error reading comm msg Q info\n");
>> + ret = -EIO;
>> + goto err_firmware_out;
>> + }
>> +
>> + /* sync & channel other info */
>> + ret = bcm_vk_sync_card_info(vk);
>> + if (ret) {
>> + dev_err(dev, "Syncing Card Info failure\n");
>> + goto err_firmware_out;
>> + }
>> + }
>> +
>> +err_firmware_out:
>> + release_firmware(fw);
>> +
>> +err_out:
>> + if (load_type == VK_IMAGE_TYPE_BOOT2)
>> + dma_free_coherent(dev, max_buf, bufp, boot2_dma_addr);
> bufp is uninitialized. Never use the label name "err" or "out" because
> it's always wrong. This is just a combination of the two.
fixed bufp and renamed err_out.  Note: there are many err_out labels
throughout the linux kernel.
>
>> +
>> + return ret;
>> +}
>> +
>> +static u32 bcm_vk_next_boot_image(struct bcm_vk *vk)
>> +{
>> + uint32_t boot_status;
>> + uint32_t fw_status;
>> + u32 load_type = 0; /* default for unknown */
>> +
>> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> +
>> + if (!BCM_VK_INTF_IS_DOWN(boot_status) && (boot_status & SRAM_OPEN))
>> + load_type = VK_IMAGE_TYPE_BOOT1;
>> + else if (boot_status == BOOT1_RUNNING)
>> + load_type = VK_IMAGE_TYPE_BOOT2;
>> +
>> + /*
>> + * TO_FIX: For now, like to know what value we get everytime
>> + * for debugging.
>> + */
>> + dev_info(&vk->pdev->dev,
>> + "boot-status value for next image: 0x%x : fw-status 0x%x\n",
>> + boot_status, fw_status);
>> +
>> + return load_type;
>> +}
>> +
>> +int bcm_vk_auto_load_all_images(struct bcm_vk *vk)
>> +{
>> + int i, id, ret = -1;
>> + struct device *dev = &vk->pdev->dev;
>> + struct pci_dev *pdev = to_pci_dev(dev);
>> + uint32_t curr_type;
>> + const char *curr_name;
>> +
>> + switch (pdev->device) {
>> + case PCI_DEVICE_ID_VALKYRIE:
>> + id = VALKYRIE;
>> + break;
>> +
>> + case PCI_DEVICE_ID_VIPER:
>> + id = VIPER;
>> + break;
>> +
>> + default:
>> + dev_err(dev, "no images for 0x%x\n", pdev->device);
>> + goto bcm_vk_auto_load_all_exit;
>> + }
>> +
>> + /* log a message to know the relative loading order */
>> + dev_info(dev, "Load All for device %d\n", vk->misc_devid);
>> +
>> + for (i = 0; i < NUM_BOOT_STAGES; i++) {
>> + curr_type = image_tab[id][i].image_type;
>> + if (bcm_vk_next_boot_image(vk) == curr_type) {
>> + curr_name = image_tab[id][i].image_name;
>> + ret = bcm_vk_load_image_by_type(vk, curr_type,
>> + curr_name);
>> + dev_info(dev, "Auto load %s, ret %d\n",
>> + curr_name, ret);
>> +
>> + if (ret) {
>> + dev_err(dev, "Error loading default %s\n",
>> + curr_name);
>> + goto bcm_vk_auto_load_all_exit;
>> + }
>> + }
>> + }
>> +
>> +bcm_vk_auto_load_all_exit:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_trigger_autoload(struct bcm_vk *vk)
>> +{
>> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0)
>> + return -EPERM;
>> +
>> + set_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> + return 0;
>> +}
>> +
>> +static long bcm_vk_load_image(struct bcm_vk *vk, struct vk_image *arg)
>> +{
>> + int ret;
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_image image;
>> + u32 next_loadable;
>> +
>> + if (copy_from_user(&image, arg, sizeof(image))) {
>> + ret = -EACCES;
>> + goto bcm_vk_load_image_exit;
>> + }
>> +
>> + /* first check if fw is at the right state for the download */
> What does this comment mean? Just delete it. The code is clear on
> its own.
done
>
>> + next_loadable = bcm_vk_next_boot_image(vk);
>> + if (next_loadable != image.type) {
>> + dev_err(dev, "Next expected image %u, Loading %u\n",
>> + next_loadable, image.type);
>> + ret = -EPERM;
>> + goto bcm_vk_load_image_exit;
>> + }
>> +
>> + /*
>> + * if something is pending download already. This could only happen
>> + * for now when the driver is being loaded, or if someone has issued
>> + * another download command in another shell.
>> + */
>> + if (test_and_set_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload) != 0) {
>> + dev_err(dev, "Download operation already pending.\n");
>> + return -EPERM;
> Woohoo for the direct return! Just make this function use direct
> returns everywhere and delete all the do nothing returns.
ok.
>
>> + }
>> +
>> + ret = bcm_vk_load_image_by_type(vk, image.type, image.filename);
>> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
>> +
>> +bcm_vk_load_image_exit:
>> + return ret;
>> +}
>> +
>> +static long bcm_vk_access_bar(struct bcm_vk *vk, struct vk_access *arg)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_access access;
>> + long ret = -EINVAL;
>> + u32 value;
>> + long i;
>> + long num;
>> +
>> + if (copy_from_user(&access, arg, sizeof(struct vk_access))) {
>> + ret = -EACCES;
> return -EFAULT;
this function has been removed entirely base on other's feedback to use
sysfs access to pcie space.
>
>> + goto err_out;
>> + }
>> +
>> + /* do some range checking in the barno and offset */
>> + if (access.barno >= MAX_BAR) {
>> + dev_err(dev, "invalid bar no %d\n", access.barno);
>> + goto err_out;
>> + } else if ((access.offset + access.len) >
>> + pci_resource_len(vk->pdev, access.barno * 2)) {
> There is an arithmatic overlfow. Also len should be a multiple of
> sizeof(u32).
>
> } else if (access.len % sizeof(u32) ||
> access.offset + access.len < access.offset ||
> access.offset + access.len > pci_resource_len(vk->pdev,
> access.barno * 2)) {
this function has been removed entirely base on other's feedback to use
sysfs access to pcie space.
>
>> + dev_err(dev, "invalid bar offset 0x%llx, len 0x%x\n",
>> + access.offset, access.len);
>> + goto err_out;
>> + }
>> +
>> + if (access.type == VK_ACCESS_READ) {
>> + dev_dbg(dev, "read barno:%d offset:0x%llx len:0x%x\n",
>> + access.barno, access.offset, access.len);
>> + num = access.len / sizeof(u32);
>> + for (i = 0; i < num; i++) {
>> + value = vkread32(vk, access.barno,
>> + access.offset + (i * sizeof(u32)));
>> + ret = put_user(value, access.data + i);
>> + if (ret)
>> + goto err_out;
>> +
>> + dev_dbg(dev, "0x%x\n", value);
>> + }
>> + } else if (access.type == VK_ACCESS_WRITE) {
>> + dev_dbg(dev, "write barno:%d offset:0x%llx len:0x%x\n",
>> + access.barno, access.offset, access.len);
>> + num = access.len / sizeof(u32);
>> + for (i = 0; i < num; i++) {
>> + ret = get_user(value, access.data + i);
>> + if (ret)
>> + goto err_out;
>> +
>> + vkwrite32(vk, value, access.barno,
>> + access.offset + (i * sizeof(u32)));
>> + dev_dbg(dev, "0x%x\n", value);
>> + }
>> + } else {
>> + dev_dbg(dev, "invalid access type %d\n", access.type);
>> + }
>> +err_out:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_reset_successful(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + u32 fw_status, reset_reason;
>> + int ret = -EAGAIN;
>> +
>> + /*
>> + * Reset could be triggered when the card in several state:
>> + * i) in bootROM
>> + * ii) after boot1
>> + * iii) boot2 running
>> + *
>> + * i) & ii) - no status bits will be updated. If vkboot1
>> + * runs automatically after reset, it will update the reason
>> + * to be unknown reason
>> + * iii) - reboot reason match + deinit done.
>> + */
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> + /* immediate exit if interface goes down */
>> + if (BCM_VK_INTF_IS_DOWN(fw_status)) {
>> + dev_err(dev, "PCIe Intf Down!\n");
>> + goto bcm_vk_reset_exit;
>> + }
>> +
>> + /* initial check on reset reason */
> This comment doesn't add anything.
removed
>
>> + reset_reason = (fw_status & VK_FWSTS_RESET_REASON_MASK);
>> + if ((reset_reason == VK_FWSTS_RESET_MBOX_DB)
>> + || (reset_reason == VK_FWSTS_RESET_UNKNOWN))
>> + ret = 0;
> Please use checkpatch.pl --strict.
fixed all --strict
>
> if ((reset_reason == VK_FWSTS_RESET_MBOX_DB) ||
> (reset_reason == VK_FWSTS_RESET_UNKNOWN))
> ret = 0;
>
>
>> +
>> + /*
>> + * if some of the deinit bits are set, but done
>> + * bit is not, this is a failure if triggered while boot2 is running
>> + */
>> + if ((fw_status & VK_FWSTS_DEINIT_TRIGGERED)
>> + && !(fw_status & VK_FWSTS_RESET_DONE))
>> + ret = -EAGAIN;
>> +
>> +bcm_vk_reset_exit:
>> + dev_dbg(dev, "FW status = 0x%x ret %d\n", fw_status, ret);
>> +
>> + return ret;
>> +}
>> +
>> +static long bcm_vk_reset(struct bcm_vk *vk, struct vk_reset *arg)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_reset reset;
>> + int ret = 0;
>> +
>> + if (copy_from_user(&reset, arg, sizeof(struct vk_reset))) {
>> + ret = -EACCES;
>
> return -EFAULT;
done
>
>> + goto err_out;
>> + }
>> + dev_info(dev, "Issue Reset\n");
>> +
>> + /*
>> + * The following is the sequence of reset:
>> + * - send card level graceful shut down
>> + * - wait enough time for VK to handle its business, stopping DMA etc
>> + * - kill host apps
>> + * - Trigger interrupt with DB
>> + */
>> + bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_GRACEFUL, 0);
>> +
>> + spin_lock(&vk->ctx_lock);
>> + if (!vk->reset_ppid) {
>> + vk->reset_ppid = current;
>> + } else {
>> + dev_err(dev, "Reset already launched by process pid %d\n",
>> + task_pid_nr(vk->reset_ppid));
>> + ret = -EACCES;
>> + }
>> + spin_unlock(&vk->ctx_lock);
>> + if (ret)
>> + goto err_out;
>> +
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_trigger_reset(vk);
>> +
>> + /*
>> + * Wait enough time for card os to deinit
>> + * and populate the reset reason.
>> + */
>> + msleep(BCM_VK_DEINIT_TIME_MS);
>> +
>> + ret = bcm_vk_reset_successful(vk);
>> +
>> +err_out:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_mmap(struct file *file, struct vm_area_struct *vma)
>> +{
>> + struct bcm_vk_ctx *ctx = file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + unsigned long pg_size;
>> +
>> + /* only BAR2 is mmap possible, which is bar num 4 due to 64bit */
>> +#define VK_MMAPABLE_BAR 4
>> +
>> + pg_size = ((pci_resource_len(vk->pdev, VK_MMAPABLE_BAR) - 1)
>> + >> PAGE_SHIFT) + 1;
>> + if (vma->vm_pgoff + vma_pages(vma) > pg_size)
>> + return -EINVAL;
>> +
>> + vma->vm_pgoff += (pci_resource_start(vk->pdev, VK_MMAPABLE_BAR)
>> + >> PAGE_SHIFT);
>> + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
>> +
>> + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
>> + vma->vm_end - vma->vm_start,
>> + vma->vm_page_prot);
>> +}
>> +
>> +static long bcm_vk_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
>> +{
>> + long ret = -EINVAL;
>> + struct bcm_vk_ctx *ctx = file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + void __user *argp = (void __user *)arg;
>> +
>> + dev_dbg(&vk->pdev->dev,
>> + "ioctl, cmd=0x%02x, arg=0x%02lx\n",
>> + cmd, arg);
>> +
>> + mutex_lock(&vk->mutex);
>> +
>> + switch (cmd) {
>> + case VK_IOCTL_LOAD_IMAGE:
>> + ret = bcm_vk_load_image(vk, argp);
>> + break;
>> +
>> + case VK_IOCTL_ACCESS_BAR:
>> + ret = bcm_vk_access_bar(vk, argp);
>> + break;
>> +
>> + case VK_IOCTL_RESET:
>> + ret = bcm_vk_reset(vk, argp);
>> + break;
>> +
>> + default:
>> + break;
>> + }
>> +
>> + mutex_unlock(&vk->mutex);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct file_operations bcm_vk_fops = {
>> + .owner = THIS_MODULE,
>> + .open = bcm_vk_open,
>> + .read = bcm_vk_read,
>> + .write = bcm_vk_write,
>> + .release = bcm_vk_release,
>> + .mmap = bcm_vk_mmap,
>> + .unlocked_ioctl = bcm_vk_ioctl,
>> +};
>> +
>> +static int bcm_vk_on_panic(struct notifier_block *nb,
>> + unsigned long e, void *p)
>> +{
>> + struct bcm_vk *vk = container_of(nb, struct bcm_vk, panic_nb);
>> +
>> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_HARD);
>> +
>> + return 0;
>> +}
>> +
>> +static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
>> +{
>> + int err;
>> + int i;
>> + int id;
>> + int irq;
>> + char name[20];
>> + struct bcm_vk *vk;
>> + struct device *dev = &pdev->dev;
>> + struct miscdevice *misc_device;
>> + uint32_t boot_status;
>> +
>> + /* allocate vk structure which is tied to kref for freeing */
>> + vk = kzalloc(sizeof(*vk), GFP_KERNEL);
>> + if (!vk)
>> + return -ENOMEM;
>> +
>> + kref_init(&vk->kref);
>> + if (nr_ib_sgl_blk > BCM_VK_IB_SGL_BLK_MAX) {
>> + dev_warn(dev, "Inband SGL blk %d limited to max %d\n",
>> + nr_ib_sgl_blk, BCM_VK_IB_SGL_BLK_MAX);
>> + nr_ib_sgl_blk = BCM_VK_IB_SGL_BLK_MAX;
>> + }
>> + vk->ib_sgl_size = nr_ib_sgl_blk * VK_MSGQ_BLK_SIZE;
>> + vk->pdev = pdev;
>> + mutex_init(&vk->mutex);
>> +
>> + err = pci_enable_device(pdev);
>> + if (err) {
>> + dev_err(dev, "Cannot enable PCI device\n");
>> + return err;
>> + }
>> +
>> + err = pci_request_regions(pdev, DRV_MODULE_NAME);
>> + if (err) {
>> + dev_err(dev, "Cannot obtain PCI resources\n");
>> + goto err_disable_pdev;
>> + }
>> +
>> + /* make sure DMA is good */
>> + err = dma_set_mask_and_coherent(&pdev->dev,
>> + DMA_BIT_MASK(BCM_VK_DMA_BITS));
>> + if (err) {
>> + dev_err(dev, "failed to set DMA mask\n");
>> + goto err_disable_pdev;
>> + }
>> +
>> + /* The tdma is a scratch area for some DMA testings. */
>> + if (nr_scratch_pages) {
>> + vk->tdma_vaddr = dma_alloc_coherent(dev,
>> + nr_scratch_pages * PAGE_SIZE,
>> + &vk->tdma_addr, GFP_KERNEL);
>> + if (!vk->tdma_vaddr) {
>> + err = -ENOMEM;
>> + goto err_disable_pdev;
>> + }
>> + }
>> +
>> + pci_set_master(pdev);
>> + pci_set_drvdata(pdev, vk);
>> +
>> + irq = pci_alloc_irq_vectors(pdev,
>> + 1,
>> + VK_MSIX_IRQ_MAX,
>> + PCI_IRQ_MSI | PCI_IRQ_MSIX);
>> +
>> + if (irq < VK_MSIX_IRQ_MAX) {
>> + dev_err(dev, "failed to get %d MSIX interrupts, ret(%d)\n",
>> + VK_MSIX_IRQ_MAX, irq);
>> + err = (irq >= 0) ? -EINVAL : irq;
>> + goto err_disable_pdev;
>> + }
>> +
>> + dev_info(dev, "Number of IRQs %d allocated.\n", irq);
>> +
>> + for (i = 0; i < MAX_BAR; i++) {
>> + /* multiple by 2 for 64 bit BAR mapping */
>> + vk->bar[i] = pci_ioremap_bar(pdev, i * 2);
>> + if (!vk->bar[i]) {
>> + dev_err(dev, "failed to remap BAR%d\n", i);
>> + goto err_iounmap;
>> + }
>> + }
>> +
>> + for (vk->num_irqs = 0;
>> + vk->num_irqs < VK_MSIX_MSGQ_MAX;
>> + vk->num_irqs++) {
>> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
>> + bcm_vk_msgq_irqhandler,
>> + IRQF_SHARED, DRV_MODULE_NAME, vk);
>> + if (err) {
>> + dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n",
>> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
>> + goto err_irq;
>> + }
>> + }
>> + /* one irq for notification from VK */
>> + err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
>> + bcm_vk_notf_irqhandler,
>> + IRQF_SHARED, DRV_MODULE_NAME, vk);
>> + if (err) {
>> + dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n",
>> + pdev->irq + vk->num_irqs, vk->num_irqs + 1);
>> + goto err_irq;
>> + }
>> + vk->num_irqs++;
>> +
>> + id = ida_simple_get(&bcm_vk_ida, 0, 0, GFP_KERNEL);
>> + if (id < 0) {
>> + err = id;
>> + dev_err(dev, "unable to get id\n");
>> + goto err_irq;
>> + }
>> +
>> + vk->misc_devid = id;
>> + snprintf(name, sizeof(name), DRV_MODULE_NAME ".%d", id);
>> + misc_device = &vk->miscdev;
>> + misc_device->minor = MISC_DYNAMIC_MINOR;
>> + misc_device->name = kstrdup(name, GFP_KERNEL);
>> + if (!misc_device->name) {
>> + err = -ENOMEM;
>> + goto err_ida_remove;
>> + }
>> + misc_device->fops = &bcm_vk_fops,
>> +
>> + err = misc_register(misc_device);
>> + if (err) {
>> + dev_err(dev, "failed to register device\n");
>> + goto err_kfree_name;
>> + }
>> +
>> + err = bcm_vk_msg_init(vk);
>> + if (err) {
>> + dev_err(dev, "failed to init msg queue info\n");
>> + goto err_misc_deregister;
>> + }
>> +
>> + /* sync other info */
>> + bcm_vk_sync_card_info(vk);
>> +
>> + /* register for panic notifier */
>> + vk->panic_nb.notifier_call = bcm_vk_on_panic;
>> + atomic_notifier_chain_register(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> + snprintf(name, sizeof(name), KBUILD_MODNAME ".%d_ttyVK", id);
>> + err = bcm_vk_tty_init(vk, name);
>> + if (err)
>> + goto err_unregister_panic_notifier;
>> +
>> + /*
>> + * lets trigger an auto download. We don't want to do it serially here
>> + * because at probing time, it is not supposed to block for a long time.
>> + */
>> + boot_status = vkread32(vk, BAR_0, BAR_BOOT_STATUS);
>> + if (auto_load) {
>> + if ((boot_status & BOOT_STATE_MASK) == BROM_RUNNING) {
>> + if (bcm_vk_trigger_autoload(vk))
>> + goto err_bcm_vk_tty_exit;
>> + } else {
>> + dev_info(dev,
>> + "Auto-load skipped - BROM not in proper state (0x%x)\n",
>> + boot_status);
>> + }
>> + }
>> +
>> + /* enable hb */
>> + bcm_vk_hb_init(vk);
>> +
>> + dev_info(dev, "BCM-VK:%u created, 0x%p\n", id, vk);
>> +
>> + return 0;
>> +
>> +err_bcm_vk_tty_exit:
>> + bcm_vk_tty_exit(vk);
>> +
>> +err_unregister_panic_notifier:
>> + atomic_notifier_chain_unregister(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> +err_misc_deregister:
>> + misc_deregister(misc_device);
>> +
>> +err_kfree_name:
>> + kfree(misc_device->name);
>> + misc_device->name = NULL;
>> +
>> +err_ida_remove:
>> + ida_simple_remove(&bcm_vk_ida, id);
>> +
>> +err_irq:
>> + for (i = 0; i < vk->num_irqs; i++)
>> + devm_free_irq(dev, pci_irq_vector(pdev, i), vk);
>> +
>> + pci_disable_msix(pdev);
>> + pci_disable_msi(pdev);
>> +
>> +err_iounmap:
>> + for (i = 0; i < MAX_BAR; i++) {
>> + if (vk->bar[i])
>> + pci_iounmap(pdev, vk->bar[i]);
>> + }
>> + pci_release_regions(pdev);
>> +
>> +err_disable_pdev:
>> + pci_disable_device(pdev);
>> +
>> + return err;
>> +}
>> +
>> +void bcm_vk_release_data(struct kref *kref)
>> +{
>> + struct bcm_vk *vk = container_of(kref, struct bcm_vk, kref);
>> +
>> + /* use raw print, as dev is gone */
>> + pr_info("BCM-VK:%d release data 0x%p\n", vk->misc_devid, vk);
>> + kfree(vk);
>> +}
>> +
>> +static void bcm_vk_remove(struct pci_dev *pdev)
>> +{
>> + int i;
>> + struct bcm_vk *vk = pci_get_drvdata(pdev);
>> + struct miscdevice *misc_device = &vk->miscdev;
>> +
>> + bcm_vk_hb_deinit(vk);
>> + bcm_vk_tty_exit(vk);
>> +
>> + /* unregister panic notifier */
>> + atomic_notifier_chain_unregister(&panic_notifier_list,
>> + &vk->panic_nb);
>> +
>> + bcm_vk_msg_remove(vk);
>> +
>> + if (vk->tdma_vaddr)
>> + dma_free_coherent(&pdev->dev, nr_scratch_pages * PAGE_SIZE,
>> + vk->tdma_vaddr, vk->tdma_addr);
>> +
>> + /* remove if name is set which means misc dev registered */
>> + if (misc_device->name) {
>> + misc_deregister(misc_device);
>> + kfree(misc_device->name);
>> + ida_simple_remove(&bcm_vk_ida, vk->misc_devid);
>> + }
>> + for (i = 0; i < vk->num_irqs; i++)
>> + devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), vk);
>> +
>> + pci_disable_msix(pdev);
>> + pci_disable_msi(pdev);
>> +
>> + cancel_work_sync(&vk->wq_work);
>> + destroy_workqueue(vk->wq_thread);
>> +
>> + for (i = 0; i < MAX_BAR; i++) {
>> + if (vk->bar[i])
>> + pci_iounmap(pdev, vk->bar[i]);
>> + }
>> +
>> + dev_info(&pdev->dev, "BCM-VK:%d released\n", vk->misc_devid);
>> +
>> + pci_release_regions(pdev);
>> + pci_disable_device(pdev);
>> +
>> + kref_put(&vk->kref, bcm_vk_release_data);
>> +}
>> +
>> +static const struct pci_device_id bcm_vk_ids[] = {
>> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VALKYRIE), },
>> + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_VIPER), },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(pci, bcm_vk_ids);
>> +
>> +static struct pci_driver pci_driver = {
>> + .name = DRV_MODULE_NAME,
>> + .id_table = bcm_vk_ids,
>> + .probe = bcm_vk_probe,
>> + .remove = bcm_vk_remove,
>> +};
>> +module_pci_driver(pci_driver);
>> +
>> +MODULE_DESCRIPTION("Broadcom VK Host Driver");
>> +MODULE_AUTHOR("Scott Branden <[email protected]>");
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_VERSION("1.0");
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
>> new file mode 100644
>> index 000000000000..651fba2149e2
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
>> @@ -0,0 +1,1359 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/fs.h>
>> +#include <linux/hash.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/list.h>
>> +#include <linux/sizes.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/timer.h>
>> +
>> +#include "bcm_vk.h"
>> +#include "bcm_vk_msg.h"
>> +#include "bcm_vk_sg.h"
>> +
>> +#if defined(CONFIG_BCM_VK_H2VK_VERIFY_AND_RETRY)
>> +/*
>> + * Turn on the following to verify the data passed down to VK is good, and
>> + * if not, do retry. This is a debug/workaround on FPGA PCIe timing issues
>> + * but may be found useful for debugging other PCIe hardware issues.
>> + */
>> +static void bcm_vk_h2vk_verify_idx(struct device *dev,
>> + const char *tag,
>> + volatile uint32_t *idx,
>> + uint32_t expected)
>> +{
>> + unsigned int count = 0;
>> +
>> + while (*idx != expected) {
>> + count++;
>> + dev_err(dev, "[%d] %s exp %d idx %d\n",
>> + count, tag, expected, *idx);
>> +
>> + /* write again */
>> + *idx = expected;
>> + }
>> +}
>> +
>> +static void bcm_vk_h2vk_verify_blk(struct device *dev,
>> + const struct vk_msg_blk *src,
>> + volatile struct vk_msg_blk *dst)
>> +
>> +{
>> + struct vk_msg_blk rd_bck;
>> + unsigned int count = 0;
>> +
>> + rd_bck = *dst;
>> + while (memcmp(&rd_bck, src, sizeof(rd_bck)) != 0) {
>> + count++;
>> + dev_err(dev,
>> + "[%d]Src Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
>> + count,
>> + src->function_id,
>> + src->size,
>> + src->queue_id,
>> + src->msg_id,
>> + src->context_id,
>> + src->args[0],
>> + src->args[1]);
>> + dev_err(dev,
>> + "[%d]Rdb Blk: [0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x]\n",
>> + count,
>> + rd_bck.function_id,
>> + rd_bck.size,
>> + rd_bck.queue_id,
>> + rd_bck.msg_id,
>> + rd_bck.context_id,
>> + rd_bck.args[0],
>> + rd_bck.args[1]);
>> +
>> + *dst = *src;
>> + rd_bck = *dst;
>> + }
>> +}
>> +#else
>> +static void bcm_vk_h2vk_verify_idx(struct device __always_unused *dev,
>> + const char __always_unused *tag,
>> + volatile uint32_t __always_unused *idx,
>> + uint32_t __always_unused expected)
>> +{
>> +}
>> +
>> +static void bcm_vk_h2vk_verify_blk
>> + (struct device __always_unused *dev,
>> + const struct vk_msg_blk __always_unused *src,
>> + volatile struct vk_msg_blk __always_unused *dst)
>> +
>> +{
>> +}
>> +#endif
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> +
>> +/* Use default value of 20000 rd/wr per update */
>> +#if !defined(BCM_VK_QSTATS_ACC_CNT)
>> +#define BCM_VK_QSTATS_ACC_CNT 20000
>> +#endif
>> +
>> +void bcm_vk_update_qstats(struct bcm_vk *vk, const char *tag,
>> + struct bcm_vk_qstats *qstats, uint32_t occupancy)
>> +{
>> + struct bcm_vk_qs_cnts *qcnts = &qstats->qcnts;
>> +
>> + if (occupancy > qcnts->max_occ) {
>> + qcnts->max_occ = occupancy;
>> + if (occupancy > qcnts->max_abs)
>> + qcnts->max_abs = occupancy;
>> + }
>> +
>> + qcnts->acc_sum += occupancy;
>> + if (++qcnts->cnt >= BCM_VK_QSTATS_ACC_CNT) {
>> + /* log average and clear counters */
>> + dev_info(&vk->pdev->dev,
>> + "%s[%d]: Max: [%3d/%3d] Acc %d num %d, Aver %d\n",
>> + tag, qstats->q_num,
>> + qcnts->max_occ, qcnts->max_abs,
>> + qcnts->acc_sum,
>> + qcnts->cnt,
>> + qcnts->acc_sum / qcnts->cnt);
>> +
>> + qcnts->cnt = 0;
>> + qcnts->max_occ = 0;
>> + qcnts->acc_sum = 0;
>> + }
>> +}
>> +#endif
>> +
>> +/* number of retries when enqueue message fails before returning EAGAIN */
>> +#define BCM_VK_H2VK_ENQ_RETRY 10
>> +#define BCM_VK_H2VK_ENQ_RETRY_DELAY_MS 50
>> +
>> +bool bcm_vk_drv_access_ok(struct bcm_vk *vk)
>> +{
>> + return (!!atomic_read(&vk->msgq_inited));
>> +}
>> +
>> +static void bcm_vk_set_host_alert(struct bcm_vk *vk, uint32_t bit_mask)
>> +{
>> + struct bcm_vk_alert *alert = &vk->host_alert;
>> + unsigned long flags;
>> +
>> + /* use irqsave version as this maybe called inside timer interrupt */
>> + spin_lock_irqsave(&vk->host_alert_lock, flags);
>> + alert->notfs |= bit_mask;
>> + spin_unlock_irqrestore(&vk->host_alert_lock, flags);
>> +
>> + if (test_and_set_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload) == 0)
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +}
>> +
>> +/*
>> + * Heartbeat related defines
>> + * The heartbeat from host is a last resort. If stuck condition happens
>> + * on the card, firmware is supposed to detect it. Therefore, the heartbeat
>> + * values used will be more relaxed on the driver, which need to be bigger
>> + * than the watchdog timeout on the card.
>> + */
>> +#define BCM_VK_HB_TIMER_S 3
>> +#define BCM_VK_HB_TIMER_VALUE (BCM_VK_HB_TIMER_S * HZ)
>> +#define BCM_VK_HB_LOST_MAX 4
>> +
>> +static void bcm_vk_hb_poll(struct timer_list *t)
>> +{
>> + uint32_t uptime_s;
>> + struct bcm_vk_hb_ctrl *hb = container_of(t, struct bcm_vk_hb_ctrl,
>> + timer);
>> + struct bcm_vk *vk = container_of(hb, struct bcm_vk, hb_ctrl);
>> +
>> + if (bcm_vk_drv_access_ok(vk)) {
>> + /* read uptime from register and compare */
>> + uptime_s = vkread32(vk, BAR_0, BAR_OS_UPTIME);
>> +
>> + if (uptime_s == hb->last_uptime)
>> + hb->lost_cnt++;
>> +
>> + dev_dbg(&vk->pdev->dev, "Last uptime %d current %d, lost %d\n",
>> + hb->last_uptime, uptime_s, hb->lost_cnt);
>> +
>> + /*
>> + * if the interface goes down without any activity, a value
>> + * of 0xFFFFFFFF will be continuously read, and the detection
>> + * will be happened eventually.
>> + */
>> + hb->last_uptime = uptime_s;
>> + } else {
>> + /* reset heart beat lost cnt */
>> + hb->lost_cnt = 0;
>> + }
>> +
>> + /* next, check if heartbeat exceeds limit */
>> + if (hb->lost_cnt > BCM_VK_HB_LOST_MAX) {
>> + dev_err(&vk->pdev->dev, "Heartbeat Misses %d times, %d s!\n",
>> + BCM_VK_HB_LOST_MAX,
>> + BCM_VK_HB_LOST_MAX * BCM_VK_HB_TIMER_S);
>> +
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_HB_FAIL);
>> + }
>> + /* re-arm timer */
>> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
>> +}
>> +
>> +void bcm_vk_hb_init(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
>> +
>> + timer_setup(&hb->timer, bcm_vk_hb_poll, 0);
>> + mod_timer(&hb->timer, jiffies + BCM_VK_HB_TIMER_VALUE);
>> +}
>> +
>> +void bcm_vk_hb_deinit(struct bcm_vk *vk)
>> +{
>> + struct bcm_vk_hb_ctrl *hb = &vk->hb_ctrl;
>> +
>> + del_timer(&hb->timer);
>> +}
>> +
>> +static void bcm_vk_msgid_bitmap_clear(struct bcm_vk *vk,
>> + unsigned int start,
>> + unsigned int nbits)
>> +{
>> + spin_lock(&vk->msg_id_lock);
>> + bitmap_clear(vk->bmap, start, nbits);
>> + spin_unlock(&vk->msg_id_lock);
>> +}
>> +
>> +/*
>> + * allocate a ctx per file struct
>> + */
>> +static struct bcm_vk_ctx *bcm_vk_get_ctx(struct bcm_vk *vk,
>> + struct task_struct *ppid)
>> +{
>> + uint32_t i;
>> + struct bcm_vk_ctx *ctx = NULL;
>> + const pid_t pid = task_pid_nr(ppid);
>> + uint32_t hash_idx = hash_32(pid, VK_PID_HT_SHIFT_BIT);
>> +
>> + spin_lock(&vk->ctx_lock);
>> +
>> + /* check if it is in reset, if so, don't allow */
>> + if (vk->reset_ppid) {
>> + dev_err(&vk->pdev->dev,
>> + "No context allowed during reset by pid %d\n",
>> + task_pid_nr(vk->reset_ppid));
>> +
>> + goto in_reset_exit;
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
>> + if (!vk->ctx[i].in_use) {
>> + vk->ctx[i].in_use = true;
>> + ctx = &vk->ctx[i];
>> + break;
>> + }
>> + }
>> +
>> + if (!ctx) {
>> + dev_err(&vk->pdev->dev, "All context in use\n");
>> +
>> + goto all_in_use_exit;
>> + }
>> +
>> + /* set the pid and insert it to hash table */
>> + ctx->ppid = ppid;
>> + ctx->hash_idx = hash_idx;
>> + list_add_tail(&ctx->node, &vk->pid_ht[hash_idx].head);
>> +
>> + /* increase kref */
>> + kref_get(&vk->kref);
>> +
>> + /* clear counter */
>> + ctx->pend_cnt = 0;
>> +all_in_use_exit:
>> +in_reset_exit:
>> + spin_unlock(&vk->ctx_lock);
>> +
>> + return ctx;
>> +}
>> +
>> +static uint16_t bcm_vk_get_msg_id(struct bcm_vk *vk)
>> +{
>> + uint16_t rc = VK_MSG_ID_OVERFLOW;
>> + uint16_t test_bit_count = 0;
>> +
>> + spin_lock(&vk->msg_id_lock);
>> + while (test_bit_count < VK_MSG_ID_BITMAP_SIZE) {
>> + vk->msg_id++;
>> + vk->msg_id = vk->msg_id & VK_MSG_ID_BITMAP_MASK;
>> + if (test_bit(vk->msg_id, vk->bmap)) {
>> + test_bit_count++;
>> + continue;
>> + }
>> + rc = vk->msg_id;
>> + bitmap_set(vk->bmap, vk->msg_id, 1);
>> + break;
>> + }
>> + spin_unlock(&vk->msg_id_lock);
>> +
>> + return rc;
>> +}
>> +
>> +static int bcm_vk_free_ctx(struct bcm_vk *vk, struct bcm_vk_ctx *ctx)
>> +{
>> + uint32_t idx;
>> + uint32_t hash_idx;
>> + pid_t pid;
>> + struct bcm_vk_ctx *entry;
>> + int count = 0;
>> +
>> + if (ctx == NULL) {
>> + dev_err(&vk->pdev->dev, "NULL context detected\n");
>> + return -EINVAL;
>> + }
>> + idx = ctx->idx;
>> + pid = task_pid_nr(ctx->ppid);
>> +
>> + spin_lock(&vk->ctx_lock);
>> +
>> + if (!vk->ctx[idx].in_use) {
>> + dev_err(&vk->pdev->dev, "context[%d] not in use!\n", idx);
>> + } else {
>> + vk->ctx[idx].in_use = false;
>> + vk->ctx[idx].miscdev = NULL;
>> +
>> + /* Remove it from hash list and see if it is the last one. */
>> + list_del(&ctx->node);
>> + hash_idx = ctx->hash_idx;
>> + list_for_each_entry(entry, &vk->pid_ht[hash_idx].head, node) {
>> + if (task_pid_nr(entry->ppid) == pid)
>> + count++;
>> + }
>> + }
>> +
>> + spin_unlock(&vk->ctx_lock);
>> +
>> + return count;
>> +}
>> +
>> +static void bcm_vk_free_wkent(struct device *dev, struct bcm_vk_wkent *entry)
>> +{
>> + bcm_vk_sg_free(dev, entry->dma, VK_DMA_MAX_ADDRS);
>> +
>> + kfree(entry->vk2h_msg);
>> + kfree(entry);
>> +}
>> +
>> +static void bcm_vk_drain_all_pend(struct device *dev,
>> + struct bcm_vk_msg_chan *chan,
>> + struct bcm_vk_ctx *ctx)
>> +{
>> + uint32_t num;
>> + struct bcm_vk_wkent *entry, *tmp;
>> + struct bcm_vk *vk;
>> + struct list_head del_q;
>> +
>> + if (ctx)
>> + vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> +
>> + INIT_LIST_HEAD(&del_q);
>> + spin_lock(&chan->pendq_lock);
>> + for (num = 0; num < chan->q_nr; num++) {
>> + list_for_each_entry_safe(entry, tmp, &chan->pendq[num], node) {
>> + if (ctx == NULL) {
>> + list_del(&entry->node);
>> + list_add_tail(&entry->node, &del_q);
>> + } else if (entry->ctx->idx == ctx->idx) {
>> + struct vk_msg_blk *msg;
>> + int bit_set;
>> + bool responded;
>> +
>> + /* if it is specific ctx, log for any stuck */
>> + msg = entry->h2vk_msg;
>> + bit_set = test_bit(msg->msg_id, vk->bmap);
>> + responded = entry->vk2h_msg ? true : false;
>> + dev_info(dev,
>> + "Drained: fid %u size %u msg 0x%x(seq-%x) ctx 0x%x[fd-%d] args:[0x%x 0x%x] resp %s, bmap %d\n",
>> + msg->function_id, msg->size,
>> + msg->msg_id, entry->seq_num,
>> + msg->context_id, entry->ctx->idx,
>> + msg->args[0], msg->args[1],
>> + responded ? "T" : "F", bit_set);
>> + list_del(&entry->node);
>> + list_add_tail(&entry->node, &del_q);
>> + if (responded)
>> + ctx->pend_cnt--;
>> + else if (bit_set)
>> + bcm_vk_msgid_bitmap_clear(vk,
>> + msg->msg_id,
>> + 1);
>> + }
>> + }
>> + }
>> + spin_unlock(&chan->pendq_lock);
>> +
>> + /* batch clean up */
>> + num = 0;
>> + list_for_each_entry_safe(entry, tmp, &del_q, node) {
>> + list_del(&entry->node);
>> + bcm_vk_free_wkent(dev, entry);
>> + num++;
>> + }
>> + if (num)
>> + dev_info(dev, "Total drained items %d\n", num);
>> +}
>> +
>> +bool bcm_vk_msgq_marker_valid(struct bcm_vk *vk)
>> +{
>> + uint32_t rdy_marker = 0;
>> + uint32_t fw_status;
>> +
>> + fw_status = vkread32(vk, BAR_0, VK_BAR_FWSTS);
>> +
>> + if ((fw_status & VK_FWSTS_READY) == VK_FWSTS_READY)
>> + rdy_marker = vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> +
>> + return (rdy_marker == VK_BAR1_MSGQ_RDY_MARKER);
>> +}
>> +
>> +/*
>> + * Function to sync up the messages queue info that is provided by BAR1
>> + */
>> +int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync)
>> +{
>> + struct bcm_vk_msgq *msgq = NULL;
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t msgq_off;
>> + uint32_t num_q;
>> + struct bcm_vk_msg_chan *chan_list[] = {&vk->h2vk_msg_chan,
>> + &vk->vk2h_msg_chan};
>> + struct bcm_vk_msg_chan *chan = NULL;
>> + int i, j;
>> + int ret = 0;
>> +
>> + /*
>> + * If the driver is loaded at startup where vk OS is not up yet,
>> + * the msgq-info may not be available until a later time. In
>> + * this case, we skip and the sync function is supposed to be
>> + * called again.
>> + */
>> + if (!bcm_vk_msgq_marker_valid(vk)) {
>> + dev_info(dev, "BAR1 msgq marker not initialized.\n");
>> + return ret;
> This is "return 0;" which seems wrong.
>
>> + }
>> +
>> + msgq_off = vkread32(vk, BAR_1, VK_BAR1_MSGQ_CTRL_OFF);
>> +
>> + /* each side is always half the total */
>> + num_q = vk->h2vk_msg_chan.q_nr = vk->vk2h_msg_chan.q_nr =
>> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_NR) / 2;
>> +
>> + /* first msgq location */
>> + msgq = (struct bcm_vk_msgq *)(vk->bar[BAR_1] + msgq_off);
>> +
>> + /*
>> + * if this function is called when it is already inited,
>> + * something is wrong
>> + */
>> + if (bcm_vk_drv_access_ok(vk) && (!force_sync)) {
> Delete extra parens.
done
>
>> + dev_err(dev, "Msgq info already in sync\n");
>> + ret = -EPERM;
>> + goto already_inited;
> Please do a direct "return -EPERM;"
done
>
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(chan_list); i++) {
>> + chan = chan_list[i];
>> + memset(chan->sync_qinfo, 0, sizeof(chan->sync_qinfo));
>> +
>> + for (j = 0; j < num_q; j++) {
>> + chan->msgq[j] = msgq;
>> +
>> + dev_info(dev,
>> + "MsgQ[%d] type %d num %d, @ 0x%x, rd_idx %d wr_idx %d, size %d, nxt 0x%x\n",
>> + j,
>> + chan->msgq[j]->type,
>> + chan->msgq[j]->num,
>> + chan->msgq[j]->start,
>> + chan->msgq[j]->rd_idx,
>> + chan->msgq[j]->wr_idx,
>> + chan->msgq[j]->size,
>> + chan->msgq[j]->nxt);
> It would be shorter to replace chan->msgq[j]->type with msgq->type.
cleanup done
>
>> +
>> + /* formulate and record static info */
>> + chan->sync_qinfo[j].q_start =
>> + vk->bar[BAR_1] + chan->msgq[j]->start;
> Maybe set "qinfo = &chan->sync_qinfo[j];" at the start of the loop so
> that these fit on one line.
>
> qinfo->q_start = vk->bar[BAR_1] + msgq->start;
done.
>
>> + chan->sync_qinfo[j].q_size = chan->msgq[j]->size;
>> + /* set low threshold as 50% or 1/2 */
>> + chan->sync_qinfo[j].q_low =
>> + chan->sync_qinfo[j].q_size >> 1;
>> + chan->sync_qinfo[j].q_mask =
>> + chan->sync_qinfo[j].q_size - 1;
>> +
>> + msgq = (struct bcm_vk_msgq *)
>> + ((char *)msgq + sizeof(*msgq) + msgq->nxt);
>> +
>> + rmb(); /* do a read mb to guarantee */
>> + }
>> + }
>> + atomic_set(&vk->msgq_inited, 1);
>> +
>> +already_inited:
>> + return ret;
>> +}
>> +
>> +static int bcm_vk_msg_chan_init(struct bcm_vk_msg_chan *chan)
>> +{
>> + int rc = 0;
>> + uint32_t i;
>> +
>> + mutex_init(&chan->msgq_mutex);
>> + spin_lock_init(&chan->pendq_lock);
>> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
>> + INIT_LIST_HEAD(&chan->pendq[i]);
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + chan->qstats[i].q_num = i;
>> +#endif
>> + }
>> +
>> + return rc;
>
> return 0;
done
>
>> +}
>> +
>> +static void bcm_vk_append_pendq(struct bcm_vk_msg_chan *chan, uint16_t q_num,
>> + struct bcm_vk_wkent *entry)
>> +{
>> + spin_lock(&chan->pendq_lock);
>> + list_add_tail(&entry->node, &chan->pendq[q_num]);
>> + if (entry->vk2h_msg)
>> + entry->ctx->pend_cnt++;
>> + spin_unlock(&chan->pendq_lock);
>> +}
>> +
>> +static uint32_t bcm_vk_append_ib_sgl(struct bcm_vk *vk,
>> + struct bcm_vk_wkent *entry,
>> + struct _vk_data *data,
>> + unsigned int num_planes)
>> +{
>> + unsigned int i;
>> + unsigned int item_cnt = 0;
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
>> + struct vk_msg_blk *msg = &entry->h2vk_msg[0];
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + uint32_t ib_sgl_size = 0;
>> + uint8_t *buf = (uint8_t *)&entry->h2vk_msg[entry->h2vk_blks];
>> + uint32_t avail;
>> +
>> + /* check if high watermark is hit, and if so, skip */
>> + msgq = chan->msgq[msg->queue_id];
>> + qinfo = &chan->sync_qinfo[msg->queue_id];
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> + if (avail < qinfo->q_low) {
>> + dev_dbg(dev, "Skip inserting inband SGL, [0x%x/0x%x]\n",
>> + avail, qinfo->q_size);
>> + return ib_sgl_size;
> return 0;
done
>
>> + }
>> +
>> + for (i = 0; i < num_planes; i++) {
>> + if (data[i].address &&
>> + (ib_sgl_size + data[i].size) <= vk->ib_sgl_size) {
>> +
>> + item_cnt++;
>> + memcpy(buf, entry->dma[i].sglist, data[i].size);
>> + ib_sgl_size += data[i].size;
>> + buf += data[i].size;
>> + }
>> + }
>> +
>> + dev_dbg(dev, "Num %u sgl items appended, size 0x%x, room 0x%x\n",
>> + item_cnt, ib_sgl_size, vk->ib_sgl_size);
>> +
>> + /* round up size */
>> + ib_sgl_size = (ib_sgl_size + VK_MSGQ_BLK_SIZE - 1)
>> + >> VK_MSGQ_BLK_SZ_SHIFT;
>> +
>> + return ib_sgl_size;
>> +}
>> +
>> +void bcm_h2vk_doorbell(struct bcm_vk *vk, uint32_t q_num,
>> + uint32_t db_val)
>> +{
>> + /* press door bell based on q_num */
>> + vkwrite32(vk,
>> + db_val,
>> + BAR_0,
>> + VK_BAR0_REGSEG_DB_BASE + q_num * VK_BAR0_REGSEG_DB_REG_GAP);
>> +}
>> +
>> +static int bcm_h2vk_msg_enqueue(struct bcm_vk *vk, struct bcm_vk_wkent *entry)
>> +{
>> + static uint32_t seq_num;
>> + struct bcm_vk_msg_chan *chan = &vk->h2vk_msg_chan;
>> + struct device *dev = &vk->pdev->dev;
>> + struct vk_msg_blk *src = &entry->h2vk_msg[0];
>> +
>> + volatile struct vk_msg_blk *dst;
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + uint32_t q_num = src->queue_id;
>> + uint32_t wr_idx; /* local copy */
>> + uint32_t i;
>> + uint32_t avail;
>> + uint32_t retry;
>> +
>> + if (entry->h2vk_blks != src->size + 1) {
>> + dev_err(dev, "number of blks %d not matching %d MsgId[0x%x]: func %d ctx 0x%x\n",
>> + entry->h2vk_blks,
>> + src->size + 1,
>> + src->msg_id,
>> + src->function_id,
>> + src->context_id);
>> + return -EMSGSIZE;
>> + }
>> +
>> + msgq = chan->msgq[q_num];
>> + qinfo = &chan->sync_qinfo[q_num];
>> +
>> + rmb(); /* start with a read barrier */
>> + mutex_lock(&chan->msgq_mutex);
>> +
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + bcm_vk_update_qstats(vk, "h2vk", &chan->qstats[q_num],
>> + qinfo->q_size - avail);
>> +#endif
>> + /* if not enough space, return EAGAIN and let app handles it */
>> + retry = 0;
>> + while ((avail < entry->h2vk_blks)
>> + && (retry++ < BCM_VK_H2VK_ENQ_RETRY)) {
>> + mutex_unlock(&chan->msgq_mutex);
>> +
>> + msleep(BCM_VK_H2VK_ENQ_RETRY_DELAY_MS);
>> + mutex_lock(&chan->msgq_mutex);
>> + avail = VK_MSGQ_AVAIL_SPACE(msgq, qinfo);
>> + }
>> + if (retry > BCM_VK_H2VK_ENQ_RETRY) {
>> + mutex_unlock(&chan->msgq_mutex);
>> + return -EAGAIN;
>> + }
>> +
>> + /* at this point, mutex is taken and there is enough space */
>> + entry->seq_num = seq_num++; /* update debug seq number */
>> + wr_idx = msgq->wr_idx;
>> +
>> + if (wr_idx >= qinfo->q_size) {
>> + dev_crit(dev, "Invalid wr_idx 0x%x => max 0x%x!",
>> + wr_idx, qinfo->q_size);
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(vk, ERR_LOG_HOST_PCIE_DWN);
>> + goto idx_err;
>> + }
>> +
>> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
>> + for (i = 0; i < entry->h2vk_blks; i++) {
>> + *dst = *src;
>> +
>> + bcm_vk_h2vk_verify_blk(dev, src, dst);
>> +
>> + src++;
>> + wr_idx = VK_MSGQ_INC(qinfo, wr_idx, 1);
>> + dst = VK_MSGQ_BLK_ADDR(qinfo, wr_idx);
>> + }
>> +
>> + /* flush the write pointer */
>> + msgq->wr_idx = wr_idx;
>> + wmb(); /* flush */
>> +
>> + bcm_vk_h2vk_verify_idx(dev, "wr_idx", &msgq->wr_idx, wr_idx);
>> +
>> + /* log new info for debugging */
>> + dev_dbg(dev,
>> + "MsgQ[%d] [Rd Wr] = [%d %d] blks inserted %d - Q = [u-%d a-%d]/%d\n",
>> + msgq->num,
>> + msgq->rd_idx, msgq->wr_idx, entry->h2vk_blks,
>> + VK_MSGQ_OCCUPIED(msgq, qinfo),
>> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
>> + msgq->size);
>> + /*
>> + * press door bell based on queue number. 1 is added to the wr_idx
>> + * to avoid the value of 0 appearing on the VK side to distinguish
>> + * from initial value.
>> + */
>> + bcm_h2vk_doorbell(vk, q_num, wr_idx + 1);
>> +idx_err:
>> + mutex_unlock(&chan->msgq_mutex);
>> + return 0;
>> +}
>> +
>> +int bcm_vk_send_shutdown_msg(struct bcm_vk *vk, uint32_t shut_type,
>> + pid_t pid)
>> +{
>> + int rc = 0;
>> + struct bcm_vk_wkent *entry;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /*
>> + * check if the marker is still good. Sometimes, the PCIe interface may
>> + * have gone done, and if so and we ship down thing based on broken
>> + * values, kernel may panic.
>> + */
>> + if (!bcm_vk_msgq_marker_valid(vk)) {
>> + dev_info(dev, "PCIe comm chan - invalid marker (0x%x)!\n",
>> + vkread32(vk, BAR_1, VK_BAR1_MSGQ_DEF_RDY));
>> + return -EINVAL;
>> + }
>> +
>> + entry = kzalloc(sizeof(struct bcm_vk_wkent) +
>> + sizeof(struct vk_msg_blk), GFP_KERNEL);
>> + if (!entry)
>> + return -ENOMEM;
>> +
>> + /* just fill up non-zero data */
>> + entry->h2vk_msg[0].function_id = VK_FID_SHUTDOWN;
>> + entry->h2vk_msg[0].queue_id = 0; /* use highest queue */
>> + entry->h2vk_blks = 1; /* always 1 block */
>> +
>> + entry->h2vk_msg[0].args[0] = shut_type;
>> + entry->h2vk_msg[0].args[1] = pid;
>> +
>> + rc = bcm_h2vk_msg_enqueue(vk, entry);
>> + if (rc)
>> + dev_err(dev,
>> + "Sending shutdown message to q %d for pid %d fails.\n",
>> + entry->h2vk_msg[0].queue_id, pid);
>> +
>> + kfree(entry);
>> +
>> + return rc;
>> +}
>> +
>> +int bcm_vk_handle_last_sess(struct bcm_vk *vk, struct task_struct *ppid)
>> +{
>> + int rc = 0;
>> + pid_t pid = task_pid_nr(ppid);
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /*
>> + * don't send down or do anything if message queue is not initialized
>> + * and if it is the reset session, clear it.
>> + */
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> +
>> + if (vk->reset_ppid == ppid)
>> + vk->reset_ppid = NULL;
>> + return -EPERM;
>> + }
>> +
>> + dev_dbg(dev, "No more sessions, shut down pid %d\n", pid);
>> +
>> + /* only need to do it if it is not the reset process */
>> + if (vk->reset_ppid != ppid)
>> + rc = bcm_vk_send_shutdown_msg(vk, VK_SHUTDOWN_PID, pid);
>> + else
>> + /* reset the pointer if it is exiting last session */
>> + vk->reset_ppid = NULL;
>> +
>> + return rc;
>> +}
>> +
>> +static struct bcm_vk_wkent *bcm_vk_find_pending(struct bcm_vk *vk,
>> + struct bcm_vk_msg_chan *chan,
>> + uint16_t q_num,
>> + uint16_t msg_id)
>> +{
>> + bool found = false;
>> + struct bcm_vk_wkent *entry;
>> +
>> + spin_lock(&chan->pendq_lock);
>> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
>> +
>> + if (entry->h2vk_msg[0].msg_id == msg_id) {
>> + list_del(&entry->node);
>> + found = true;
>> + bcm_vk_msgid_bitmap_clear(vk, msg_id, 1);
>> + break;
>> + }
>> + }
>> + spin_unlock(&chan->pendq_lock);
>> + return ((found) ? entry : NULL);
>> +}
>> +
>> +static uint32_t bcm_vk2h_msg_dequeue(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
>> + struct vk_msg_blk *data;
>> + volatile struct vk_msg_blk *src;
>> + struct vk_msg_blk *dst;
>> + struct bcm_vk_msgq *msgq;
>> + struct bcm_vk_sync_qinfo *qinfo;
>> + struct bcm_vk_wkent *entry;
>> + uint32_t rd_idx;
>> + uint32_t q_num, j;
>> + uint32_t num_blks;
>> + uint32_t total = 0;
>> +
>> + /*
>> + * drain all the messages from the queues, and find its pending
>> + * entry in the h2vk queue, based on msg_id & q_num, and move the
>> + * entry to the vk2h pending queue, waiting for user space
>> + * program to extract
>> + */
>> + mutex_lock(&chan->msgq_mutex);
>> + rmb(); /* start with a read barrier */
>> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
>> + msgq = chan->msgq[q_num];
>> + qinfo = &chan->sync_qinfo[q_num];
>> +
>> + while (!VK_MSGQ_EMPTY(msgq)) {
>> +
>> + /*
>> + * Make a local copy and get pointer to src blk
>> + * The rd_idx is masked before getting the pointer to
>> + * avoid out of bound access in case the interface goes
>> + * down. It will end up pointing to the last block in
>> + * the buffer, but subsequent src->size check would be
>> + * able to catch this.
>> + */
>> + rd_idx = msgq->rd_idx;
>> + src = VK_MSGQ_BLK_ADDR(qinfo,
>> + rd_idx & VK_MSGQ_SIZE_MASK(qinfo));
>> +
>> + if ((rd_idx >= qinfo->q_size)
>> + || (src->size > (qinfo->q_size - 1))) {
>> + dev_crit(dev,
>> + "Invalid rd_idx 0x%x or size 0x%x => max 0x%x!",
>> + rd_idx, src->size, qinfo->q_size);
>> + bcm_vk_blk_drv_access(vk);
>> + bcm_vk_set_host_alert(
>> + vk, ERR_LOG_HOST_PCIE_DWN);
>> + goto idx_err;
>> + }
>> +
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + bcm_vk_update_qstats(vk, "vk2h", &chan->qstats[q_num],
>> + VK_MSGQ_OCCUPIED(msgq, qinfo));
>> +#endif
>> + num_blks = src->size + 1;
>> + data = kzalloc(num_blks * VK_MSGQ_BLK_SIZE, GFP_KERNEL);
>> + if (data) {
>> + /* copy messages and linearize it */
>> + dst = data;
>> + for (j = 0; j < num_blks; j++) {
>> + *dst = *src;
>> +
>> + dst++;
>> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, 1);
>> + src = VK_MSGQ_BLK_ADDR(qinfo, rd_idx);
>> + }
>> + total++;
>> + } else {
>> + dev_crit(dev, "Error allocating memory\n");
>> + /* just keep draining..... */
>> + rd_idx = VK_MSGQ_INC(qinfo, rd_idx, num_blks);
>> + }
>> +
>> + /* flush rd pointer after a message is dequeued */
>> + msgq->rd_idx = rd_idx;
>> + mb(); /* do both rd/wr as we are extracting data out */
>> +
>> + bcm_vk_h2vk_verify_idx(dev, "rd_idx",
>> + &msgq->rd_idx, rd_idx);
>> +
>> + /* log new info for debugging */
>> + dev_dbg(dev,
>> + "MsgQ[%d] [Rd Wr] = [%d %d] blks extracted %d - Q = [u-%d a-%d]/%d\n",
>> + msgq->num,
>> + msgq->rd_idx, msgq->wr_idx, num_blks,
>> + VK_MSGQ_OCCUPIED(msgq, qinfo),
>> + VK_MSGQ_AVAIL_SPACE(msgq, qinfo),
>> + msgq->size);
>> +
>> + /*
>> + * No need to search if it is an autonomous one-way
>> + * message from driver, as these messages do not bear
>> + * a h2vk pending item. Currently, only the shutdown
>> + * message falls into this category.
>> + */
>> + if (data->function_id == VK_FID_SHUTDOWN) {
>> + kfree(data);
>> + continue;
>> + }
>> +
>> + /* lookup original message in h2vk direction */
>> + entry = bcm_vk_find_pending(vk,
>> + &vk->h2vk_msg_chan,
>> + q_num,
>> + data->msg_id);
>> +
>> + /*
>> + * if there is message to does not have prior send,
>> + * this is the location to add here
>> + */
>> + if (entry) {
>> + entry->vk2h_blks = num_blks;
>> + entry->vk2h_msg = data;
>> + bcm_vk_append_pendq(&vk->vk2h_msg_chan,
>> + q_num, entry);
>> +
>> + } else {
>> + dev_crit(dev,
>> + "Could not find MsgId[0x%x] for resp func %d bmap %d\n",
>> + data->msg_id, data->function_id,
>> + test_bit(data->msg_id, vk->bmap));
>> + kfree(data);
>> + }
>> +
>> + }
>> + }
>> +idx_err:
>> + mutex_unlock(&chan->msgq_mutex);
>> + dev_dbg(dev, "total %d drained from queues\n", total);
>> +
>> + return total;
>> +}
>> +
>> +/*
>> + * deferred work queue for draining and auto download.
>> + */
>> +static void bcm_vk_wq_handler(struct work_struct *work)
>> +{
>> + struct bcm_vk *vk = container_of(work, struct bcm_vk, wq_work);
>> + struct device *dev = &vk->pdev->dev;
>> + uint32_t tot;
>> +
>> + /* check wq offload bit map to perform various operations */
>> + if (test_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload)) {
>> + /* clear bit right the way for notification */
>> + clear_bit(BCM_VK_WQ_NOTF_PEND, vk->wq_offload);
>> + bcm_vk_handle_notf(vk);
>> + }
>> + if (test_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload)) {
>> + bcm_vk_auto_load_all_images(vk);
>> +
>> + /*
>> + * at the end of operation, clear AUTO bit and pending
>> + * bit
>> + */
>> + clear_bit(BCM_VK_WQ_DWNLD_AUTO, vk->wq_offload);
>> + clear_bit(BCM_VK_WQ_DWNLD_PEND, vk->wq_offload);
>> + }
>> +
>> + /* next, try to drain */
>> + tot = bcm_vk2h_msg_dequeue(vk);
>> +
>> + if (tot == 0)
>> + dev_dbg(dev, "Spurious trigger for workqueue\n");
>> +}
>> +
>> +/*
>> + * init routine for all required data structures
>> + */
>> +static int bcm_vk_data_init(struct bcm_vk *vk)
>> +{
>> + int rc = 0;
>> + int i;
>> +
>> + spin_lock_init(&vk->ctx_lock);
>> + for (i = 0; i < ARRAY_SIZE(vk->ctx); i++) {
>> + vk->ctx[i].in_use = false;
>> + vk->ctx[i].idx = i; /* self identity */
>> + vk->ctx[i].miscdev = NULL;
>> + }
>> + spin_lock_init(&vk->msg_id_lock);
>> + spin_lock_init(&vk->host_alert_lock);
>> + vk->msg_id = 0;
>> +
>> + /* initialize hash table */
>> + for (i = 0; i < VK_PID_HT_SZ; i++)
>> + INIT_LIST_HEAD(&vk->pid_ht[i].head);
>> +
>> + INIT_WORK(&vk->wq_work, bcm_vk_wq_handler);
>> + return rc;
> return 0;
done
>
>> +}
>> +
>> +irqreturn_t bcm_vk_msgq_irqhandler(int irq, void *dev_id)
>> +{
>> + struct bcm_vk *vk = dev_id;
>> +
>> + if (!bcm_vk_drv_access_ok(vk)) {
>> + dev_err(&vk->pdev->dev,
>> + "Interrupt %d received when msgq not inited\n", irq);
>> + goto skip_schedule_work;
>> + }
>> +
>> + queue_work(vk->wq_thread, &vk->wq_work);
>> +
>> +skip_schedule_work:
>> + return IRQ_HANDLED;
>> +}
>> +
>> +int bcm_vk_open(struct inode *inode, struct file *p_file)
>> +{
>> + struct bcm_vk_ctx *ctx;
>> + struct miscdevice *miscdev = (struct miscdevice *)p_file->private_data;
>> + struct bcm_vk *vk = container_of(miscdev, struct bcm_vk, miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + int rc = 0;
>> +
>> + /* get a context and set it up for file */
>> + ctx = bcm_vk_get_ctx(vk, current);
>> + if (!ctx) {
>> + dev_err(dev, "Error allocating context\n");
>> + rc = -ENOMEM;
>> + } else {
>> +
>> + /*
>> + * set up context and replace private data with context for
>> + * other methods to use. Reason for the context is because
>> + * it is allowed for multiple sessions to open the sysfs, and
>> + * for each file open, when upper layer query the response,
>> + * only those that are tied to a specific open should be
>> + * returned. The context->idx will be used for such binding
>> + */
>> + ctx->miscdev = miscdev;
>> + p_file->private_data = ctx;
>> + dev_dbg(dev, "ctx_returned with idx %d, pid %d\n",
>> + ctx->idx, task_pid_nr(ctx->ppid));
>> + }
>> + return rc;
>> +}
>> +
>> +ssize_t bcm_vk_read(struct file *p_file, char __user *buf, size_t count,
>> + loff_t *f_pos)
>> +{
>> + ssize_t rc = -ENOMSG;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
>> + miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_msg_chan *chan = &vk->vk2h_msg_chan;
>> + struct bcm_vk_wkent *entry = NULL;
>> + uint32_t q_num;
>> + uint32_t rsp_length;
>> + bool found = false;
>> +
>> + if (!bcm_vk_drv_access_ok(vk))
>> + return -EPERM;
>> +
>> + dev_dbg(dev, "Buf count %ld\n", count);
>> + found = false;
>> +
>> + /*
>> + * search through the pendq on the vk2h chan, and return only those
>> + * that belongs to the same context. Search is always from the high to
>> + * the low priority queues
>> + */
>> + spin_lock(&chan->pendq_lock);
>> + for (q_num = 0; q_num < chan->q_nr; q_num++) {
>> + list_for_each_entry(entry, &chan->pendq[q_num], node) {
>> + if (entry->ctx->idx == ctx->idx) {
>> + if (count >=
>> + (entry->vk2h_blks * VK_MSGQ_BLK_SIZE)) {
>> + list_del(&entry->node);
>> + ctx->pend_cnt--;
>> + found = true;
>> + } else {
>> + /* buffer not big enough */
>> + rc = -EMSGSIZE;
>> + }
>> + goto bcm_vk_read_loop_exit;
>> + }
>> + }
>> + }
>> + bcm_vk_read_loop_exit:
>> + spin_unlock(&chan->pendq_lock);
>> +
>> + if (found) {
>> + /* retrieve the passed down msg_id */
>> + entry->vk2h_msg[0].msg_id = entry->usr_msg_id;
>> + rsp_length = entry->vk2h_blks * VK_MSGQ_BLK_SIZE;
>> + if (copy_to_user(buf, entry->vk2h_msg, rsp_length) == 0)
>> + rc = rsp_length;
> The error code should be -EFAULT if this message fails (vs -ENOMSG).
done
>
>
>> +
>> + bcm_vk_free_wkent(dev, entry);
>> + } else if (rc == -EMSGSIZE) {
>> + struct vk_msg_blk tmp_msg = entry->vk2h_msg[0];
>> +
>> + /*
>> + * in this case, return just the first block, so
>> + * that app knows what size it is looking for.
>> + */
>> + tmp_msg.msg_id = entry->usr_msg_id;
>> + tmp_msg.size = entry->vk2h_blks - 1;
>> + if (copy_to_user(buf, &tmp_msg, VK_MSGQ_BLK_SIZE) != 0) {
>> + dev_err(dev, "Error return 1st block in -EMSGSIZE\n");
>> + rc = -EFAULT;
>> + }
>> + }
>> + return rc;
>> +}
>> +
>> +ssize_t bcm_vk_write(struct file *p_file, const char __user *buf,
>> + size_t count, loff_t *f_pos)
>> +{
>> + ssize_t rc = -EPERM;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk,
>> + miscdev);
>> + struct bcm_vk_msgq *msgq;
>> + struct device *dev = &vk->pdev->dev;
>> + struct bcm_vk_wkent *entry;
>> + uint32_t sgl_extra_blks;
>> +
>> + if (!bcm_vk_drv_access_ok(vk))
>> + return -EPERM;
>> +
>> + dev_dbg(dev, "Msg count %ld\n", count);
>> +
>> + /* first, do sanity check where count should be multiple of basic blk */
>> + if (count & (VK_MSGQ_BLK_SIZE - 1)) {
>> + dev_err(dev, "Failure with size %ld not multiple of %ld\n",
>> + count, VK_MSGQ_BLK_SIZE);
>> + rc = -EBADR;
> #define EBADR 53 /* Invalid request descriptor */
>
> This should just be -EINVAL;
done
>
>
>> + goto bcm_vk_write_err;
> These label names are too long. They don't need the function prefix.
removed all bcm_vk_ from labels.
>
>> + }
>> +
>> + /* allocate the work entry + buffer for size count and inband sgl */
>> + entry = kzalloc(sizeof(struct bcm_vk_wkent) + count + vk->ib_sgl_size,
>> + GFP_KERNEL);
>> + if (!entry) {
>> + rc = -ENOMEM;
>> + goto bcm_vk_write_err;
>> + }
>> +
>> + /* now copy msg from user space, and then formulate the wk ent */
>> + if (copy_from_user(&entry->h2vk_msg[0], buf, count))
>> + goto bcm_vk_write_free_ent;
> This is returning -EPERM instead of -EFAULT.
corrected.
>
>> +
>> + entry->h2vk_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
>> + entry->ctx = ctx;
>> +
>> + /* do a check on the blk size which could not exceed queue space */
>> + msgq = vk->h2vk_msg_chan.msgq[entry->h2vk_msg[0].queue_id];
>> + if (entry->h2vk_blks + (vk->ib_sgl_size >> VK_MSGQ_BLK_SZ_SHIFT)
>> + > (msgq->size - 1)) {
>> + dev_err(dev, "Blk size %d exceed max queue size allowed %d\n",
>> + entry->h2vk_blks, msgq->size - 1);
>> + rc = -EOVERFLOW;
> This should probably just be -EINVAL as well.
done
>
>
>> + goto bcm_vk_write_free_ent;
>> + }
>> +
>> + /* Use internal message id */
>> + entry->usr_msg_id = entry->h2vk_msg[0].msg_id;
>> + rc = bcm_vk_get_msg_id(vk);
>> + if (rc == VK_MSG_ID_OVERFLOW) {
>> + dev_err(dev, "msg_id overflow\n");
>> + rc = -EOVERFLOW;
>> + goto bcm_vk_write_free_ent;
>> + }
>> + entry->h2vk_msg[0].msg_id = rc;
>> +
>> + dev_dbg(dev,
>> + "Message ctx id %d, usr_msg_id 0x%x sent msg_id 0x%x\n",
>> + ctx->idx, entry->usr_msg_id,
>> + entry->h2vk_msg[0].msg_id);
>> +
>> + /* Convert any pointers to sg list */
>> + if (entry->h2vk_msg[0].function_id == VK_FID_TRANS_BUF) {
>> + unsigned int num_planes;
>> + int dir;
>> + struct _vk_data *data;
>> +
>> + /*
>> + * check if we are in reset, if so, no buffer transfer is
>> + * allowed and return error.
>> + */
>> + if (vk->reset_ppid) {
>> + dev_dbg(dev, "No Transfer allowed during reset, pid %d.\n",
>> + task_pid_nr(ctx->ppid));
>> + rc = -EACCES;
>> + goto bcm_vk_write_free_msgid;
>> + }
>> +
>> + num_planes = entry->h2vk_msg[0].args[0] & VK_CMD_PLANES_MASK;
>> + if ((entry->h2vk_msg[0].args[0] & VK_CMD_MASK)
>> + == VK_CMD_DOWNLOAD) {
>> + /* Memory transfer from vk device */
> There are a bunch of pretty obvious comments that should just be
> deleted.
done
>
>> + dir = DMA_FROM_DEVICE;
>> + } else {
>> + /* Memory transfer to vk device */
done
>> + dir = DMA_TO_DEVICE;
>> + }
>> +
>> + /* Calculate vk_data location */
>> + /* Go to end of the message */
>> + data = (struct _vk_data *)
>> + &(entry->h2vk_msg[entry->h2vk_msg[0].size + 1]);
>> + /* Now back up to the start of the pointers */
>> + data -= num_planes;
>> +
>> + /* Convert user addresses to DMA SG List */
>> + rc = bcm_vk_sg_alloc(dev, entry->dma, dir, data, num_planes);
>> + if (rc)
>> + goto bcm_vk_write_free_msgid;
>> +
>> + /* try to embed inband sgl */
>> + sgl_extra_blks = bcm_vk_append_ib_sgl(vk, entry, data,
>> + num_planes);
>> + entry->h2vk_blks += sgl_extra_blks;
>> + entry->h2vk_msg[0].size += sgl_extra_blks;
>> + }
>> +
>> + /*
>> + * store wk ent to pending queue until a response is got. This needs to
> ^^
> vk?
actually, wk ent is correct but changed comment to "work entry" to spell
it out.
>
>> + * be done before enqueuing the message
>> + */
>> + bcm_vk_append_pendq(&vk->h2vk_msg_chan, entry->h2vk_msg[0].queue_id,
>> + entry);
>> +
>> + rc = bcm_h2vk_msg_enqueue(vk, entry);
>> + if (rc) {
>> + dev_err(dev, "Fail to enqueue msg to h2vk queue\n");
>> +
>> + /* remove message from pending list */
>> + entry = bcm_vk_find_pending(vk,
>> + &vk->h2vk_msg_chan,
>> + entry->h2vk_msg[0].queue_id,
>> + entry->h2vk_msg[0].msg_id);
>
> Can bcm_vk_find_pending() return NULL here (leading to an Oops when we
> do entry->h2vk_msg[0].msg_id and why can't we just use the previous
> value of entry? The comment doesn't help me.
Entry won't be NULL.  It needs to be removed from pending q if it fails
to be enqueued in the msg q to clean up the pending q on failure.  On
normal operation the msg is enqueued successfully and will be dequeued
from pending q on message completion.  Renamed bcm_vk_find_pending to
bcm_vk_dequeue_pending to help.

>> + goto bcm_vk_write_free_ent;
>> + }
>> +
>> + return count;
>> +
>> +bcm_vk_write_free_msgid:
>> + bcm_vk_msgid_bitmap_clear(vk, entry->h2vk_msg[0].msg_id, 1);
>> +bcm_vk_write_free_ent:
>> + kfree(entry);
>> +bcm_vk_write_err:
>> + return rc;
>> +}
>> +
>> +int bcm_vk_release(struct inode *inode, struct file *p_file)
>> +{
>> + int ret;
>> + struct bcm_vk_ctx *ctx = p_file->private_data;
>> + struct bcm_vk *vk = container_of(ctx->miscdev, struct bcm_vk, miscdev);
>> + struct device *dev = &vk->pdev->dev;
>> + struct task_struct *ppid = ctx->ppid;
>> + pid_t pid = task_pid_nr(ppid);
>> +
>> + dev_dbg(dev, "Draining with context idx %d pid %d\n",
>> + ctx->idx, pid);
>> +
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, ctx);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, ctx);
>> +
>> + ret = bcm_vk_free_ctx(vk, ctx);
> This returns a positive number.
Thanks - fixed logic issue here to return 0 if not last session.

    if (ret == 0)
        ret = bcm_vk_handle_last_sess(vk, pid);
    else
        ret = 0;
>
>> + if (ret == 0)
>> + ret = bcm_vk_handle_last_sess(vk, ppid);
>> +
>> + /* free memory if it is the last reference */
>> + kref_put(&vk->kref, bcm_vk_release_data);
>> +
>> + return ret;
> Which we propogate to here. Probably this should just be "return 0;".
fixed logic above.
>
>> +}
>> +
>> +int bcm_vk_msg_init(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> + int err = 0;
>> +
>> + if (bcm_vk_data_init(vk)) {
>> + dev_err(dev, "Error initializing internal data structures\n");
>> + err = -EINVAL;
>> + goto err_out;
>> + }
>> +
>> + if (bcm_vk_msg_chan_init(&vk->h2vk_msg_chan) ||
>> + bcm_vk_msg_chan_init(&vk->vk2h_msg_chan)) {
>> + dev_err(dev, "Error initializing communication channel\n");
>> + err = -EIO;
>> + goto err_out;
>> + }
>> +
>> + /* create dedicated workqueue */
>> + vk->wq_thread = create_singlethread_workqueue(vk->miscdev.name);
>> + if (!vk->wq_thread) {
>> + dev_err(dev, "Fail to create workqueue thread\n");
>> + err = -ENOMEM;
>> + goto err_out;
>> + }
>> +
>> + /* read msgq info */
>> + if (bcm_vk_sync_msgq(vk, false)) {
>> + dev_err(dev, "Error reading comm msg Q info\n");
>> + err = -EIO;
>> + goto err_out;
>> + }
>> +
>> +err_out:
>> + return err;
> This should probably clean up the work queue on error?
call create_singlethread_workqueue as last step in init so no cleanup
needed on error now.
>
>> +}
>> +
>> +void bcm_vk_msg_remove(struct bcm_vk *vk)
>> +{
>> + bcm_vk_blk_drv_access(vk);
>> +
>> + /* drain all pending items */
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
>> +}
>> +
>> +void bcm_vk_trigger_reset(struct bcm_vk *vk)
>> +{
>> + uint32_t i;
>> + u32 value;
>> +
>> + /* clean up before pressing the door bell */
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->h2vk_msg_chan, NULL);
>> + bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->vk2h_msg_chan, NULL);
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_MSGQ_DEF_RDY);
>> + /* make tag '\0' terminated */
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_BOOT1_VER_TAG);
>> +
>> + for (i = 0; i < VK_BAR1_DAUTH_MAX; i++) {
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_STORE_ADDR(i));
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_DAUTH_VALID_ADDR(i));
>> + }
>> + for (i = 0; i < VK_BAR1_SOTP_REVID_MAX; i++)
>> + vkwrite32(vk, 0, BAR_1, VK_BAR1_SOTP_REVID_ADDR(i));
>> +
>> + memset(&vk->card_info, 0, sizeof(vk->card_info));
>> + memset(&vk->alert_cnts, 0, sizeof(vk->alert_cnts));
>> +
>> + /*
>> + * When boot request fails, the CODE_PUSH_OFFSET stays persistent.
>> + * Allowing us to debug the failure. When we call reset,
>> + * we should clear CODE_PUSH_OFFSET so ROM does not execute
>> + * boot again (and fails again) and instead waits for a new
>> + * codepush.
>> + */
>> + value = vkread32(vk, BAR_0, BAR_CODEPUSH_SBL);
>> + value &= ~CODEPUSH_MASK;
>> + vkwrite32(vk, value, BAR_0, BAR_CODEPUSH_SBL);
>> +
>> + /* reset fw_status with proper reason, and press db */
>> + vkwrite32(vk, VK_FWSTS_RESET_MBOX_DB, BAR_0, VK_BAR_FWSTS);
>> + bcm_h2vk_doorbell(vk, VK_BAR0_RESET_DB_NUM, VK_BAR0_RESET_DB_SOFT);
>> +
>> + /* clear the uptime register after reset pressed and alert record */
>> + vkwrite32(vk, 0, BAR_0, BAR_OS_UPTIME);
>> + memset(&vk->host_alert, 0, sizeof(vk->host_alert));
>> + memset(&vk->peer_alert, 0, sizeof(vk->peer_alert));
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + /* clear qstats */
>> + for (i = 0; i < VK_MSGQ_MAX_NR; i++) {
>> + memset(&vk->h2vk_msg_chan.qstats[i].qcnts, 0,
>> + sizeof(vk->h2vk_msg_chan.qstats[i].qcnts));
>> + memset(&vk->vk2h_msg_chan.qstats[i].qcnts, 0,
>> + sizeof(vk->vk2h_msg_chan.qstats[i].qcnts));
>> + }
>> +#endif
>> + /* clear 4096 bits of bitmap */
>> + bitmap_clear(vk->bmap, 0, VK_MSG_ID_BITMAP_SIZE);
>> +}
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.h b/drivers/misc/bcm-vk/bcm_vk_msg.h
>> new file mode 100644
>> index 000000000000..076307b2f1c4
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_msg.h
>> @@ -0,0 +1,210 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_MSG_H
>> +#define BCM_VK_MSG_H
>> +
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +#include "bcm_vk_sg.h"
>> +
>> +/* Single message queue control structure */
>> +struct bcm_vk_msgq {
>> + uint16_t type; /* queue type */
>> + uint16_t num; /* queue number */
>> + uint32_t start; /* offset in BAR1 where the queue memory starts */
>> + volatile uint32_t rd_idx; /* read idx */
>> + volatile uint32_t wr_idx; /* write idx */
>> + uint32_t size; /*
>> + * size, which is in number of 16byte blocks,
>> + * to align with the message data structure.
>> + */
>> + uint32_t nxt; /*
>> + * nxt offset to the next msg queue struct.
>> + * This is to provide flexibity for alignment purposes.
>> + */
>> +};
>> +
>> +/*
>> + * Structure to record static info from the msgq sync. We keep local copy
>> + * for some of these variables for both performance + checking purpose.
>> + */
>> +struct bcm_vk_sync_qinfo {
>> + void __iomem *q_start;
>> + uint32_t q_size;
>> + uint32_t q_mask;
>> + uint32_t q_low;
>> +};
>> +
>> +#define VK_MSGQ_MAX_NR 4 /* Maximum number of message queues */
>> +
>> +/*
>> + * some useful message queue macros
>> + */
>> +
>> +/* vk_msg_blk is 16 bytes fixed */
>> +#define VK_MSGQ_BLK_SIZE (sizeof(struct vk_msg_blk))
>> +/* shift for fast division of basic msg blk size */
>> +#define VK_MSGQ_BLK_SZ_SHIFT 4
>> +
>> +#define VK_MSGQ_EMPTY(_msgq) ((_msgq)->rd_idx == (_msgq)->wr_idx)
>> +
>> +#define VK_MSGQ_SIZE_MASK(_qinfo) ((_qinfo)->q_mask)
>> +
>> +#define VK_MSGQ_INC(_qinfo, _idx, _inc) \
>> + (((_idx) + (_inc)) & VK_MSGQ_SIZE_MASK(_qinfo))
>> +
>> +#define VK_MSGQ_BLK_ADDR(_qinfo, _idx) \
>> + (volatile struct vk_msg_blk *)((_qinfo)->q_start + \
>> + (VK_MSGQ_BLK_SIZE * (_idx)))
>> +
>> +#define VK_MSGQ_OCCUPIED(_msgq, _qinfo) \
>> + (((_msgq)->wr_idx - (_msgq)->rd_idx) & VK_MSGQ_SIZE_MASK(_qinfo))
>> +
>> +#define VK_MSGQ_AVAIL_SPACE(_msgq, _qinfo) \
>> + ((_qinfo)->q_size - VK_MSGQ_OCCUPIED(_msgq, _qinfo) - 1)
>> +
>> +/* context per session opening of sysfs */
>> +struct bcm_vk_ctx {
>> + struct list_head node; /* use for linkage in Hash Table */
>> + uint idx;
>> + bool in_use;
>> + struct task_struct *ppid;
>> + uint32_t hash_idx;
>> + struct miscdevice *miscdev;
>> + int pend_cnt; /* number of items pending to be read from host */
>> +};
>> +
>> +/* pid hash table entry */
>> +struct bcm_vk_ht_entry {
>> + struct list_head head;
>> +};
>> +
>> +#define VK_DMA_MAX_ADDRS 4 /* Max 4 DMA Addresses */
>> +/* structure for house keeping a single work entry */
>> +struct bcm_vk_wkent {
>> +
>> + struct list_head node; /* for linking purpose */
>> + struct bcm_vk_ctx *ctx;
>> +
>> + /* Store up to 4 dma pointers */
>> + struct bcm_vk_dma dma[VK_DMA_MAX_ADDRS];
>> +
>> + uint32_t vk2h_blks; /* response */
>> + struct vk_msg_blk *vk2h_msg;
>> +
>> + /*
>> + * put the h2vk_msg at the end so that we could simply append h2vk msg
>> + * to the end of the allocated block
>> + */
>> + uint32_t usr_msg_id;
>> + uint32_t h2vk_blks;
>> + uint32_t seq_num;
>> + struct vk_msg_blk h2vk_msg[0];
>> +};
>> +
>> +/* queue stats counters */
>> +struct bcm_vk_qs_cnts {
>> + uint32_t cnt; /* general counter, used to limit output */
>> + uint32_t acc_sum;
>> + uint32_t max_occ; /* max during a sampling period */
>> + uint32_t max_abs; /* the abs max since reset */
>> +};
>> +
>> +/* stats structure */
>> +struct bcm_vk_qstats {
>> + uint32_t q_num;
>> + struct bcm_vk_qs_cnts qcnts;
>> +};
>> +
>> +/* control channel structure for either h2vk or vk2h communication */
>> +struct bcm_vk_msg_chan {
>> + uint32_t q_nr;
>> + struct mutex msgq_mutex;
>> + /* pointing to BAR locations */
>> + struct bcm_vk_msgq *msgq[VK_MSGQ_MAX_NR];
>> +
>> + spinlock_t pendq_lock;
>> + /* for temporary storing pending items, one for each queue */
>> + struct list_head pendq[VK_MSGQ_MAX_NR];
>> + /* static queue info from the sync */
>> + struct bcm_vk_sync_qinfo sync_qinfo[VK_MSGQ_MAX_NR];
>> +#if defined(CONFIG_BCM_VK_QSTATS)
>> + /* qstats */
>> + struct bcm_vk_qstats qstats[VK_MSGQ_MAX_NR];
>> +#endif
>> +};
>> +
>> +/* total number of supported ctx, 32 ctx each for 5 components */
>> +#define VK_CMPT_CTX_MAX (32 * 5)
>> +
>> +/* hash table defines to store the opened FDs */
>> +#define VK_PID_HT_SHIFT_BIT 7 /* 128 */
>> +#define VK_PID_HT_SZ (1 << VK_PID_HT_SHIFT_BIT)
>> +
>> +/* The following are offsets of DDR info provided by the vk card */
>> +#define VK_BAR0_SEG_SIZE (4 * SZ_1K) /* segment size for BAR0 */
>> +
>> +/* shutdown types supported */
>> +#define VK_SHUTDOWN_PID 1
>> +#define VK_SHUTDOWN_GRACEFUL 2
>> +
>> +/*
>> + * first door bell reg, ie for queue = 0. Only need the first one, as
>> + * we will use the queue number to derive the others
>> + */
>> +#define VK_BAR0_REGSEG_DB_BASE 0x484
>> +#define VK_BAR0_REGSEG_DB_REG_GAP 8 /*
>> + * DB register gap,
>> + * DB1 at 0x48c and DB2 at 0x494
>> + */
>> +
>> +/* reset register and specific values */
>> +#define VK_BAR0_RESET_DB_NUM 3
>> +#define VK_BAR0_RESET_DB_SOFT 0xFFFFFFFF
>> +#define VK_BAR0_RESET_DB_HARD 0xFFFFFFFD
>> +
>> +/* BAR1 message q definition */
>> +
>> +/* indicate if msgq ctrl in BAR1 is populated */
>> +#define VK_BAR1_MSGQ_DEF_RDY 0x60c0
>> +/* ready marker value for the above location, normal boot2 */
>> +#define VK_BAR1_MSGQ_RDY_MARKER 0xbeefcafe
>> +/* ready marker value for the above location, normal boot2 */
>> +#define VK_BAR1_DIAG_RDY_MARKER 0xdeadcafe
>> +/* number of msgqs in BAR1 */
>> +#define VK_BAR1_MSGQ_NR 0x60c4
>> +/* BAR1 queue control structure offset */
>> +#define VK_BAR1_MSGQ_CTRL_OFF 0x60c8
>> +/* BAR1 ucode and boot1 version tag */
>> +#define VK_BAR1_UCODE_VER_TAG 0x6170
>> +#define VK_BAR1_BOOT1_VER_TAG 0x61b0
>> +#define VK_BAR1_VER_TAG_SIZE 64
>> +/* Memory to hold the DMA buffer memory address allocated for boot2 download */
>> +#define VK_BAR1_DMA_BUF_OFF_HI 0x61e0
>> +#define VK_BAR1_DMA_BUF_OFF_LO (VK_BAR1_DMA_BUF_OFF_HI + 4)
>> +#define VK_BAR1_DMA_BUF_SZ (VK_BAR1_DMA_BUF_OFF_HI + 8)
>> +/* Scratch memory allocated on host for VK */
>> +#define VK_BAR1_SCRATCH_OFF_LO 0x61f0
>> +#define VK_BAR1_SCRATCH_OFF_HI (VK_BAR1_SCRATCH_OFF_LO + 4)
>> +#define VK_BAR1_SCRATCH_SZ_ADDR (VK_BAR1_SCRATCH_OFF_LO + 8)
>> +#define VK_BAR1_SCRATCH_DEF_NR_PAGES 32
>> + /* BAR1 SOTP AUTH and REVID info */
>> +#define VK_BAR1_DAUTH_BASE_ADDR 0x6200
>> +#define VK_BAR1_DAUTH_STORE_SIZE 0x48
>> +#define VK_BAR1_DAUTH_VALID_SIZE 0x8
>> +#define VK_BAR1_DAUTH_MAX 4
>> +#define VK_BAR1_DAUTH_STORE_ADDR(x) \
>> + (VK_BAR1_DAUTH_BASE_ADDR + \
>> + (x) * (VK_BAR1_DAUTH_STORE_SIZE + VK_BAR1_DAUTH_VALID_SIZE))
>> +#define VK_BAR1_DAUTH_VALID_ADDR(x) \
>> + (VK_BAR1_DAUTH_STORE_ADDR(x) + VK_BAR1_DAUTH_STORE_SIZE)
>> +
>> +#define VK_BAR1_SOTP_REVID_BASE_ADDR 0x6340
>> +#define VK_BAR1_SOTP_REVID_SIZE 0x10
>> +#define VK_BAR1_SOTP_REVID_MAX 2
>> +#define VK_BAR1_SOTP_REVID_ADDR(x) \
>> + (VK_BAR1_SOTP_REVID_BASE_ADDR + (x) * VK_BAR1_SOTP_REVID_SIZE)
>> +
>> +#endif
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.c b/drivers/misc/bcm-vk/bcm_vk_sg.c
>> new file mode 100644
>> index 000000000000..e5de07958f0b
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.c
>> @@ -0,0 +1,273 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +#include <linux/dma-mapping.h>
>> +#include <linux/mm.h>
>> +#include <linux/pagemap.h>
>> +#include <linux/vmalloc.h>
>> +
>> +#include <asm/page.h>
>> +#include <asm/pgtable.h>
>> +#include <asm/unaligned.h>
>> +
>> +#include <uapi/linux/misc/bcm_vk.h>
>> +
>> +#include "bcm_vk.h"
>> +#include "bcm_vk_msg.h"
>> +#include "bcm_vk_sg.h"
>> +
>> +/*
>> + * Valkyrie has a hardware limitation of 16M transfer size.
>> + * So limit the SGL chunks to 16M.
>> + */
>> +#define BCM_VK_MAX_SGL_CHUNK SZ_16M
>> +
>> +static int bcm_vk_dma_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata);
>> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma);
>> +
>> +/* Uncomment to dump SGLIST */
>> +//#define BCM_VK_DUMP_SGLIST
>> +
>> +static int bcm_vk_dma_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int direction,
>> + struct _vk_data *vkdata)
>> +{
>> + dma_addr_t addr, sg_addr;
>> + int err;
>> + int i;
>> + int offset;
>> + uint32_t size;
>> + uint32_t remaining_size;
>> + uint32_t transfer_size;
>> + uint64_t data;
>> + unsigned long first, last;
>> + struct _vk_data *sgdata;
>> +
>> + /* Get 64-bit user address */
>> + data = get_unaligned(&(vkdata->address));
> Extra parens.
removed
>
>> +
>> + /* offset into first page */
>> + offset = offset_in_page(data);
>> +
>> + /* Calculate number of pages */
>> + first = (data & PAGE_MASK) >> PAGE_SHIFT;
>> + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
>> + dma->nr_pages = last - first + 1;
>> +
>> + /* Allocate DMA pages */
>> + dma->pages = kmalloc_array(dma->nr_pages,
>> + sizeof(struct page *),
>> + GFP_KERNEL);
>> + if (dma->pages == NULL)
>> + return -ENOMEM;
>> +
>> + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
>> + data, vkdata->size, dma->nr_pages);
>> +
>> + dma->direction = direction;
>> +
>> + /* Get user pages into memory */
>> + err = get_user_pages_fast(data & PAGE_MASK,
>> + dma->nr_pages,
>> + direction == DMA_FROM_DEVICE,
>> + dma->pages);
>> + if (err != dma->nr_pages) {
>> + dma->nr_pages = (err >= 0) ? err : 0;
>> + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
>> + err, dma->nr_pages);
>> + return err < 0 ? err : -EINVAL;
>> + }
>> +
>> + /* Max size of sg list is 1 per mapped page + fields at start */
>> + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
>> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
>> +
>> + /* Allocate sglist */
>> + dma->sglist = dma_alloc_coherent(dev,
>> + dma->sglen,
>> + &dma->handle,
>> + GFP_KERNEL);
>
> dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
> GFP_KERNEL);
done
>
>
>
>> + if (!dma->sglist)
>> + return -ENOMEM;
> No cleanup?
what needs to be cleaned up?
>
>> +
>> + dma->sglist[SGLIST_NUM_SG] = 0;
>> + dma->sglist[SGLIST_TOTALSIZE] = vkdata->size;
>> + remaining_size = vkdata->size;
>> + sgdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
>> +
>> + /* Map all pages into DMA */
>> + i = 0;
> Set but not used.
removed
>
>> + size = min_t(size_t, PAGE_SIZE - offset, remaining_size);
>> + remaining_size -= size;
>> + sg_addr = dma_map_page(dev,
>> + dma->pages[0],
>> + offset,
>> + size,
>> + dma->direction);
>> + transfer_size = size;
>> + if (unlikely(dma_mapping_error(dev, sg_addr))) {
>> + __free_page(dma->pages[0]);
>> + return -EIO;
>> + }
>> +
>> + for (i = 1; i < dma->nr_pages; i++) {
>> + size = min_t(size_t, PAGE_SIZE, remaining_size);
>> + remaining_size -= size;
>> + addr = dma_map_page(dev,
>> + dma->pages[i],
>> + 0,
>> + size,
>> + dma->direction);
>> + if (unlikely(dma_mapping_error(dev, addr))) {
>> + __free_page(dma->pages[i]);
>> + return -EIO;
>> + }
>> +
>> + /*
>> + * Compress SG list entry when pages are contiguous
>> + * and transfer size less or equal to BCM_VK_MAX_SGL_CHUNK
>> + */
>> + if ((addr == (sg_addr + transfer_size)) &&
>> + ((transfer_size + size) <= BCM_VK_MAX_SGL_CHUNK)) {
>> + /* pages are contiguous, add to same sg entry */
>> + transfer_size += size;
>> + } else {
>> + /* pages are not contiguous, write sg entry */
>> + sgdata->size = transfer_size;
>> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
>> + dma->sglist[SGLIST_NUM_SG]++;
>> +
>> + /* start new sg entry */
>> + sgdata++;
>> + sg_addr = addr;
>> + transfer_size = size;
>> + }
>> + }
>> + /* Write last sg list entry */
>> + sgdata->size = transfer_size;
>> + put_unaligned(sg_addr, (uint64_t *)&(sgdata->address));
>> + dma->sglist[SGLIST_NUM_SG]++;
>> +
>> + /* Update pointers and size field to point to sglist */
>> + put_unaligned((uint64_t)dma->handle, &(vkdata->address));
>> + vkdata->size = (dma->sglist[SGLIST_NUM_SG] * sizeof(*sgdata)) +
>> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
>> +
>> +#ifdef BCM_VK_DUMP_SGLIST
>> + dev_dbg(dev,
>> + "sgl 0x%llx handle 0x%llx, sglen: 0x%x sgsize: 0x%x\n",
>> + (uint64_t)dma->sglist,
>> + dma->handle,
>> + dma->sglen,
>> + vkdata->size);
>> + for (i = 0; i < vkdata->size / sizeof(uint32_t); i++)
>> + dev_dbg(dev, "i:0x%x 0x%x\n", i, dma->sglist[i]);
>> +#endif
>> +
>> + return 0;
>> +}
>> +
>> +int bcm_vk_sg_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata,
>> + int num)
>> +{
>> + int i;
>> + int rc = -EINVAL;
>> +
>> + /* Convert user addresses to DMA SG List */
>> + for (i = 0; i < num; i++) {
>> + if (vkdata[i].size && vkdata[i].address) {
>> + /*
>> + * If both size and address are non-zero
>> + * then DMA alloc.
>> + */
>> + rc = bcm_vk_dma_alloc(dev,
>> + &dma[i],
>> + dir,
>> + &vkdata[i]);
>> + } else if (vkdata[i].size ||
>> + vkdata[i].address) {
>> + /*
>> + * If one of size and address are zero
>> + * there is a problem.
>> + */
>> + dev_err(dev,
>> + "Invalid vkdata %x 0x%x 0x%llx\n",
>> + i, vkdata[i].size, vkdata[i].address);
>> + rc = -EINVAL;
>> + } else {
>> + /*
>> + * If size and address are both zero
>> + * don't convert, but return success.
>> + */
>> + rc = 0;
>> + }
>> +
>> + if (rc)
>> + goto fail_alloc;
>> + }
>> + return rc;
>> +
>> +fail_alloc:
>> + while (i > 0) {
>> + i--;
>> + if (dma[i].sglist)
>> + bcm_vk_dma_free(dev, &dma[i]);
>> + }
>> + return rc;
>> +}
>> +
>> +static int bcm_vk_dma_free(struct device *dev, struct bcm_vk_dma *dma)
>> +{
>> + dma_addr_t addr;
>> + int i;
>> + int num_sg;
>> + uint32_t size;
>> + struct _vk_data *vkdata;
>> +
>> + dev_dbg(dev, "free sglist=%p sglen=0x%x\n",
>> + dma->sglist, dma->sglen);
>> +
> This fits in 80 characters.
>
> dev_dbg(dev, "free sglist=%p sglen=0x%x\n", dma->sglist, dma->sglen);
done
>
>> + /* Unmap all pages in the sglist */
>> + num_sg = dma->sglist[SGLIST_NUM_SG];
>> + vkdata = (struct _vk_data *)&(dma->sglist[SGLIST_VKDATA_START]);
> Extra parens.
removed
>
>> + for (i = 0; i < num_sg; i++) {
>> + size = vkdata[i].size;
>> + addr = get_unaligned(&(vkdata[i].address));
>> +
>> + dma_unmap_page(dev, addr, size, dma->direction);
>> + }
>> +
>> + /* Free allocated sglist */
>> + dma_free_coherent(dev, dma->sglen, dma->sglist, dma->handle);
>> +
>> + /* Release lock on all pages */
>> + for (i = 0; i < dma->nr_pages; i++)
>> + put_page(dma->pages[i]);
>> +
>> + /* Free allocated dma pages */
>> + kfree(dma->pages);
>> + dma->sglist = NULL;
>> +
>> + return 0;
>> +}
>> +
>> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num)
>> +{
>> + int i;
>> +
>> + /* Unmap and free all pages and sglists */
>> + for (i = 0; i < num; i++) {
>> + if (dma[i].sglist)
>> + bcm_vk_dma_free(dev, &dma[i]);
>> + }
>> +
>> + return 0;
>> +}
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_sg.h b/drivers/misc/bcm-vk/bcm_vk_sg.h
>> new file mode 100644
>> index 000000000000..df88154e80b7
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_sg.h
>> @@ -0,0 +1,60 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#ifndef BCM_VK_SG_H
>> +#define BCM_VK_SG_H
>> +
>> +#include <linux/dma-mapping.h>
>> +
>> +struct bcm_vk_dma {
>> + /* for userland buffer */
>> + struct page **pages;
>> + int nr_pages;
>> +
>> + /* common */
>> + dma_addr_t handle;
>> + /*
>> + * sglist is of the following LE format
>> + * [U32] num_sg = number of sg addresses (N)
>> + * [U32] totalsize = totalsize of data being transferred in sglist
>> + * [U32] size[0] = size of data in address0
>> + * [U32] addr_l[0] = lower 32-bits of address0
>> + * [U32] addr_h[0] = higher 32-bits of address0
>> + * ..
>> + * [U32] size[N-1] = size of data in addressN-1
>> + * [U32] addr_l[N-1] = lower 32-bits of addressN-1
>> + * [U32] addr_h[N-1] = higher 32-bits of addressN-1
>> + */
>> + uint32_t *sglist;
>> +#define SGLIST_NUM_SG 0
>> +#define SGLIST_TOTALSIZE 1
>> +#define SGLIST_VKDATA_START 2
>> +
>> + int sglen; /* Length (bytes) of sglist */
>> + int direction;
>> +};
>> +
>> +struct _vk_data {
>> + uint32_t size; /* data size in bytes */
>> + uint64_t address; /* Pointer to data */
>> +} __packed;
>> +
>> +/*
>> + * Scatter-gather DMA buffer API.
>> + *
>> + * These functions provide a simple way to create a page list and a
>> + * scatter-gather list from userspace address and map the memory
>> + * for DMA operation.
>> + */
>> +int bcm_vk_sg_alloc(struct device *dev,
>> + struct bcm_vk_dma *dma,
>> + int dir,
>> + struct _vk_data *vkdata,
>> + int num);
>> +
>> +int bcm_vk_sg_free(struct device *dev, struct bcm_vk_dma *dma, int num);
>> +
>> +#endif
>> +
>> diff --git a/drivers/misc/bcm-vk/bcm_vk_tty.c b/drivers/misc/bcm-vk/bcm_vk_tty.c
>> new file mode 100644
>> index 000000000000..3835218aaec6
>> --- /dev/null
>> +++ b/drivers/misc/bcm-vk/bcm_vk_tty.c
>> @@ -0,0 +1,327 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright 2018-2020 Broadcom.
>> + */
>> +
>> +#include <linux/tty.h>
>> +#include <linux/tty_driver.h>
>> +#include <linux/tty_flip.h>
>> +
>> +#include "bcm_vk.h"
>> +
>> +/* TTYVK base offset is 0x30000 into BAR1 */
>> +#define BAR1_TTYVK_BASE_OFFSET 0x300000
>> +/* Each TTYVK channel (TO or FROM) is 0x10000 */
>> +#define BAR1_TTYVK_CHAN_OFFSET 0x100000
>> +/* Each TTYVK channel has TO and FROM, hence the * 2 */
>> +#define BAR1_TTYVK_BASE(index) (BAR1_TTYVK_BASE_OFFSET + \
>> + ((index) * BAR1_TTYVK_CHAN_OFFSET * 2))
>> +/* TO TTYVK channel base comes before FROM for each index */
>> +#define TO_TTYK_BASE(index) BAR1_TTYVK_BASE(index)
>> +#define FROM_TTYK_BASE(index) (BAR1_TTYVK_BASE(index) + \
>> + BAR1_TTYVK_CHAN_OFFSET)
>> +
>> +struct bcm_vk_tty_chan {
>> + uint32_t reserved;
>> + uint32_t size;
>> + uint32_t wr;
>> + uint32_t rd;
>> + uint32_t *data;
>> +};
>> +
>> +#define VK_BAR_CHAN(v, DIR, e) ((v)->DIR##_offset \
>> + + offsetof(struct bcm_vk_tty_chan, e))
>> +#define VK_BAR_CHAN_SIZE(v, DIR) VK_BAR_CHAN(v, DIR, size)
>> +#define VK_BAR_CHAN_WR(v, DIR) VK_BAR_CHAN(v, DIR, wr)
>> +#define VK_BAR_CHAN_RD(v, DIR) VK_BAR_CHAN(v, DIR, rd)
>> +#define VK_BAR_CHAN_DATA(v, DIR, off) (VK_BAR_CHAN(v, DIR, data) + off)
>> +
>> +/* Poll every 1/10 of second - temp hack till we use MSI interrupt */
>> +#define SERIAL_TIMER_VALUE (HZ / 10)
>> +
>> +#if defined(BCM_VK_LEGACY_API)
>> +
>> +/* No support in legacy case, and do a dummy init and exit */
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + dev_dbg(dev, "init %s\n", name);
>> + return 0;
>> +}
>> +
>> +void bcm_vk_tty_exit(struct bcm_vk *vk)
>> +{
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + dev_dbg(dev, "exit\n");
>> +}
>> +
>> +#else
>> +
>> +static void bcm_vk_tty_poll(struct timer_list *t)
>> +{
>> + struct bcm_vk *vk = from_timer(vk, t, serial_timer);
>> + struct bcm_vk_tty *vktty;
>> + int card_status;
>> + int ready_mask;
>> + int count = 0;
>> + unsigned char c;
>> + int i;
>> + int wr;
>> +
>> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
>> +
>> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
>> + /* Check the card status that the tty channel is ready */
>> + ready_mask = BIT(i);
>> + if ((card_status & ready_mask) == 0)
> Get rid of ready_mask and use BIT(i) directly.
done
>> + continue;
>> +
>> + vktty = &vk->tty[i];
>> +
>> + /* Fetch the wr offset in buffer from VK */
>> + wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, from));
>> + if (wr >= vktty->from_size) {
>> + dev_err(&vk->pdev->dev,
>> + "ERROR: poll ttyVK%d wr:0x%x > 0x%x\n",
>> + i, wr, vktty->from_size);
>> + /* Need to signal and close device in this case */
>> + return;
>> + }
>> +
>> + /*
>> + * Simple read of circular buffer and
>> + * insert into tty flip buffer
>> + */
>> + while (vk->tty[i].rd != wr) {
>> + c = vkread8(vk, BAR_1,
>> + VK_BAR_CHAN_DATA(vktty, from, vktty->rd));
>> + vktty->rd++;
>> + if (vktty->rd >= vktty->from_size)
>> + vktty->rd = 0;
>> + tty_insert_flip_char(&vktty->port, c, TTY_NORMAL);
>> + count++;
>> + }
>> + }
>> +
>> + if (count) {
>> + tty_flip_buffer_push(&vktty->port);
>> +
>> + /* Update read offset from shadow register to card */
>> + vkwrite32(vk, vktty->rd, BAR_1, VK_BAR_CHAN_RD(vktty, from));
>> + }
>> +
>> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
>> +}
>> +
>> +static int bcm_vk_tty_open(struct tty_struct *tty, struct file *file)
>> +{
>> + int card_status;
>> + int ready_mask;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> + int index;
>> +
>> + /* initialize the pointer in case something fails */
>> + tty->driver_data = NULL;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
>> + index = tty->index;
>> +
>> + if (index >= BCM_VK_NUM_TTY)
>> + return -EINVAL;
>> +
>> + vktty = &vk->tty[index];
>> +
>> + vktty->to_offset = TO_TTYK_BASE(index);
>> + vktty->from_offset = FROM_TTYK_BASE(index);
>> +
>> + /* Do not allow tty device to be opened if tty on card not ready */
>> + card_status = vkread32(vk, BAR_0, BAR_CARD_STATUS);
>> + if (card_status == -1)
>> + return -1;
>> +
>> + ready_mask = BIT(index);
>> + if ((card_status & ready_mask) == 0)
>> + return -1;
>> +
>> + /*
>> + * Get shadow registers of the buffer sizes and the "to" write offset
>> + * and "from" read offset
>> + */
>> + vktty->to_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, to));
>> + vktty->wr = vkread32(vk, BAR_1, VK_BAR_CHAN_WR(vktty, to));
>> + vktty->from_size = vkread32(vk, BAR_1, VK_BAR_CHAN_SIZE(vktty, from));
>> + vktty->rd = vkread32(vk, BAR_1, VK_BAR_CHAN_RD(vktty, from));
>> +
>> + if (tty->count == 1) {
>> + timer_setup(&vk->serial_timer, bcm_vk_tty_poll, 0);
>> + mod_timer(&vk->serial_timer, jiffies + SERIAL_TIMER_VALUE);
>> + }
>> + return 0;
>> +}
>> +
>> +static void bcm_vk_tty_close(struct tty_struct *tty, struct file *file)
>> +{
>> + struct bcm_vk *vk;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
>
> struct bcm_vk *vk = dev_get_drvdata(tty->dev);
done
>
>> +
>> + if (tty->count == 1)
>> + del_timer_sync(&vk->serial_timer);
>> +}
>> +
>> +static int bcm_vk_tty_write(struct tty_struct *tty,
>> + const unsigned char *buffer,
>> + int count)
>> +{
>> + int index;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> + int i;
>> + int retval;
>> +
>> + index = tty->index;
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
> The cast is unnecessary.
removed
>
>> + vktty = &vk->tty[index];
>> +
>> + /* Simple write each byte to circular buffer */
>> + for (i = 0; i < count; i++) {
>> + vkwrite8(vk,
>> + buffer[i],
>> + BAR_1,
>> + VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
> vkwrite8(vk, buffer[i], BAR_1,
> VK_BAR_CHAN_DATA(vktty, to, vktty->wr));
done
>
>
>> + vktty->wr++;
>> + if (vktty->wr >= vktty->to_size)
>> + vktty->wr = 0;
>> + }
>> + /* Update write offset from shadow register to card */
>> + /* TODO: Need to add write to doorbell here */
>> + vkwrite32(vk, vktty->wr, BAR_1, VK_BAR_CHAN_WR(vktty, to));
>> +
>> + retval = count;
>> +
>> + return retval;
> Delete the "retval" variable.
done
>
>> +}
>> +
>> +static int bcm_vk_tty_write_room(struct tty_struct *tty)
>> +{
>> + int room;
>> + struct bcm_vk *vk;
>> + struct bcm_vk_tty *vktty;
>> +
>> + vk = (struct bcm_vk *)dev_get_drvdata(tty->dev);
> Delete cast.
done
>
>> + vktty = &vk->tty[tty->index];
>> +
>> + /*
>> + * Calculate how much room is left in the device
>> + * Just return the size -1 of buffer. We could care about
>> + * overflow but don't at this point.
>> + */
> This comment raises more questions than it answers. Why would we care
> about overflow and why don't we at this point? Perhaps just delete the
> comment.
removed
>> + room = vktty->to_size - 1;
>> +
>> + return room;
> Delete the room variable and the vktty variable.
>
> return vk->tty[tty->index].to_size - 1;
done
>
>
>> +}
>> +
>> +static const struct tty_operations serial_ops = {
>> + .open = bcm_vk_tty_open,
>> + .close = bcm_vk_tty_close,
>> + .write = bcm_vk_tty_write,
>> + .write_room = bcm_vk_tty_write_room,
>> +};
>> +
>> +int bcm_vk_tty_init(struct bcm_vk *vk, char *name)
>> +{
>> + int i;
>> + int err;
>> + unsigned long flags;
>> + struct tty_driver *tty_drv;
>> + struct device *dev = &vk->pdev->dev;
>> +
>> + /* allocate the tty driver */
>> + flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
>> + tty_drv = tty_alloc_driver(BCM_VK_NUM_TTY, flags);
> Delete the flags variable.
done
>
>> +
> Delete this blank line.
done
>
>> + if (IS_ERR(tty_drv)) {
>> + err = PTR_ERR(tty_drv);
>> + goto err_exit;
>
> return PTR_ERR(tty_drv);
done
>
>> + }
>> + /* Save struct tty_driver for uninstalling the device */
>> + vk->tty_drv = tty_drv;
>> +
>> + /* initialize the tty driver */
>> + tty_drv->driver_name = KBUILD_MODNAME;
>> + tty_drv->name = kstrdup(name, GFP_KERNEL);
>> + if (!tty_drv->name) {
>> + err = -ENOMEM;
>> + goto err_put_tty_driver;
>> + }
>> + tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
>> + tty_drv->subtype = SERIAL_TYPE_NORMAL;
>> + tty_drv->init_termios = tty_std_termios;
>> + tty_set_operations(tty_drv, &serial_ops);
>> +
>> + /* register the tty driver */
>> + err = tty_register_driver(tty_drv);
>> + if (err) {
>> + dev_err(dev, "tty_register_driver failed\n");
>> + goto err_kfree_tty_name;
>> + }
>> +
>> + for (i = 0; i < BCM_VK_NUM_TTY; i++) {
>> + struct device *tty_dev;
>> +
>> + tty_port_init(&vk->tty[i].port);
>> + tty_dev = tty_port_register_device(&vk->tty[i].port,
>> + tty_drv,
>> + i,
>> + dev);
> tty_dev = tty_port_register_device(&vk->tty[i].port, tty_drv,
> i, dev);
done
>
>
>> + dev_set_drvdata(tty_dev, vk);
>> +
>> + if (IS_ERR(tty_dev)) {
> Calling dev_set_drvdata() before checking for IS_ERR().
fixed
>
>> + int j;
>> +
>> + for (j = 0; j < i; j++)
>> + tty_port_unregister_device(&vk->tty[j].port,
>> + tty_drv,
>> + j);
>> + goto err_tty_unregister_driver;
> No error code.
done
>
>> + }
> if (IS_ERR(tty_dev)) {
> err = PTR_ERR(tty_dev);
> goto unwind;
> }
done
>> + }
>> +
>> + return 0;
>> +
> unwind:
> while (--i >= 0)
> tty_port_unregister_device(&vk->tty[i].port, tty_drv, i);
>
thanks for cleanup
>> +err_tty_unregister_driver:
>> + tty_unregister_driver(tty_drv);
>> +
>> +err_kfree_tty_name:
>> + kfree(tty_drv->name);
>> + tty_drv->name = NULL;
>> +
>> +err_put_tty_driver:
>> + put_tty_driver(tty_drv);
>> +
>> +err_exit:
>> + return err;
>> +}
>> +
>> +void bcm_vk_tty_exit(struct bcm_vk *vk)
>> +{
>> + int i;
>> +
>> + del_timer_sync(&vk->serial_timer);
>> + for (i = 0; i < BCM_VK_NUM_TTY; ++i) {
>> + tty_port_unregister_device(&vk->tty[i].port,
>> + vk->tty_drv,
>> + i);
>> + tty_port_destroy(&vk->tty[i].port);
>> + }
>> + tty_unregister_driver(vk->tty_drv);
>> + put_tty_driver(vk->tty_drv);
>> +
>> + kfree(vk->tty_drv->name);
> ^^^^^^^^^^^
>
> Use after free because of the put_tty_driver(). I think test again
> with CONFIG_PAGE_POISONING?
fixed
>
>> + vk->tty_drv->name = NULL;
>> +}
> regards,
> dan carpenter
>

2020-04-18 11:49:19

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Fri, Apr 17, 2020 at 02:49:11PM -0700, Scott Branden wrote:
> > > +static int bcm_vk_dma_alloc(struct device *dev,
> > > + struct bcm_vk_dma *dma,
> > > + int direction,
> > > + struct _vk_data *vkdata)
> > > +{
> > > + dma_addr_t addr, sg_addr;
> > > + int err;
> > > + int i;
> > > + int offset;
> > > + uint32_t size;
> > > + uint32_t remaining_size;
> > > + uint32_t transfer_size;
> > > + uint64_t data;
> > > + unsigned long first, last;
> > > + struct _vk_data *sgdata;
> > > +
> > > + /* Get 64-bit user address */
> > > + data = get_unaligned(&(vkdata->address));
> > Extra parens.
> removed
> >
> > > +
> > > + /* offset into first page */
> > > + offset = offset_in_page(data);
> > > +
> > > + /* Calculate number of pages */
> > > + first = (data & PAGE_MASK) >> PAGE_SHIFT;
> > > + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
> > > + dma->nr_pages = last - first + 1;
> > > +
> > > + /* Allocate DMA pages */
> > > + dma->pages = kmalloc_array(dma->nr_pages,
> > > + sizeof(struct page *),
> > > + GFP_KERNEL);
> > > + if (dma->pages == NULL)
> > > + return -ENOMEM;
> > > +
> > > + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
> > > + data, vkdata->size, dma->nr_pages);
> > > +
> > > + dma->direction = direction;
> > > +
> > > + /* Get user pages into memory */
> > > + err = get_user_pages_fast(data & PAGE_MASK,
> > > + dma->nr_pages,
> > > + direction == DMA_FROM_DEVICE,
> > > + dma->pages);
> > > + if (err != dma->nr_pages) {
> > > + dma->nr_pages = (err >= 0) ? err : 0;
> > > + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
> > > + err, dma->nr_pages);
> > > + return err < 0 ? err : -EINVAL;
> > > + }
> > > +
> > > + /* Max size of sg list is 1 per mapped page + fields at start */
> > > + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
> > > + (sizeof(uint32_t) * SGLIST_VKDATA_START);
> > > +
> > > + /* Allocate sglist */
> > > + dma->sglist = dma_alloc_coherent(dev,
> > > + dma->sglen,
> > > + &dma->handle,
> > > + GFP_KERNEL);
> >
> > dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
> > GFP_KERNEL);
> done
> >
> >
> >
> > > + if (!dma->sglist)
> > > + return -ENOMEM;
> > No cleanup?
> what needs to be cleaned up?

dma->pages should be freed probably? And a put_user_pages_fast()?

regards,
dan carpenter

2020-04-18 11:50:40

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

On Sat, Apr 18, 2020 at 02:45:16PM +0300, Dan Carpenter wrote:
> On Fri, Apr 17, 2020 at 02:49:11PM -0700, Scott Branden wrote:
> > > > +static int bcm_vk_dma_alloc(struct device *dev,
> > > > + struct bcm_vk_dma *dma,
> > > > + int direction,
> > > > + struct _vk_data *vkdata)
> > > > +{
> > > > + dma_addr_t addr, sg_addr;
> > > > + int err;
> > > > + int i;
> > > > + int offset;
> > > > + uint32_t size;
> > > > + uint32_t remaining_size;
> > > > + uint32_t transfer_size;
> > > > + uint64_t data;
> > > > + unsigned long first, last;
> > > > + struct _vk_data *sgdata;
> > > > +
> > > > + /* Get 64-bit user address */
> > > > + data = get_unaligned(&(vkdata->address));
> > > Extra parens.
> > removed
> > >
> > > > +
> > > > + /* offset into first page */
> > > > + offset = offset_in_page(data);
> > > > +
> > > > + /* Calculate number of pages */
> > > > + first = (data & PAGE_MASK) >> PAGE_SHIFT;
> > > > + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
> > > > + dma->nr_pages = last - first + 1;
> > > > +
> > > > + /* Allocate DMA pages */
> > > > + dma->pages = kmalloc_array(dma->nr_pages,
> > > > + sizeof(struct page *),
> > > > + GFP_KERNEL);
> > > > + if (dma->pages == NULL)
> > > > + return -ENOMEM;
> > > > +
> > > > + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
> > > > + data, vkdata->size, dma->nr_pages);
> > > > +
> > > > + dma->direction = direction;
> > > > +
> > > > + /* Get user pages into memory */
> > > > + err = get_user_pages_fast(data & PAGE_MASK,
> > > > + dma->nr_pages,
> > > > + direction == DMA_FROM_DEVICE,
> > > > + dma->pages);
> > > > + if (err != dma->nr_pages) {
> > > > + dma->nr_pages = (err >= 0) ? err : 0;
> > > > + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
> > > > + err, dma->nr_pages);
> > > > + return err < 0 ? err : -EINVAL;
> > > > + }
> > > > +
> > > > + /* Max size of sg list is 1 per mapped page + fields at start */
> > > > + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
> > > > + (sizeof(uint32_t) * SGLIST_VKDATA_START);
> > > > +
> > > > + /* Allocate sglist */
> > > > + dma->sglist = dma_alloc_coherent(dev,
> > > > + dma->sglen,
> > > > + &dma->handle,
> > > > + GFP_KERNEL);
> > >
> > > dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
> > > GFP_KERNEL);
> > done
> > >
> > >
> > >
> > > > + if (!dma->sglist)
> > > > + return -ENOMEM;
> > > No cleanup?
> > what needs to be cleaned up?
>
> dma->pages should be freed probably? And a put_user_pages_fast()?

Sorry put_user_pages_fast() isn't a function. My bad.

regards,
dan carpenter

2020-04-18 17:28:31

by Scott Branden

[permalink] [raw]
Subject: Re: [PATCH v2 6/7] misc: bcm-vk: add Broadcom VK driver

Thanks Dan, I'll send out new version as soon as my other patch (you had
requested for test_fx mutex cleanups)
https://lore.kernel.org/linux-arm-msm/[email protected]/

hits the linux-next tree so this patch series will apply cleanly to
linux-next.




On 2020-04-18 4:47 a.m., Dan Carpenter wrote:
> On Sat, Apr 18, 2020 at 02:45:16PM +0300, Dan Carpenter wrote:
>> On Fri, Apr 17, 2020 at 02:49:11PM -0700, Scott Branden wrote:
>>>>> +static int bcm_vk_dma_alloc(struct device *dev,
>>>>> + struct bcm_vk_dma *dma,
>>>>> + int direction,
>>>>> + struct _vk_data *vkdata)
>>>>> +{
>>>>> + dma_addr_t addr, sg_addr;
>>>>> + int err;
>>>>> + int i;
>>>>> + int offset;
>>>>> + uint32_t size;
>>>>> + uint32_t remaining_size;
>>>>> + uint32_t transfer_size;
>>>>> + uint64_t data;
>>>>> + unsigned long first, last;
>>>>> + struct _vk_data *sgdata;
>>>>> +
>>>>> + /* Get 64-bit user address */
>>>>> + data = get_unaligned(&(vkdata->address));
>>>> Extra parens.
>>> removed
>>>>> +
>>>>> + /* offset into first page */
>>>>> + offset = offset_in_page(data);
>>>>> +
>>>>> + /* Calculate number of pages */
>>>>> + first = (data & PAGE_MASK) >> PAGE_SHIFT;
>>>>> + last = ((data + vkdata->size - 1) & PAGE_MASK) >> PAGE_SHIFT;
>>>>> + dma->nr_pages = last - first + 1;
>>>>> +
>>>>> + /* Allocate DMA pages */
>>>>> + dma->pages = kmalloc_array(dma->nr_pages,
>>>>> + sizeof(struct page *),
>>>>> + GFP_KERNEL);
>>>>> + if (dma->pages == NULL)
>>>>> + return -ENOMEM;
>>>>> +
>>>>> + dev_dbg(dev, "Alloc DMA Pages [0x%llx+0x%x => %d pages]\n",
>>>>> + data, vkdata->size, dma->nr_pages);
>>>>> +
>>>>> + dma->direction = direction;
>>>>> +
>>>>> + /* Get user pages into memory */
>>>>> + err = get_user_pages_fast(data & PAGE_MASK,
>>>>> + dma->nr_pages,
>>>>> + direction == DMA_FROM_DEVICE,
>>>>> + dma->pages);
>>>>> + if (err != dma->nr_pages) {
>>>>> + dma->nr_pages = (err >= 0) ? err : 0;
>>>>> + dev_err(dev, "get_user_pages_fast, err=%d [%d]\n",
>>>>> + err, dma->nr_pages);
>>>>> + return err < 0 ? err : -EINVAL;
>>>>> + }
>>>>> +
>>>>> + /* Max size of sg list is 1 per mapped page + fields at start */
>>>>> + dma->sglen = (dma->nr_pages * sizeof(*sgdata)) +
>>>>> + (sizeof(uint32_t) * SGLIST_VKDATA_START);
>>>>> +
>>>>> + /* Allocate sglist */
>>>>> + dma->sglist = dma_alloc_coherent(dev,
>>>>> + dma->sglen,
>>>>> + &dma->handle,
>>>>> + GFP_KERNEL);
>>>> dma->sglist = dma_alloc_coherent(dev, dma->sglen, &dma->handle,
>>>> GFP_KERNEL);
>>> done
>>>>
>>>>> + if (!dma->sglist)
>>>>> + return -ENOMEM;
>>>> No cleanup?
>>> what needs to be cleaned up?
>> dma->pages should be freed probably? And a put_user_pages_fast()?
> Sorry put_user_pages_fast() isn't a function. My bad.
>
> regards,
> dan carpenter