diff mbox

[5/6] remoteproc/MIPS: Add a remoteproc driver for MIPS

Message ID 1472810395-21381-6-git-send-email-matt.redfearn@imgtec.com
State Superseded
Headers show

Commit Message

Matt Redfearn Sept. 2, 2016, 9:59 a.m. UTC
Add a remoteproc driver to steal, load the firmware, and boot an offline
MIPS core, turning it into a coprocessor.

This driver provides a sysfs to allow arbitrary firmware to be loaded
onto a core, which may expose virtio devices. Coprocessor firmware must
abide by the UHI coprocessor boot protocol.

Signed-off-by: Lisa Parratt <lisa.parratt@imgtec.com>
Signed-off-by: Matt Redfearn <matt.redfearn@imgtec.com>

---

 Documentation/ABI/testing/sysfs-class-mips-rproc |  24 +
 drivers/remoteproc/Kconfig                       |  11 +
 drivers/remoteproc/Makefile                      |   1 +
 drivers/remoteproc/mips_remoteproc.c             | 651 +++++++++++++++++++++++
 4 files changed, 687 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-mips-rproc
 create mode 100644 drivers/remoteproc/mips_remoteproc.c

Comments

Matt Redfearn Sept. 12, 2016, 8:49 a.m. UTC | #1
Hi,

Any feedback (or an ack) on this patch from Ohad or Bjorn would be much 
appreciated!

Thanks,

Matt


