[PATCH v3 04/12] firmware: tegra: Add IVC library

Jon Hunter jonathanh at nvidia.com
Mon Aug 22 03:46:49 PDT 2016


On 19/08/16 18:32, Thierry Reding wrote:
> From: Thierry Reding <treding at nvidia.com>
> 
> The Inter-VM communication (IVC) is a communication protocol which is
> designed for interprocessor communication (IPC) or the communication
> between the hypervisor and the virtual machine with a guest OS.
> 
> Message channels are used to communicate between processors. They are
> backed by DRAM or SRAM, so care must be taken to maintain coherence of
> data.
> 
> The IVC library maintains memory-based descriptors for the transmission
> and reception channels as well as the data coherence of the counter and
> payload. Clients, such as the driver for the BPMP firmware, can use the
> library to exchange messages with remote processors.
> 
> Based on work by Peter Newman <pnewman at nvidia.com> and Joseph Lo
> <josephl at nvidia.com>.
> 
> Signed-off-by: Thierry Reding <treding at nvidia.com>
> ---
> Changes in v3:
> - use a more object oriented design
> 
>  drivers/firmware/Kconfig        |   1 +
>  drivers/firmware/Makefile       |   1 +
>  drivers/firmware/tegra/Kconfig  |  13 +
>  drivers/firmware/tegra/Makefile |   1 +
>  drivers/firmware/tegra/ivc.c    | 683 ++++++++++++++++++++++++++++++++++++++++
>  include/soc/tegra/ivc.h         | 109 +++++++
>  6 files changed, 808 insertions(+)
>  create mode 100644 drivers/firmware/tegra/Kconfig
>  create mode 100644 drivers/firmware/tegra/Makefile
>  create mode 100644 drivers/firmware/tegra/ivc.c
>  create mode 100644 include/soc/tegra/ivc.h

[snip]

> +static void *tegra_ivc_frame_virt(struct tegra_ivc *ivc,
> +				  struct tegra_ivc_header *header,
> +				  unsigned int frame)
> +{
> +	BUG_ON(frame >= ivc->num_frames);

WARN_ON and return an error pointer?

> +
> +	return (void *)(header + 1) + ivc->frame_size * frame;
> +}
> +
> +static inline dma_addr_t tegra_ivc_frame_phys(struct tegra_ivc *ivc,
> +					      dma_addr_t phys,
> +					      unsigned int frame)
> +{
> +	unsigned long offset;
> +
> +	BUG_ON(!ivc->peer);
> +	BUG_ON(frame >= ivc->num_frames);

WARN_ON?

> +
> +	offset = sizeof(struct tegra_ivc_header) + ivc->frame_size * frame;
> +
> +	return phys + offset;
> +}

[snip]