On 02/09/16 10:59, Matt Redfearn wrote:
> Add a remoteproc driver to steal, load the firmware, and boot an offline
> MIPS core, turning it into a coprocessor.
>
> This driver provides a sysfs to allow arbitrary firmware to be loaded
> onto a core, which may expose virtio devices. Coprocessor firmware must
> abide by the UHI coprocessor boot protocol.
>
> Signed-off-by: Lisa Parratt <lisa.parratt@imgtec.com>
> Signed-off-by: Matt Redfearn <matt.redfearn@imgtec.com>
>
> ---
>
>   Documentation/ABI/testing/sysfs-class-mips-rproc |  24 +
>   drivers/remoteproc/Kconfig                       |  11 +
>   drivers/remoteproc/Makefile                      |   1 +
>   drivers/remoteproc/mips_remoteproc.c             | 651 +++++++++++++++++++++++
>   4 files changed, 687 insertions(+)
>   create mode 100644 Documentation/ABI/testing/sysfs-class-mips-rproc
>   create mode 100644 drivers/remoteproc/mips_remoteproc.c
>
> diff --git a/Documentation/ABI/testing/sysfs-class-mips-rproc b/Documentation/ABI/testing/sysfs-class-mips-rproc
> new file mode 100644
> index 000000000000..c09d02a755e4
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-mips-rproc
> @@ -0,0 +1,24 @@
> +What:		/sys/class/mips-rproc/rproc#/firmware
> +Date:		August 2015
> +Contact:	Lisa Parratt <lisa.parratt@imgtec.com>
> +Description:	MIPS VPE remoteproc start
> +
> +	This node only exists when a VPE is considered offline by Linux. Writes
> +	to this file will start firmware running on a VPE.
> +
> +	If the VPE is idle, specifying a name will cause a remoteproc instance
> +	to be allocated, which will cause the core to be stolen, the firmware
> +	image to be loaded, and the remoteproc instance to be started.
> +	Otherwise, the operation will fail.
> +
> +What:		/sys/class/mips-rproc/rproc#/stop
> +Date:		August 2015
> +Contact:	Lisa Parratt <lisa.parratt@imgtec.com>
> +Description:	MIPS VPE remoteproc stop
> +
> +	This node only exists when a VPE is considered offline by Linux. Writes
> +	to this file will stop firmware running on a VPE.
> +
> +	If the VPE is running a remote proc instance, the instance will be
> +	stopped, the core returned, and the instance freed.
> +	Otherwise, the operation will fail.
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index 1a8bf76a925f..05db52e0e668 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -100,4 +100,15 @@ config ST_REMOTEPROC
>   	  processor framework.
>   	  This can be either built-in or a loadable module.
>   
> +config MIPS_REMOTEPROC
> +	tristate "MIPS remoteproc support"
> +	depends on MIPS_CPS && HAS_DMA
> +	select CMA
> +	select REMOTEPROC
> +	select MIPS_STEAL
> +	help
> +	  Say y here to support using offline cores/VPEs as remote processors
> +	  via the remote processor framework.
> +	  If unsure say N.
> +
>   endmenu
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> index 92d3758bd15c..de19cd320f3a 100644
> --- a/drivers/remoteproc/Makefile
> +++ b/drivers/remoteproc/Makefile
> @@ -14,3 +14,4 @@ obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
>   obj-$(CONFIG_QCOM_MDT_LOADER)		+= qcom_mdt_loader.o
>   obj-$(CONFIG_QCOM_Q6V5_PIL)		+= qcom_q6v5_pil.o
>   obj-$(CONFIG_ST_REMOTEPROC)		+= st_remoteproc.o
> +obj-$(CONFIG_MIPS_REMOTEPROC)		+= mips_remoteproc.o
> diff --git a/drivers/remoteproc/mips_remoteproc.c b/drivers/remoteproc/mips_remoteproc.c
> new file mode 100644
> index 000000000000..944ad66280b4
> --- /dev/null
> +++ b/drivers/remoteproc/mips_remoteproc.c
> @@ -0,0 +1,651 @@
> +/*
> + * MIPS Remote Processor driver
> + *
> + * Copyright (C) 2016 Imagination Technologies
> + * Lisa Parratt <lisa.parratt@imgtec.com>
> + * Matt Redfearn <matt.redfearn@imgtec.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> +
> +#include <linux/cpu.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/remoteproc.h>
> +
> +#include <asm/dma-coherence.h>
> +#include <asm/smp-cps.h>
> +#include <asm/tlbflush.h>
> +#include <asm/tlbmisc.h>
> +
> +#include "remoteproc_internal.h"
> +
> +struct mips_rproc {
> +	struct rproc		*rproc;
> +	char			*firmware;
> +	struct task_struct	*tsk;
> +	struct device		dev;
> +	unsigned int		cpu;
> +	int			ipi_linux;
> +	int			ipi_remote;
> +};
> +
> +static struct rproc *mips_rprocs[NR_CPUS];
> +
> +#define to_mips_rproc(d) container_of(d, struct mips_rproc, dev)
> +
> +
> +/* Compute the largest page mask a physical address can be mapped with */
> +static unsigned long mips_rproc_largest_pm(unsigned long pa,
> +					   unsigned long maxmask)
> +{
> +	unsigned long mask;
> +	/* Find address bits limiting alignment */
> +	unsigned long shift = ffs(pa);
> +
> +	/* Obey MIPS restrictions on page sizes */
> +	if (pa) {
> +		if (shift & 1)
> +			shift -= 2;
> +		else
> +			shift--;
> +	}
> +	mask = ULONG_MAX << shift;
> +	return maxmask & ~mask;
> +}
> +
> +/* Compute the next largest page mask for a given page mask */
> +static unsigned long mips_rproc_next_pm(unsigned long pm, unsigned long maxmask)
> +{
> +	if (pm != PM_4K)
> +		return ((pm << 2) | pm) & maxmask;
> +	else
> +		return PM_16K;
> +}
> +
> +static void mips_map_page(unsigned long da, unsigned long pa, int c,
> +			  unsigned long pagemask, unsigned long pagesize)
> +{
> +	unsigned long pa2 = pa + (pagesize / 2);
> +	unsigned long entryhi, entrylo0, entrylo1;
> +
> +	/* Compute the mapping */
> +	pa = (pa >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> +	pa2 = (pa2 >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
> +	entryhi = da & 0xfffffe000;
> +	entrylo0 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa;
> +	entrylo1 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa2;
> +
> +	pr_debug("Create wired entry %d, CCA %d\n", read_c0_wired(), c);
> +	pr_debug(" EntryHi: 0x%016lx\n", entryhi);
> +	pr_debug(" EntryLo0: 0x%016lx\n", entrylo0);
> +	pr_debug(" EntryLo1: 0x%016lx\n", entrylo1);
> +	pr_debug(" Pagemask: 0x%016lx\n", pagemask);
> +	pr_debug("\n");
> +
> +	add_wired_entry(entrylo0, entrylo1, entryhi, pagemask);
> +}
> +
> +/*
> + * Compute the page required to fulfill a mapping. Escapes alignment derived
> + * page size limitations before using biggest fit to map the remainder.
> + */
> +static inline void mips_rproc_fit_page(unsigned long da, unsigned long pa,
> +					int c, unsigned long size,
> +					unsigned long maxmask)
> +{
> +	unsigned long bigmask, nextmask;
> +	unsigned long pagemask, pagesize;
> +	unsigned long distance, target;
> +
> +	do {
> +		/* Compute the current largest page mask */
> +		bigmask = mips_rproc_largest_pm(pa, maxmask);
> +		/* Compute the next largest pagesize */
> +		nextmask = mips_rproc_next_pm(bigmask, maxmask);
> +		/*
> +		 * Compute the distance from our current physical address to
> +		 * the next page boundary.
> +		 */
> +		distance = (nextmask + 0x2000) - (pa & nextmask);
> +		/*
> +		 * Decide between searching to get to the next highest page
> +		 * boundary or finishing.
> +		 */
> +		target = distance < size ? distance : size;
> +		/* Fit */
> +		while (target) {
> +			/* Find the largest supported page size that will fit */
> +			for (pagesize = maxmask + 0x2000;
> +			     (pagesize > 0x2000) && (pagesize > target);
> +			     pagesize /= 4) {
> +			}
> +			/* Convert it to a page mask */
> +			pagemask = pagesize - 0x2000;
> +			/* Emit it */
> +			mips_map_page(da, pa, c, pagemask, pagesize);
> +			/* Move to next step */
> +			size -= pagesize;
> +			da += pagesize;
> +			pa += pagesize;
> +			target -= pagesize;
> +		}
> +	} while (size);
> +}
> +
> +
> +static int mips_rproc_carveouts(struct rproc *rproc, int max_pagemask)
> +{
> +	struct rproc_mem_entry *carveout;
> +
> +	list_for_each_entry(carveout, &rproc->carveouts, node) {
> +		int c = CONF_CM_CACHABLE_COW;
> +
> +		dev_dbg(&rproc->dev,
> +			"carveout mapping da 0x%x -> %pad length 0x%x, CCA %d",
> +			carveout->da, &carveout->dma, carveout->len, c);
> +
> +		mips_rproc_fit_page(carveout->da, carveout->dma, c,
> +				    carveout->len, max_pagemask);
> +	}
> +	return 0;
> +}
> +
> +
> +static int mips_rproc_vdevs(struct rproc *rproc, int max_pagemask)
> +{
> +	struct rproc_vdev *rvdev;
> +
> +	list_for_each_entry(rvdev, &rproc->rvdevs, node) {
> +		int i, size;
> +
> +		for (i = 0; i < ARRAY_SIZE(rvdev->vring); i++) {
> +			struct rproc_vring *vring = &rvdev->vring[i];
> +			unsigned long pa = vring->dma;
> +			int c;
> +
> +			if (hw_coherentio) {
> +				/*
> +				 * The DMA API will allocate cacheable buffers
> +				 * for shared resources, so the firmware should
> +				 * also access those buffers cached
> +				 */
> +				c = (_page_cachable_default >> _CACHE_SHIFT);
> +			} else {
> +				/*
> +				 * Otherwise, shared buffers should be accessed
> +				 * uncached
> +				 */
> +				c = CONF_CM_UNCACHED;
> +			}
> +
> +			/* actual size of vring (in bytes) */
> +			size = PAGE_ALIGN(vring_size(vring->len, vring->align));
> +
> +			dev_dbg(&rproc->dev,
> +				"vring mapping da %pad -> %pad length 0x%x, CCA %d",
> +				&vring->dma, &vring->dma, size, c);
> +
> +			mips_rproc_fit_page(pa, pa, c, size, max_pagemask);
> +		}
> +	}
> +	return 0;
> +}
> +
> +static void mips_rproc_cpu_entry(void)
> +{
> +	struct rproc *rproc = mips_rprocs[smp_processor_id()];
> +	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
> +	int ipi_to_remote = ipi_get_hwirq(mproc->ipi_remote, mproc->cpu);
> +	int ipi_from_remote = ipi_get_hwirq(mproc->ipi_linux, 0);
> +	unsigned long old_pagemask, max_pagemask;
> +
> +	if (!rproc)
> +		return;
> +
> +	dev_info(&rproc->dev, "Starting %s on MIPS CPU%d\n",
> +		 mproc->firmware, mproc->cpu);
> +
> +	/* Get the maximum pagemask supported on this CPU */
> +	old_pagemask = read_c0_pagemask();
> +	write_c0_pagemask(PM_HUGE_MASK);
> +	mtc0_tlbw_hazard();
> +	max_pagemask = read_c0_pagemask();
> +	write_c0_pagemask(old_pagemask);
> +	mtc0_tlbw_hazard();
> +
> +	/* Start with no wired entries */
> +	write_c0_wired(0);
> +
> +	/* Flush all previous TLB entries */
> +	local_flush_tlb_all();
> +
> +	/* Map firmware resources into virtual memory */
> +	mips_rproc_carveouts(rproc, max_pagemask);
> +	mips_rproc_vdevs(rproc, max_pagemask);
> +
> +	dev_dbg(&rproc->dev, "IPI to remote: %d\n", ipi_to_remote);
> +	dev_dbg(&rproc->dev, "IPI from remote: %d\n", ipi_from_remote);
> +
> +	/* Hand off the CPU to the firmware */
> +	dev_dbg(&rproc->dev, "Jumping to firmware at 0x%x\n", rproc->bootaddr);
> +
> +	write_c0_entryhi(0); /* Set ASID 0 */
> +	tlbw_use_hazard();
> +
> +	/* Firmware protocol */
> +	__asm__("addiu $a0, $zero, -3");
> +	__asm__("move $a1, %0" :: "r" (ipi_to_remote));
> +	__asm__("move $a2, %0" :: "r" (ipi_from_remote));
> +	__asm__("move $a3, $zero");
> +	__asm__("jr %0" :: "r" (rproc->bootaddr));
> +}
> +
> +
> +static irqreturn_t mips_rproc_ipi_handler(int irq, void *dev_id)
> +{
> +	/* Synthetic interrupts shouldn't need acking */
> +	return IRQ_WAKE_THREAD;
> +}
> +
> +
> +static irqreturn_t mips_rproc_vq_int(int irq, void *p)
> +{
> +	struct rproc *rproc = (struct rproc *)p;
> +	void *entry;
> +	int id;
> +
> +	/* We don't have a mailbox, so iterate over all vqs and kick them. */
> +	idr_for_each_entry(&rproc->notifyids, entry, id)
> +		rproc_vq_interrupt(rproc, id);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +
> +/* Helper function to find the IPI domain */
> +static struct irq_domain *ipi_domain(void)
> +{
> +	struct device_node *node = of_irq_find_parent(of_root);
> +	struct irq_domain *ipidomain;
> +
> +	ipidomain = irq_find_matching_host(node, DOMAIN_BUS_IPI);
> +	/*
> +	 * Some platforms have half DT setup. So if we found irq node but
> +	 * didn't find an ipidomain, try to search for one that is not in the
> +	 * DT.
> +	 */
> +	if (node && !ipidomain)
> +		ipidomain = irq_find_matching_host(NULL, DOMAIN_BUS_IPI);
> +
> +	return ipidomain;
> +}
> +
> +
> +int mips_rproc_op_start(struct rproc *rproc)
> +{
> +	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
> +	int err;
> +	int cpu = mproc->cpu;
> +
> +	if (mips_rprocs[cpu]) {
> +		dev_err(&rproc->dev, "CPU%d in use\n", cpu);
> +		return -EBUSY;
> +	}
> +	mips_rprocs[cpu] = rproc;
> +
> +	/* Create task for the CPU to use before handing off to firmware */
> +	mproc->tsk = fork_idle(cpu);
> +	if (IS_ERR(mproc->tsk)) {
> +		dev_err(&rproc->dev, "fork_idle() failed for CPU%d\n", cpu);
> +		return -ENOMEM;
> +	}
> +
> +	/* We won't be needing the Linux IPIs anymore */
> +	if (mips_smp_ipi_free(get_cpu_mask(cpu)))
> +		return -EINVAL;
> +
> +	/*
> +	 * Direct IPIs from the remote processor to CPU0 since that can't be
> +	 * offlined while the remote CPU is running.
> +	 */
> +	mproc->ipi_linux = irq_reserve_ipi(ipi_domain(), get_cpu_mask(0));
> +	if (!mproc->ipi_linux) {
> +		dev_err(&mproc->dev, "Failed to reserve incoming kick\n");
> +		goto exit_rproc_nofrom;
> +	}
> +
> +	mproc->ipi_remote = irq_reserve_ipi(ipi_domain(), get_cpu_mask(cpu));
> +	if (!mproc->ipi_remote) {
> +		dev_err(&mproc->dev, "Failed to reserve outgoing kick\n");
> +		goto exit_rproc_noto;
> +	}
> +
> +	/* register incoming ipi */
> +	err = devm_request_threaded_irq(&mproc->dev, mproc->ipi_linux,
> +					mips_rproc_ipi_handler,
> +					mips_rproc_vq_int, 0,
> +					"mips-rproc IPI in", mproc->rproc);
> +	if (err) {
> +		dev_err(&mproc->dev, "Failed to register incoming kick: %d\n",
> +			err);
> +		goto exit_rproc_noint;
> +	}
> +
> +	if (!mips_cps_steal_cpu_and_execute(cpu, &mips_rproc_cpu_entry,
> +						mproc->tsk))
> +		return 0;
> +
> +	dev_err(&mproc->dev, "Failed to steal CPU%d for remote\n", cpu);
> +	devm_free_irq(&mproc->dev, mproc->ipi_linux, mproc->rproc);
> +exit_rproc_noint:
> +	irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(cpu));
> +exit_rproc_noto:
> +	irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> +exit_rproc_nofrom:
> +	free_task(mproc->tsk);
> +	mips_rprocs[cpu] = NULL;
> +
> +	/* Set up the Linux IPIs again */
> +	mips_smp_ipi_allocate(get_cpu_mask(cpu));
> +	return -EINVAL;
> +}
> +
> +int mips_rproc_op_stop(struct rproc *rproc)
> +{
> +	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
> +
> +	if (mproc->ipi_linux)
> +		devm_free_irq(&mproc->dev, mproc->ipi_linux, mproc->rproc);
> +
> +	irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
> +	irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(mproc->cpu));
> +
> +	/* Set up the Linux IPIs again */
> +	mips_smp_ipi_allocate(get_cpu_mask(mproc->cpu));
> +
> +	free_task(mproc->tsk);
> +
> +	mips_rprocs[mproc->cpu] = NULL;
> +
> +	return mips_cps_halt_and_return_cpu(mproc->cpu);
> +}
> +
> +
> +void mips_rproc_op_kick(struct rproc *rproc, int vqid)
> +{
> +	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
> +
> +	ipi_send_single(mproc->ipi_remote, mproc->cpu);
> +}
> +
> +struct rproc_ops mips_rproc_proc_ops = {
> +	.start	= mips_rproc_op_start,
> +	.stop	= mips_rproc_op_stop,
> +	.kick	= mips_rproc_op_kick,
> +};
> +
> +
> +static int mips_rproc_probe(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static int mips_rproc_remove(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static struct platform_driver mips_rproc_driver = {
> +	.probe = mips_rproc_probe,
> +	.remove = mips_rproc_remove,
> +	.driver = {
> +		.name = "mips-rproc"
> +	},
> +};
> +
> +
> +/* Steal a core and run some firmware on it */
> +int mips_rproc_start(struct mips_rproc *mproc, const char *firmware, size_t len)
> +{
> +	int err = -EINVAL;
> +	struct mips_rproc **priv;
> +
> +	/* Duplicate the filename, dropping whitespace from the end via len */
> +	mproc->firmware = kstrndup(firmware, len, GFP_KERNEL);
> +	if (!mproc->firmware)
> +		return -ENOMEM;
> +
> +	mproc->rproc = rproc_alloc(&mproc->dev, "mips", &mips_rproc_proc_ops,
> +				   mproc->firmware,
> +				   sizeof(struct mips_rproc *));
> +	if (!mproc->rproc)
> +		return -ENOMEM;
> +
> +	priv = mproc->rproc->priv;
> +	*priv = mproc;
> +
> +	/* go live! */
> +	err = rproc_add(mproc->rproc);
> +	if (err) {
> +		dev_err(&mproc->dev, "Failed to add rproc: %d\n", err);
> +		rproc_put(mproc->rproc);
> +		kfree(mproc->firmware);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Stop a core, and return it to being offline */
> +int mips_rproc_stop(struct mips_rproc *mproc)
> +{
> +	rproc_shutdown(mproc->rproc);
> +	rproc_del(mproc->rproc);
> +	rproc_put(mproc->rproc);
> +	mproc->rproc = NULL;
> +	return 0;
> +}
> +
> +/* sysfs interface to mips_rproc_start */
> +static ssize_t firmware_store(struct device *dev,
> +			      struct device_attribute *attr,
> +			      const char *buf, size_t count)
> +{
> +	struct mips_rproc *mproc = to_mips_rproc(dev);
> +	size_t len = count;
> +	int err = -EINVAL;
> +
> +	if (buf[count - 1] == '\n')
> +		len--;
> +
> +	if (!mproc->rproc && len)
> +		err = mips_rproc_start(mproc, buf, len);
> +	else if (len)
> +		err = -EBUSY;
> +
> +	return err ? err : count;
> +}
> +static DEVICE_ATTR_WO(firmware);
> +
> +/* sysfs interface to mips_rproc_stop */
> +static ssize_t stop_store(struct device *dev,
> +			      struct device_attribute *attr,
> +			      const char *buf, size_t count)
> +{
> +	struct mips_rproc *mproc = to_mips_rproc(dev);
> +	int err = -EINVAL;
> +
> +
> +	if (mproc->rproc)
> +		err = mips_rproc_stop(mproc);
> +	else
> +		err = -EBUSY;
> +
> +	return err ? err : count;
> +}
> +static DEVICE_ATTR_WO(stop);
> +
> +/* Boiler plate for devclarng mips-rproc sysfs devices */
> +static struct attribute *mips_rproc_attrs[] = {
> +	&dev_attr_firmware.attr,
> +	&dev_attr_stop.attr,
> +	NULL
> +};
> +
> +static struct attribute_group mips_rproc_devgroup = {
> +	.attrs = mips_rproc_attrs
> +};
> +
> +static const struct attribute_group *mips_rproc_devgroups[] = {
> +	&mips_rproc_devgroup,
> +	NULL
> +};
> +
> +static char *mips_rproc_devnode(struct device *dev, umode_t *mode)
> +{
> +	return kasprintf(GFP_KERNEL, "mips-rproc/%s", dev_name(dev));
> +}
> +
> +static struct class mips_rproc_class = {
> +	.name		= "mips-rproc",
> +	.devnode	= mips_rproc_devnode,
> +	.dev_groups	= mips_rproc_devgroups,
> +};
> +
> +static void mips_rproc_release(struct device *dev)
> +{
> +}
> +
> +static int mips_rproc_uevent(struct device *dev, struct kobj_uevent_env *env)
> +{
> +	struct mips_rproc *mproc = to_mips_rproc(dev);
> +
> +	if (!mproc)
> +		return -ENODEV;
> +
> +	return 0;
> +}
> +
> +static struct device_type mips_rproc_type = {
> +	.release	= mips_rproc_release,
> +	.uevent		= mips_rproc_uevent
> +};
> +
> +/* Helper function for locating the device for a CPU */
> +int mips_rproc_device_rproc_match(struct device *dev, const void *data)
> +{
> +	struct mips_rproc *mproc = to_mips_rproc(dev);
> +	unsigned int cpu = *(unsigned int *)data;
> +
> +	return mproc->cpu == cpu;
> +}
> +
> +/* Create a sysfs device in response to CPU down */
> +int mips_rproc_device_register(unsigned int cpu)
> +{
> +	struct mips_rproc *dev;
> +
> +	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +	if (!dev)
> +		return -EINVAL;
> +
> +	dev->dev.driver = &mips_rproc_driver.driver;
> +	dev->dev.type = &mips_rproc_type;
> +	dev->dev.class = &mips_rproc_class;
> +	dev->dev.id = cpu;
> +	dev_set_name(&dev->dev, "rproc%u", cpu);
> +	dev->cpu = cpu;
> +
> +	return device_register(&dev->dev);
> +}
> +
> +/* Destroy a sysfs device in response to CPU up */
> +int mips_rproc_device_unregister(unsigned int cpu)
> +{
> +	struct device *dev = class_find_device(&mips_rproc_class, NULL, &cpu,
> +					       mips_rproc_device_rproc_match);
> +	struct mips_rproc *mproc = to_mips_rproc(dev);
> +
> +	if (mips_rprocs[cpu])
> +		mips_rproc_stop(mproc);
> +
> +	put_device(dev);
> +	device_unregister(dev);
> +	kfree(mproc);
> +	return 0;
> +}
> +
> +/* Intercept CPU hotplug events for syfs purposes */
> +static int mips_rproc_callback(struct notifier_block *nfb, unsigned long action,
> +			       void *hcpu)
> +{
> +	unsigned int cpu = (unsigned long)hcpu;
> +
> +	switch (action) {
> +	case CPU_UP_PREPARE:
> +	case CPU_DOWN_FAILED:
> +		mips_rproc_device_unregister(cpu);
> +		break;
> +	case CPU_DOWN_PREPARE:
> +		mips_rproc_device_register(cpu);
> +		break;
> +	}
> +
> +	return NOTIFY_OK;
> +}
> +
> +static struct notifier_block mips_rproc_notifier __refdata = {
> +	.notifier_call = mips_rproc_callback
> +};
> +
> +static int __init mips_rproc_init(void)
> +{
> +	int cpu;
> +	/* create mips-rproc device class for sysfs */
> +	int err = class_register(&mips_rproc_class);
> +
> +	if (err) {
> +		pr_err("mips-proc: unable to register mips-rproc class\n");
> +		return err;
> +	}
> +
> +	/* Dynamically create mips-rproc class devices based on hotplug data */
> +	get_online_cpus();
> +	for_each_possible_cpu(cpu)
> +		if (!cpu_online(cpu))
> +			mips_rproc_device_register(cpu);
> +	register_hotcpu_notifier(&mips_rproc_notifier);
> +	put_online_cpus();
> +
> +	return 0;
> +}
> +
> +static void __exit mips_rproc_exit(void)
> +{
> +	int cpu;
> +	/* Destroy mips-rproc class devices */
> +	get_online_cpus();
> +	unregister_hotcpu_notifier(&mips_rproc_notifier);
> +	for_each_possible_cpu(cpu)
> +		if (!cpu_online(cpu))
> +			mips_rproc_device_unregister(cpu);
> +	put_online_cpus();
> +
> +	class_unregister(&mips_rproc_class);
> +}
> +
> +subsys_initcall(mips_rproc_init);
> +module_exit(mips_rproc_exit);
> +
> +module_platform_driver(mips_rproc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("MIPS Remote Processor control driver");

--
To unsubscribe from this list: send the line "unsubscribe linux-remoteproc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/ABI/testing/sysfs-class-mips-rproc b/Documentation/ABI/testing/sysfs-class-mips-rproc
new file mode 100644
index 000000000000..c09d02a755e4
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-mips-rproc
@@ -0,0 +1,24 @@ 
+What:		/sys/class/mips-rproc/rproc#/firmware
+Date:		August 2015
+Contact:	Lisa Parratt <lisa.parratt@imgtec.com>
+Description:	MIPS VPE remoteproc start
+
+	This node only exists when a VPE is considered offline by Linux. Writes
+	to this file will start firmware running on a VPE.
+
+	If the VPE is idle, specifying a name will cause a remoteproc instance
+	to be allocated, which will cause the core to be stolen, the firmware
+	image to be loaded, and the remoteproc instance to be started.
+	Otherwise, the operation will fail.
+
+What:		/sys/class/mips-rproc/rproc#/stop
+Date:		August 2015
+Contact:	Lisa Parratt <lisa.parratt@imgtec.com>
+Description:	MIPS VPE remoteproc stop
+
+	This node only exists when a VPE is considered offline by Linux. Writes
+	to this file will stop firmware running on a VPE.
+
+	If the VPE is running a remote proc instance, the instance will be
+	stopped, the core returned, and the instance freed.
+	Otherwise, the operation will fail.
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 1a8bf76a925f..05db52e0e668 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -100,4 +100,15 @@  config ST_REMOTEPROC
 	  processor framework.
 	  This can be either built-in or a loadable module.
 
+config MIPS_REMOTEPROC
+	tristate "MIPS remoteproc support"
+	depends on MIPS_CPS && HAS_DMA
+	select CMA
+	select REMOTEPROC
+	select MIPS_STEAL
+	help
+	  Say y here to support using offline cores/VPEs as remote processors
+	  via the remote processor framework.
+	  If unsure say N.
+
 endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 92d3758bd15c..de19cd320f3a 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -14,3 +14,4 @@  obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
 obj-$(CONFIG_QCOM_MDT_LOADER)		+= qcom_mdt_loader.o
 obj-$(CONFIG_QCOM_Q6V5_PIL)		+= qcom_q6v5_pil.o
 obj-$(CONFIG_ST_REMOTEPROC)		+= st_remoteproc.o
+obj-$(CONFIG_MIPS_REMOTEPROC)		+= mips_remoteproc.o
diff --git a/drivers/remoteproc/mips_remoteproc.c b/drivers/remoteproc/mips_remoteproc.c
new file mode 100644
index 000000000000..944ad66280b4
--- /dev/null
+++ b/drivers/remoteproc/mips_remoteproc.c
@@ -0,0 +1,651 @@ 
+/*
+ * MIPS Remote Processor driver
+ *
+ * Copyright (C) 2016 Imagination Technologies
+ * Lisa Parratt <lisa.parratt@imgtec.com>
+ * Matt Redfearn <matt.redfearn@imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/cpu.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/remoteproc.h>
+
+#include <asm/dma-coherence.h>
+#include <asm/smp-cps.h>
+#include <asm/tlbflush.h>
+#include <asm/tlbmisc.h>
+
+#include "remoteproc_internal.h"
+
+struct mips_rproc {
+	struct rproc		*rproc;
+	char			*firmware;
+	struct task_struct	*tsk;
+	struct device		dev;
+	unsigned int		cpu;
+	int			ipi_linux;
+	int			ipi_remote;
+};
+
+static struct rproc *mips_rprocs[NR_CPUS];
+
+#define to_mips_rproc(d) container_of(d, struct mips_rproc, dev)
+
+
+/* Compute the largest page mask a physical address can be mapped with */
+static unsigned long mips_rproc_largest_pm(unsigned long pa,
+					   unsigned long maxmask)
+{
+	unsigned long mask;
+	/* Find address bits limiting alignment */
+	unsigned long shift = ffs(pa);
+
+	/* Obey MIPS restrictions on page sizes */
+	if (pa) {
+		if (shift & 1)
+			shift -= 2;
+		else
+			shift--;
+	}
+	mask = ULONG_MAX << shift;
+	return maxmask & ~mask;
+}
+
+/* Compute the next largest page mask for a given page mask */
+static unsigned long mips_rproc_next_pm(unsigned long pm, unsigned long maxmask)
+{
+	if (pm != PM_4K)
+		return ((pm << 2) | pm) & maxmask;
+	else
+		return PM_16K;
+}
+
+static void mips_map_page(unsigned long da, unsigned long pa, int c,
+			  unsigned long pagemask, unsigned long pagesize)
+{
+	unsigned long pa2 = pa + (pagesize / 2);
+	unsigned long entryhi, entrylo0, entrylo1;
+
+	/* Compute the mapping */
+	pa = (pa >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
+	pa2 = (pa2 >> 6) & (ULONG_MAX << MIPS_ENTRYLO_PFN_SHIFT);
+	entryhi = da & 0xfffffe000;
+	entrylo0 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa;
+	entrylo1 = (c << ENTRYLO_C_SHIFT) | ENTRYLO_D | ENTRYLO_V | pa2;
+
+	pr_debug("Create wired entry %d, CCA %d\n", read_c0_wired(), c);
+	pr_debug(" EntryHi: 0x%016lx\n", entryhi);
+	pr_debug(" EntryLo0: 0x%016lx\n", entrylo0);
+	pr_debug(" EntryLo1: 0x%016lx\n", entrylo1);
+	pr_debug(" Pagemask: 0x%016lx\n", pagemask);
+	pr_debug("\n");
+
+	add_wired_entry(entrylo0, entrylo1, entryhi, pagemask);
+}
+
+/*
+ * Compute the page required to fulfill a mapping. Escapes alignment derived
+ * page size limitations before using biggest fit to map the remainder.
+ */
+static inline void mips_rproc_fit_page(unsigned long da, unsigned long pa,
+					int c, unsigned long size,
+					unsigned long maxmask)
+{
+	unsigned long bigmask, nextmask;
+	unsigned long pagemask, pagesize;
+	unsigned long distance, target;
+
+	do {
+		/* Compute the current largest page mask */
+		bigmask = mips_rproc_largest_pm(pa, maxmask);
+		/* Compute the next largest pagesize */
+		nextmask = mips_rproc_next_pm(bigmask, maxmask);
+		/*
+		 * Compute the distance from our current physical address to
+		 * the next page boundary.
+		 */
+		distance = (nextmask + 0x2000) - (pa & nextmask);
+		/*
+		 * Decide between searching to get to the next highest page
+		 * boundary or finishing.
+		 */
+		target = distance < size ? distance : size;
+		/* Fit */
+		while (target) {
+			/* Find the largest supported page size that will fit */
+			for (pagesize = maxmask + 0x2000;
+			     (pagesize > 0x2000) && (pagesize > target);
+			     pagesize /= 4) {
+			}
+			/* Convert it to a page mask */
+			pagemask = pagesize - 0x2000;
+			/* Emit it */
+			mips_map_page(da, pa, c, pagemask, pagesize);
+			/* Move to next step */
+			size -= pagesize;
+			da += pagesize;
+			pa += pagesize;
+			target -= pagesize;
+		}
+	} while (size);
+}
+
+
+static int mips_rproc_carveouts(struct rproc *rproc, int max_pagemask)
+{
+	struct rproc_mem_entry *carveout;
+
+	list_for_each_entry(carveout, &rproc->carveouts, node) {
+		int c = CONF_CM_CACHABLE_COW;
+
+		dev_dbg(&rproc->dev,
+			"carveout mapping da 0x%x -> %pad length 0x%x, CCA %d",
+			carveout->da, &carveout->dma, carveout->len, c);
+
+		mips_rproc_fit_page(carveout->da, carveout->dma, c,
+				    carveout->len, max_pagemask);
+	}
+	return 0;
+}
+
+
+static int mips_rproc_vdevs(struct rproc *rproc, int max_pagemask)
+{
+	struct rproc_vdev *rvdev;
+
+	list_for_each_entry(rvdev, &rproc->rvdevs, node) {
+		int i, size;
+
+		for (i = 0; i < ARRAY_SIZE(rvdev->vring); i++) {
+			struct rproc_vring *vring = &rvdev->vring[i];
+			unsigned long pa = vring->dma;
+			int c;
+
+			if (hw_coherentio) {
+				/*
+				 * The DMA API will allocate cacheable buffers
+				 * for shared resources, so the firmware should
+				 * also access those buffers cached
+				 */
+				c = (_page_cachable_default >> _CACHE_SHIFT);
+			} else {
+				/*
+				 * Otherwise, shared buffers should be accessed
+				 * uncached
+				 */
+				c = CONF_CM_UNCACHED;
+			}
+
+			/* actual size of vring (in bytes) */
+			size = PAGE_ALIGN(vring_size(vring->len, vring->align));
+
+			dev_dbg(&rproc->dev,
+				"vring mapping da %pad -> %pad length 0x%x, CCA %d",
+				&vring->dma, &vring->dma, size, c);
+
+			mips_rproc_fit_page(pa, pa, c, size, max_pagemask);
+		}
+	}
+	return 0;
+}
+
+static void mips_rproc_cpu_entry(void)
+{
+	struct rproc *rproc = mips_rprocs[smp_processor_id()];
+	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
+	int ipi_to_remote = ipi_get_hwirq(mproc->ipi_remote, mproc->cpu);
+	int ipi_from_remote = ipi_get_hwirq(mproc->ipi_linux, 0);
+	unsigned long old_pagemask, max_pagemask;
+
+	if (!rproc)
+		return;
+
+	dev_info(&rproc->dev, "Starting %s on MIPS CPU%d\n",
+		 mproc->firmware, mproc->cpu);
+
+	/* Get the maximum pagemask supported on this CPU */
+	old_pagemask = read_c0_pagemask();
+	write_c0_pagemask(PM_HUGE_MASK);
+	mtc0_tlbw_hazard();
+	max_pagemask = read_c0_pagemask();
+	write_c0_pagemask(old_pagemask);
+	mtc0_tlbw_hazard();
+
+	/* Start with no wired entries */
+	write_c0_wired(0);
+
+	/* Flush all previous TLB entries */
+	local_flush_tlb_all();
+
+	/* Map firmware resources into virtual memory */
+	mips_rproc_carveouts(rproc, max_pagemask);
+	mips_rproc_vdevs(rproc, max_pagemask);
+
+	dev_dbg(&rproc->dev, "IPI to remote: %d\n", ipi_to_remote);
+	dev_dbg(&rproc->dev, "IPI from remote: %d\n", ipi_from_remote);
+
+	/* Hand off the CPU to the firmware */
+	dev_dbg(&rproc->dev, "Jumping to firmware at 0x%x\n", rproc->bootaddr);
+
+	write_c0_entryhi(0); /* Set ASID 0 */
+	tlbw_use_hazard();
+
+	/* Firmware protocol */
+	__asm__("addiu $a0, $zero, -3");
+	__asm__("move $a1, %0" :: "r" (ipi_to_remote));
+	__asm__("move $a2, %0" :: "r" (ipi_from_remote));
+	__asm__("move $a3, $zero");
+	__asm__("jr %0" :: "r" (rproc->bootaddr));
+}
+
+
+static irqreturn_t mips_rproc_ipi_handler(int irq, void *dev_id)
+{
+	/* Synthetic interrupts shouldn't need acking */
+	return IRQ_WAKE_THREAD;
+}
+
+
+static irqreturn_t mips_rproc_vq_int(int irq, void *p)
+{
+	struct rproc *rproc = (struct rproc *)p;
+	void *entry;
+	int id;
+
+	/* We don't have a mailbox, so iterate over all vqs and kick them. */
+	idr_for_each_entry(&rproc->notifyids, entry, id)
+		rproc_vq_interrupt(rproc, id);
+
+	return IRQ_HANDLED;
+}
+
+
+/* Helper function to find the IPI domain */
+static struct irq_domain *ipi_domain(void)
+{
+	struct device_node *node = of_irq_find_parent(of_root);
+	struct irq_domain *ipidomain;
+
+	ipidomain = irq_find_matching_host(node, DOMAIN_BUS_IPI);
+	/*
+	 * Some platforms have half DT setup. So if we found irq node but
+	 * didn't find an ipidomain, try to search for one that is not in the
+	 * DT.
+	 */
+	if (node && !ipidomain)
+		ipidomain = irq_find_matching_host(NULL, DOMAIN_BUS_IPI);
+
+	return ipidomain;
+}
+
+
+int mips_rproc_op_start(struct rproc *rproc)
+{
+	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
+	int err;
+	int cpu = mproc->cpu;
+
+	if (mips_rprocs[cpu]) {
+		dev_err(&rproc->dev, "CPU%d in use\n", cpu);
+		return -EBUSY;
+	}
+	mips_rprocs[cpu] = rproc;
+
+	/* Create task for the CPU to use before handing off to firmware */
+	mproc->tsk = fork_idle(cpu);
+	if (IS_ERR(mproc->tsk)) {
+		dev_err(&rproc->dev, "fork_idle() failed for CPU%d\n", cpu);
+		return -ENOMEM;
+	}
+
+	/* We won't be needing the Linux IPIs anymore */
+	if (mips_smp_ipi_free(get_cpu_mask(cpu)))
+		return -EINVAL;
+
+	/*
+	 * Direct IPIs from the remote processor to CPU0 since that can't be
+	 * offlined while the remote CPU is running.
+	 */
+	mproc->ipi_linux = irq_reserve_ipi(ipi_domain(), get_cpu_mask(0));
+	if (!mproc->ipi_linux) {
+		dev_err(&mproc->dev, "Failed to reserve incoming kick\n");
+		goto exit_rproc_nofrom;
+	}
+
+	mproc->ipi_remote = irq_reserve_ipi(ipi_domain(), get_cpu_mask(cpu));
+	if (!mproc->ipi_remote) {
+		dev_err(&mproc->dev, "Failed to reserve outgoing kick\n");
+		goto exit_rproc_noto;
+	}
+
+	/* register incoming ipi */
+	err = devm_request_threaded_irq(&mproc->dev, mproc->ipi_linux,
+					mips_rproc_ipi_handler,
+					mips_rproc_vq_int, 0,
+					"mips-rproc IPI in", mproc->rproc);
+	if (err) {
+		dev_err(&mproc->dev, "Failed to register incoming kick: %d\n",
+			err);
+		goto exit_rproc_noint;
+	}
+
+	if (!mips_cps_steal_cpu_and_execute(cpu, &mips_rproc_cpu_entry,
+						mproc->tsk))
+		return 0;
+
+	dev_err(&mproc->dev, "Failed to steal CPU%d for remote\n", cpu);
+	devm_free_irq(&mproc->dev, mproc->ipi_linux, mproc->rproc);
+exit_rproc_noint:
+	irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(cpu));
+exit_rproc_noto:
+	irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
+exit_rproc_nofrom:
+	free_task(mproc->tsk);
+	mips_rprocs[cpu] = NULL;
+
+	/* Set up the Linux IPIs again */
+	mips_smp_ipi_allocate(get_cpu_mask(cpu));
+	return -EINVAL;
+}
+
+int mips_rproc_op_stop(struct rproc *rproc)
+{
+	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
+
+	if (mproc->ipi_linux)
+		devm_free_irq(&mproc->dev, mproc->ipi_linux, mproc->rproc);
+
+	irq_destroy_ipi(mproc->ipi_linux, get_cpu_mask(0));
+	irq_destroy_ipi(mproc->ipi_remote, get_cpu_mask(mproc->cpu));
+
+	/* Set up the Linux IPIs again */
+	mips_smp_ipi_allocate(get_cpu_mask(mproc->cpu));
+
+	free_task(mproc->tsk);
+
+	mips_rprocs[mproc->cpu] = NULL;
+
+	return mips_cps_halt_and_return_cpu(mproc->cpu);
+}
+
+
+void mips_rproc_op_kick(struct rproc *rproc, int vqid)
+{
+	struct mips_rproc *mproc = *(struct mips_rproc **)rproc->priv;
+
+	ipi_send_single(mproc->ipi_remote, mproc->cpu);
+}
+
+struct rproc_ops mips_rproc_proc_ops = {
+	.start	= mips_rproc_op_start,
+	.stop	= mips_rproc_op_stop,
+	.kick	= mips_rproc_op_kick,
+};
+
+
+static int mips_rproc_probe(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int mips_rproc_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver mips_rproc_driver = {
+	.probe = mips_rproc_probe,
+	.remove = mips_rproc_remove,
+	.driver = {
+		.name = "mips-rproc"
+	},
+};
+
+
+/* Steal a core and run some firmware on it */
+int mips_rproc_start(struct mips_rproc *mproc, const char *firmware, size_t len)
+{
+	int err = -EINVAL;
+	struct mips_rproc **priv;
+
+	/* Duplicate the filename, dropping whitespace from the end via len */
+	mproc->firmware = kstrndup(firmware, len, GFP_KERNEL);
+	if (!mproc->firmware)
+		return -ENOMEM;
+
+	mproc->rproc = rproc_alloc(&mproc->dev, "mips", &mips_rproc_proc_ops,
+				   mproc->firmware,
+				   sizeof(struct mips_rproc *));
+	if (!mproc->rproc)
+		return -ENOMEM;
+
+	priv = mproc->rproc->priv;
+	*priv = mproc;
+
+	/* go live! */
+	err = rproc_add(mproc->rproc);
+	if (err) {
+		dev_err(&mproc->dev, "Failed to add rproc: %d\n", err);
+		rproc_put(mproc->rproc);
+		kfree(mproc->firmware);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Stop a core, and return it to being offline */
+int mips_rproc_stop(struct mips_rproc *mproc)
+{
+	rproc_shutdown(mproc->rproc);
+	rproc_del(mproc->rproc);
+	rproc_put(mproc->rproc);
+	mproc->rproc = NULL;
+	return 0;
+}
+
+/* sysfs interface to mips_rproc_start */
+static ssize_t firmware_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct mips_rproc *mproc = to_mips_rproc(dev);
+	size_t len = count;
+	int err = -EINVAL;
+
+	if (buf[count - 1] == '\n')
+		len--;
+
+	if (!mproc->rproc && len)
+		err = mips_rproc_start(mproc, buf, len);
+	else if (len)
+		err = -EBUSY;
+
+	return err ? err : count;
+}
+static DEVICE_ATTR_WO(firmware);
+
+/* sysfs interface to mips_rproc_stop */
+static ssize_t stop_store(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct mips_rproc *mproc = to_mips_rproc(dev);
+	int err = -EINVAL;
+
+
+	if (mproc->rproc)
+		err = mips_rproc_stop(mproc);
+	else
+		err = -EBUSY;
+
+	return err ? err : count;
+}
+static DEVICE_ATTR_WO(stop);
+
+/* Boiler plate for devclarng mips-rproc sysfs devices */
+static struct attribute *mips_rproc_attrs[] = {
+	&dev_attr_firmware.attr,
+	&dev_attr_stop.attr,
+	NULL
+};
+
+static struct attribute_group mips_rproc_devgroup = {
+	.attrs = mips_rproc_attrs
+};
+
+static const struct attribute_group *mips_rproc_devgroups[] = {
+	&mips_rproc_devgroup,
+	NULL
+};
+
+static char *mips_rproc_devnode(struct device *dev, umode_t *mode)
+{
+	return kasprintf(GFP_KERNEL, "mips-rproc/%s", dev_name(dev));
+}
+
+static struct class mips_rproc_class = {
+	.name		= "mips-rproc",
+	.devnode	= mips_rproc_devnode,
+	.dev_groups	= mips_rproc_devgroups,
+};
+
+static void mips_rproc_release(struct device *dev)
+{
+}
+
+static int mips_rproc_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct mips_rproc *mproc = to_mips_rproc(dev);
+
+	if (!mproc)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct device_type mips_rproc_type = {
+	.release	= mips_rproc_release,
+	.uevent		= mips_rproc_uevent
+};
+
+/* Helper function for locating the device for a CPU */
+int mips_rproc_device_rproc_match(struct device *dev, const void *data)
+{
+	struct mips_rproc *mproc = to_mips_rproc(dev);
+	unsigned int cpu = *(unsigned int *)data;
+
+	return mproc->cpu == cpu;
+}
+
+/* Create a sysfs device in response to CPU down */
+int mips_rproc_device_register(unsigned int cpu)
+{
+	struct mips_rproc *dev;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -EINVAL;
+
+	dev->dev.driver = &mips_rproc_driver.driver;
+	dev->dev.type = &mips_rproc_type;
+	dev->dev.class = &mips_rproc_class;
+	dev->dev.id = cpu;
+	dev_set_name(&dev->dev, "rproc%u", cpu);
+	dev->cpu = cpu;
+
+	return device_register(&dev->dev);
+}
+
+/* Destroy a sysfs device in response to CPU up */
+int mips_rproc_device_unregister(unsigned int cpu)
+{
+	struct device *dev = class_find_device(&mips_rproc_class, NULL, &cpu,
+					       mips_rproc_device_rproc_match);
+	struct mips_rproc *mproc = to_mips_rproc(dev);
+
+	if (mips_rprocs[cpu])
+		mips_rproc_stop(mproc);
+
+	put_device(dev);
+	device_unregister(dev);
+	kfree(mproc);
+	return 0;
+}
+
+/* Intercept CPU hotplug events for syfs purposes */
+static int mips_rproc_callback(struct notifier_block *nfb, unsigned long action,
+			       void *hcpu)
+{
+	unsigned int cpu = (unsigned long)hcpu;
+
+	switch (action) {
+	case CPU_UP_PREPARE:
+	case CPU_DOWN_FAILED:
+		mips_rproc_device_unregister(cpu);
+		break;
+	case CPU_DOWN_PREPARE:
+		mips_rproc_device_register(cpu);
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block mips_rproc_notifier __refdata = {
+	.notifier_call = mips_rproc_callback
+};
+
+static int __init mips_rproc_init(void)
+{
+	int cpu;
+	/* create mips-rproc device class for sysfs */
+	int err = class_register(&mips_rproc_class);
+
+	if (err) {
+		pr_err("mips-proc: unable to register mips-rproc class\n");
+		return err;
+	}
+
+	/* Dynamically create mips-rproc class devices based on hotplug data */
+	get_online_cpus();
+	for_each_possible_cpu(cpu)
+		if (!cpu_online(cpu))
+			mips_rproc_device_register(cpu);
+	register_hotcpu_notifier(&mips_rproc_notifier);
+	put_online_cpus();
+
+	return 0;
+}
+
+static void __exit mips_rproc_exit(void)
+{
+	int cpu;
+	/* Destroy mips-rproc class devices */
+	get_online_cpus();
+	unregister_hotcpu_notifier(&mips_rproc_notifier);
+	for_each_possible_cpu(cpu)
+		if (!cpu_online(cpu))
+			mips_rproc_device_unregister(cpu);
+	put_online_cpus();
+
+	class_unregister(&mips_rproc_class);
+}
+
+subsys_initcall(mips_rproc_init);
+module_exit(mips_rproc_exit);
+
+module_platform_driver(mips_rproc_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MIPS Remote Processor control driver");