> +static int check_ivc_params(unsigned long base1, unsigned long base2,
> +			    unsigned int num_frames, size_t frame_size)
> +{
> +	BUG_ON(offsetof(struct tegra_ivc_header, tx.count) & (TEGRA_IVC_ALIGN - 1));
> +	BUG_ON(offsetof(struct tegra_ivc_header, rx.count) & (TEGRA_IVC_ALIGN - 1));
> +	BUG_ON(sizeof(struct tegra_ivc_header) & (TEGRA_IVC_ALIGN - 1));

WARN_ON?

> +	if ((uint64_t)num_frames * (uint64_t)frame_size >= 0x100000000) {
> +		pr_err("num_frames * frame_size overflows\n");
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * The headers must at least be aligned enough for counters
> +	 * to be accessed atomically.
> +	 */
> +	if (base1 & (TEGRA_IVC_ALIGN - 1)) {
> +		pr_err("IVC channel start not aligned: %lx\n", base1);
> +		return -EINVAL;
> +	}
> +
> +	if (base2 & (TEGRA_IVC_ALIGN - 1)) {
> +		pr_err("IVC channel start not aligned: %lx\n", base2);
> +		return -EINVAL;
> +	}
> +
> +	if (frame_size & (TEGRA_IVC_ALIGN - 1)) {
> +		pr_err("frame size not adequately aligned: %zu\n", frame_size);
> +		return -EINVAL;
> +	}
> +
> +	if (base1 < base2) {
> +		if (base1 + frame_size * num_frames > base2) {
> +			pr_err("queue regions overlap: %lx + %zx, %zx\n",
> +			       base1, frame_size, frame_size * num_frames);
> +			return -EINVAL;
> +		}
> +	} else {
> +		if (base2 + frame_size * num_frames > base1) {
> +			pr_err("queue regions overlap: %lx + %zx, %zx\n",
> +			       base2, frame_size, frame_size * num_frames);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +int tegra_ivc_init(struct tegra_ivc *ivc, struct device *peer,
> +		   void __iomem *rx_virt, dma_addr_t rx_phys,
> +		   void __iomem *tx_virt, dma_addr_t tx_phys,
> +		   unsigned int num_frames, size_t frame_size,
> +		   void (*notify)(struct tegra_ivc *ivc, void *data),
> +		   void *data)
> +{
> +	size_t queue_size;
> +	int err;
> +
> +	err = check_ivc_params((unsigned long)rx_virt, (unsigned long)tx_virt,
> +			       num_frames, frame_size);
> +	if (err < 0)
> +		return err;
> +
> +	BUG_ON(!ivc);
> +	BUG_ON(!notify);

We should check this first and just return -EINVAL.

> +	queue_size = tegra_ivc_total_queue_size(num_frames * frame_size);
> +
> +	/*
> +	 * All sizes that can be returned by communication functions should
> +	 * fit in an int.
> +	 */
> +	if (frame_size > INT_MAX)
> +		return -E2BIG;
> +
> +	ivc->rx.channel = (struct tegra_ivc_header *)rx_virt;
> +	ivc->tx.channel = (struct tegra_ivc_header *)tx_virt;
> +
> +	if (peer) {
> +		if (rx_phys != DMA_ERROR_CODE) {
> +			ivc->rx.phys = rx_phys;
> +			ivc->tx.phys = tx_phys;
> +		} else {
> +			ivc->rx.phys = dma_map_single(peer, ivc->rx.channel,
> +						      queue_size,
> +						      DMA_BIDIRECTIONAL);
> +			if (ivc->rx.phys == DMA_ERROR_CODE)
> +				return -ENOMEM;
> +
> +			ivc->tx.phys = dma_map_single(peer, ivc->tx.channel,
> +						      queue_size,
> +						      DMA_BIDIRECTIONAL);
> +			if (ivc->tx.phys == DMA_ERROR_CODE) {
> +				dma_unmap_single(peer, ivc->rx.phys,
> +						 queue_size,
> +						 DMA_BIDIRECTIONAL);
> +				return -ENOMEM;
> +			}
> +		}
> +	}
> +
> +	ivc->peer = peer;
> +	ivc->notify = notify;
> +	ivc->notify_data = data;
> +	ivc->frame_size = frame_size;
> +	ivc->num_frames = num_frames;
> +
> +	/*
> +	 * These values aren't necessarily correct until the channel has been
> +	 * reset.
> +	 */
> +	ivc->tx.position = 0;
> +	ivc->rx.position = 0;
> +
> +	return 0;
> +}
> +EXPORT_SYMBOL(tegra_ivc_init);
> diff --git a/include/soc/tegra/ivc.h b/include/soc/tegra/ivc.h
> new file mode 100644
> index 000000000000..af9a54a54e45
> --- /dev/null
> +++ b/include/soc/tegra/ivc.h
> @@ -0,0 +1,109 @@
> +/*
> + * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> + */
> +
> +#ifndef __TEGRA_IVC_H
> +
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/types.h>
> +
> +struct tegra_ivc_header;
> +
> +struct tegra_ivc {
> +	struct device *peer;
> +
> +	struct {
> +		struct tegra_ivc_header *channel;
> +		dma_addr_t phys;
> +		u32 position;
> +	} rx, tx;
> +
> +	void (*notify)(struct tegra_ivc *ivc, void *data);
> +	void *notify_data;
> +
> +	unsigned int num_frames;
> +	size_t frame_size;
> +};
> +
> +/**
> + * tegra_ivc_read_get_next_frame - Peek at the next frame to receive
> + * @ivc		pointer of the IVC channel
> + *
> + * Peek at the next frame to be received, without removing it from
> + * the queue.
> + *
> + * Returns a pointer to the frame, or an error encoded pointer.
> + */
> +void *tegra_ivc_read_get_next_frame(struct tegra_ivc *ivc);

Is it odd to return a void * pointer here and not a pointer to a
specific structure type?

> +/**
> + * tegra_ivc_read_advance - Advance the read queue
> + * @ivc		pointer of the IVC channel
> + *
> + * Advance the read queue
> + *
> + * Returns 0, or a negative error value if failed.
> + */
> +int tegra_ivc_read_advance(struct tegra_ivc *ivc);
> +
> +/**
> + * tegra_ivc_write_get_next_frame - Poke at the next frame to transmit
> + * @ivc		pointer of the IVC channel
> + *
> + * Get access to the next frame.
> + *
> + * Returns a pointer to the frame, or an error encoded pointer.
> + */
> +void *tegra_ivc_write_get_next_frame(struct tegra_ivc *ivc);

Same here.

Cheers
Jon

-- 
nvpublic



More information about the linux-arm-kernel mailing list