mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
67
drivers/remoteproc/Kconfig
Normal file
67
drivers/remoteproc/Kconfig
Normal file
|
@ -0,0 +1,67 @@
|
|||
menu "Remoteproc drivers"
|
||||
|
||||
# REMOTEPROC gets selected by whoever wants it
|
||||
config REMOTEPROC
|
||||
tristate
|
||||
depends on HAS_DMA
|
||||
select CRC32
|
||||
select FW_LOADER
|
||||
select VIRTIO
|
||||
select VIRTUALIZATION
|
||||
|
||||
config OMAP_REMOTEPROC
|
||||
tristate "OMAP remoteproc support"
|
||||
depends on HAS_DMA
|
||||
depends on ARCH_OMAP4 || SOC_OMAP5
|
||||
depends on OMAP_IOMMU
|
||||
select REMOTEPROC
|
||||
select MAILBOX
|
||||
select OMAP2PLUS_MBOX
|
||||
select RPMSG
|
||||
help
|
||||
Say y here to support OMAP's remote processors (dual M3
|
||||
and DSP on OMAP4) via the remote processor framework.
|
||||
|
||||
Currently only supported on OMAP4.
|
||||
|
||||
Usually you want to say y here, in order to enable multimedia
|
||||
use-cases to run on your platform (multimedia codecs are
|
||||
offloaded to remote DSP processors using this framework).
|
||||
|
||||
It's safe to say n here if you're not interested in multimedia
|
||||
offloading or just want a bare minimum kernel.
|
||||
|
||||
config STE_MODEM_RPROC
|
||||
tristate "STE-Modem remoteproc support"
|
||||
depends on HAS_DMA
|
||||
select REMOTEPROC
|
||||
default n
|
||||
help
|
||||
Say y or m here to support STE-Modem shared memory driver.
|
||||
This can be either built-in or a loadable module.
|
||||
If unsure say N.
|
||||
|
||||
config DA8XX_REMOTEPROC
|
||||
tristate "DA8xx/OMAP-L13x remoteproc support"
|
||||
depends on ARCH_DAVINCI_DA8XX
|
||||
select CMA if MMU
|
||||
select REMOTEPROC
|
||||
select RPMSG
|
||||
help
|
||||
Say y here to support DA8xx/OMAP-L13x remote processors via the
|
||||
remote processor framework.
|
||||
|
||||
You want to say y here in order to enable AMP
|
||||
use-cases to run on your platform (multimedia codecs are
|
||||
offloaded to remote DSP processors using this framework).
|
||||
|
||||
This module controls the name of the firmware file that gets
|
||||
loaded on the DSP. This file must reside in the /lib/firmware
|
||||
directory. It can be specified via the module parameter
|
||||
da8xx_fw_name=<filename>, and if not specified will default to
|
||||
"rproc-dsp-fw".
|
||||
|
||||
It's safe to say n here if you're not interested in multimedia
|
||||
offloading.
|
||||
|
||||
endmenu
|
12
drivers/remoteproc/Makefile
Normal file
12
drivers/remoteproc/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Generic framework for controlling remote processors
|
||||
#
|
||||
|
||||
obj-$(CONFIG_REMOTEPROC) += remoteproc.o
|
||||
remoteproc-y := remoteproc_core.o
|
||||
remoteproc-y += remoteproc_debugfs.o
|
||||
remoteproc-y += remoteproc_virtio.o
|
||||
remoteproc-y += remoteproc_elf_loader.o
|
||||
obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
|
||||
obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o
|
||||
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
|
310
drivers/remoteproc/da8xx_remoteproc.c
Normal file
310
drivers/remoteproc/da8xx_remoteproc.c
Normal file
|
@ -0,0 +1,310 @@
|
|||
/*
|
||||
* Remote processor machine-specific module for DA8XX
|
||||
*
|
||||
* Copyright (C) 2013 Texas Instruments, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/remoteproc.h>
|
||||
|
||||
#include <mach/clock.h> /* for davinci_clk_reset_assert/deassert() */
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
static char *da8xx_fw_name;
|
||||
module_param(da8xx_fw_name, charp, S_IRUGO);
|
||||
MODULE_PARM_DESC(da8xx_fw_name,
|
||||
"\n\t\tName of DSP firmware file in /lib/firmware"
|
||||
" (if not specified defaults to 'rproc-dsp-fw')");
|
||||
|
||||
/*
|
||||
* OMAP-L138 Technical References:
|
||||
* http://www.ti.com/product/omap-l138
|
||||
*/
|
||||
#define SYSCFG_CHIPSIG0 BIT(0)
|
||||
#define SYSCFG_CHIPSIG1 BIT(1)
|
||||
#define SYSCFG_CHIPSIG2 BIT(2)
|
||||
#define SYSCFG_CHIPSIG3 BIT(3)
|
||||
#define SYSCFG_CHIPSIG4 BIT(4)
|
||||
|
||||
/**
|
||||
* struct da8xx_rproc - da8xx remote processor instance state
|
||||
* @rproc: rproc handle
|
||||
* @dsp_clk: placeholder for platform's DSP clk
|
||||
* @ack_fxn: chip-specific ack function for ack'ing irq
|
||||
* @irq_data: ack_fxn function parameter
|
||||
* @chipsig: virt ptr to DSP interrupt registers (CHIPSIG & CHIPSIG_CLR)
|
||||
* @bootreg: virt ptr to DSP boot address register (HOST1CFG)
|
||||
* @irq: irq # used by this instance
|
||||
*/
|
||||
struct da8xx_rproc {
|
||||
struct rproc *rproc;
|
||||
struct clk *dsp_clk;
|
||||
void (*ack_fxn)(struct irq_data *data);
|
||||
struct irq_data *irq_data;
|
||||
void __iomem *chipsig;
|
||||
void __iomem *bootreg;
|
||||
int irq;
|
||||
};
|
||||
|
||||
/**
|
||||
* handle_event() - inbound virtqueue message workqueue function
|
||||
*
|
||||
* This function is registered as a kernel thread and is scheduled by the
|
||||
* kernel handler.
|
||||
*/
|
||||
static irqreturn_t handle_event(int irq, void *p)
|
||||
{
|
||||
struct rproc *rproc = (struct rproc *)p;
|
||||
|
||||
/* Process incoming buffers on all our vrings */
|
||||
rproc_vq_interrupt(rproc, 0);
|
||||
rproc_vq_interrupt(rproc, 1);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* da8xx_rproc_callback() - inbound virtqueue message handler
|
||||
*
|
||||
* This handler is invoked directly by the kernel whenever the remote
|
||||
* core (DSP) has modified the state of a virtqueue. There is no
|
||||
* "payload" message indicating the virtqueue index as is the case with
|
||||
* mailbox-based implementations on OMAP4. As such, this handler "polls"
|
||||
* each known virtqueue index for every invocation.
|
||||
*/
|
||||
static irqreturn_t da8xx_rproc_callback(int irq, void *p)
|
||||
{
|
||||
struct rproc *rproc = (struct rproc *)p;
|
||||
struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
|
||||
u32 chipsig;
|
||||
|
||||
chipsig = readl(drproc->chipsig);
|
||||
if (chipsig & SYSCFG_CHIPSIG0) {
|
||||
/* Clear interrupt level source */
|
||||
writel(SYSCFG_CHIPSIG0, drproc->chipsig + 4);
|
||||
|
||||
/*
|
||||
* ACK intr to AINTC.
|
||||
*
|
||||
* It has already been ack'ed by the kernel before calling
|
||||
* this function, but since the ARM<->DSP interrupts in the
|
||||
* CHIPSIG register are "level" instead of "pulse" variety,
|
||||
* we need to ack it after taking down the level else we'll
|
||||
* be called again immediately after returning.
|
||||
*/
|
||||
drproc->ack_fxn(drproc->irq_data);
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int da8xx_rproc_start(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = rproc->dev.parent;
|
||||
struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
|
||||
struct clk *dsp_clk = drproc->dsp_clk;
|
||||
|
||||
/* hw requires the start (boot) address be on 1KB boundary */
|
||||
if (rproc->bootaddr & 0x3ff) {
|
||||
dev_err(dev, "invalid boot address: must be aligned to 1KB\n");
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(rproc->bootaddr, drproc->bootreg);
|
||||
|
||||
clk_enable(dsp_clk);
|
||||
davinci_clk_reset_deassert(dsp_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da8xx_rproc_stop(struct rproc *rproc)
|
||||
{
|
||||
struct da8xx_rproc *drproc = rproc->priv;
|
||||
|
||||
clk_disable(drproc->dsp_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* kick a virtqueue */
|
||||
static void da8xx_rproc_kick(struct rproc *rproc, int vqid)
|
||||
{
|
||||
struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
|
||||
|
||||
/* Interupt remote proc */
|
||||
writel(SYSCFG_CHIPSIG2, drproc->chipsig);
|
||||
}
|
||||
|
||||
static struct rproc_ops da8xx_rproc_ops = {
|
||||
.start = da8xx_rproc_start,
|
||||
.stop = da8xx_rproc_stop,
|
||||
.kick = da8xx_rproc_kick,
|
||||
};
|
||||
|
||||
static int reset_assert(struct device *dev)
|
||||
{
|
||||
struct clk *dsp_clk;
|
||||
|
||||
dsp_clk = clk_get(dev, NULL);
|
||||
if (IS_ERR(dsp_clk)) {
|
||||
dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));
|
||||
return PTR_ERR(dsp_clk);
|
||||
}
|
||||
|
||||
davinci_clk_reset_assert(dsp_clk);
|
||||
clk_put(dsp_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da8xx_rproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct da8xx_rproc *drproc;
|
||||
struct rproc *rproc;
|
||||
struct irq_data *irq_data;
|
||||
struct resource *bootreg_res;
|
||||
struct resource *chipsig_res;
|
||||
struct clk *dsp_clk;
|
||||
void __iomem *chipsig;
|
||||
void __iomem *bootreg;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(dev, "platform_get_irq(pdev, 0) error: %d\n", irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
irq_data = irq_get_irq_data(irq);
|
||||
if (!irq_data) {
|
||||
dev_err(dev, "irq_get_irq_data(%d): NULL\n", irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bootreg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
bootreg = devm_ioremap_resource(dev, bootreg_res);
|
||||
if (IS_ERR(bootreg))
|
||||
return PTR_ERR(bootreg);
|
||||
|
||||
chipsig_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
chipsig = devm_ioremap_resource(dev, chipsig_res);
|
||||
if (IS_ERR(chipsig))
|
||||
return PTR_ERR(chipsig);
|
||||
|
||||
dsp_clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(dsp_clk)) {
|
||||
dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));
|
||||
|
||||
return PTR_ERR(dsp_clk);
|
||||
}
|
||||
|
||||
rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
|
||||
sizeof(*drproc));
|
||||
if (!rproc)
|
||||
return -ENOMEM;
|
||||
|
||||
drproc = rproc->priv;
|
||||
drproc->rproc = rproc;
|
||||
|
||||
platform_set_drvdata(pdev, rproc);
|
||||
|
||||
/* everything the ISR needs is now setup, so hook it up */
|
||||
ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback,
|
||||
handle_event, 0, "da8xx-remoteproc",
|
||||
rproc);
|
||||
if (ret) {
|
||||
dev_err(dev, "devm_request_threaded_irq error: %d\n", ret);
|
||||
goto free_rproc;
|
||||
}
|
||||
|
||||
/*
|
||||
* rproc_add() can end up enabling the DSP's clk with the DSP
|
||||
* *not* in reset, but da8xx_rproc_start() needs the DSP to be
|
||||
* held in reset at the time it is called.
|
||||
*/
|
||||
ret = reset_assert(dev);
|
||||
if (ret)
|
||||
goto free_rproc;
|
||||
|
||||
drproc->chipsig = chipsig;
|
||||
drproc->bootreg = bootreg;
|
||||
drproc->ack_fxn = irq_data->chip->irq_ack;
|
||||
drproc->irq_data = irq_data;
|
||||
drproc->irq = irq;
|
||||
drproc->dsp_clk = dsp_clk;
|
||||
|
||||
ret = rproc_add(rproc);
|
||||
if (ret) {
|
||||
dev_err(dev, "rproc_add failed: %d\n", ret);
|
||||
goto free_rproc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_rproc:
|
||||
rproc_put(rproc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da8xx_rproc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rproc *rproc = platform_get_drvdata(pdev);
|
||||
struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
|
||||
|
||||
/*
|
||||
* It's important to place the DSP in reset before going away,
|
||||
* since a subsequent insmod of this module may enable the DSP's
|
||||
* clock before its program/boot-address has been loaded and
|
||||
* before this module's probe has had a chance to reset the DSP.
|
||||
* Without the reset, the DSP can lockup permanently when it
|
||||
* begins executing garbage.
|
||||
*/
|
||||
reset_assert(dev);
|
||||
|
||||
/*
|
||||
* The devm subsystem might end up releasing things before
|
||||
* freeing the irq, thus allowing an interrupt to sneak in while
|
||||
* the device is being removed. This should prevent that.
|
||||
*/
|
||||
disable_irq(drproc->irq);
|
||||
|
||||
rproc_del(rproc);
|
||||
rproc_put(rproc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver da8xx_rproc_driver = {
|
||||
.probe = da8xx_rproc_probe,
|
||||
.remove = da8xx_rproc_remove,
|
||||
.driver = {
|
||||
.name = "davinci-rproc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(da8xx_rproc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("DA8XX Remote Processor control driver");
|
238
drivers/remoteproc/omap_remoteproc.c
Normal file
238
drivers/remoteproc/omap_remoteproc.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* OMAP Remote Processor driver
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Ohad Ben-Cohen <ohad@wizery.com>
|
||||
* Brian Swetland <swetland@google.com>
|
||||
* Fernando Guzman Lugo <fernando.lugo@ti.com>
|
||||
* Mark Grosen <mgrosen@ti.com>
|
||||
* Suman Anna <s-anna@ti.com>
|
||||
* Hari Kanigeri <h-kanigeri2@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/omap-mailbox.h>
|
||||
|
||||
#include <linux/platform_data/remoteproc-omap.h>
|
||||
|
||||
#include "omap_remoteproc.h"
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
/**
|
||||
* struct omap_rproc - omap remote processor state
|
||||
* @mbox: omap mailbox handle
|
||||
* @nb: notifier block that will be invoked on inbound mailbox messages
|
||||
* @rproc: rproc handle
|
||||
*/
|
||||
struct omap_rproc {
|
||||
struct omap_mbox *mbox;
|
||||
struct notifier_block nb;
|
||||
struct rproc *rproc;
|
||||
};
|
||||
|
||||
/**
|
||||
* omap_rproc_mbox_callback() - inbound mailbox message handler
|
||||
* @this: notifier block
|
||||
* @index: unused
|
||||
* @data: mailbox payload
|
||||
*
|
||||
* This handler is invoked by omap's mailbox driver whenever a mailbox
|
||||
* message is received. Usually, the mailbox payload simply contains
|
||||
* the index of the virtqueue that is kicked by the remote processor,
|
||||
* and we let remoteproc core handle it.
|
||||
*
|
||||
* In addition to virtqueue indices, we also have some out-of-band values
|
||||
* that indicates different events. Those values are deliberately very
|
||||
* big so they don't coincide with virtqueue indices.
|
||||
*/
|
||||
static int omap_rproc_mbox_callback(struct notifier_block *this,
|
||||
unsigned long index, void *data)
|
||||
{
|
||||
mbox_msg_t msg = (mbox_msg_t) data;
|
||||
struct omap_rproc *oproc = container_of(this, struct omap_rproc, nb);
|
||||
struct device *dev = oproc->rproc->dev.parent;
|
||||
const char *name = oproc->rproc->name;
|
||||
|
||||
dev_dbg(dev, "mbox msg: 0x%x\n", msg);
|
||||
|
||||
switch (msg) {
|
||||
case RP_MBOX_CRASH:
|
||||
/* just log this for now. later, we'll also do recovery */
|
||||
dev_err(dev, "omap rproc %s crashed\n", name);
|
||||
break;
|
||||
case RP_MBOX_ECHO_REPLY:
|
||||
dev_info(dev, "received echo reply from %s\n", name);
|
||||
break;
|
||||
default:
|
||||
/* msg contains the index of the triggered vring */
|
||||
if (rproc_vq_interrupt(oproc->rproc, msg) == IRQ_NONE)
|
||||
dev_dbg(dev, "no message was found in vqid %d\n", msg);
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/* kick a virtqueue */
|
||||
static void omap_rproc_kick(struct rproc *rproc, int vqid)
|
||||
{
|
||||
struct omap_rproc *oproc = rproc->priv;
|
||||
struct device *dev = rproc->dev.parent;
|
||||
int ret;
|
||||
|
||||
/* send the index of the triggered virtqueue in the mailbox payload */
|
||||
ret = omap_mbox_msg_send(oproc->mbox, vqid);
|
||||
if (ret)
|
||||
dev_err(dev, "omap_mbox_msg_send failed: %d\n", ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Power up the remote processor.
|
||||
*
|
||||
* This function will be invoked only after the firmware for this rproc
|
||||
* was loaded, parsed successfully, and all of its resource requirements
|
||||
* were met.
|
||||
*/
|
||||
static int omap_rproc_start(struct rproc *rproc)
|
||||
{
|
||||
struct omap_rproc *oproc = rproc->priv;
|
||||
struct device *dev = rproc->dev.parent;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
if (pdata->set_bootaddr)
|
||||
pdata->set_bootaddr(rproc->bootaddr);
|
||||
|
||||
oproc->nb.notifier_call = omap_rproc_mbox_callback;
|
||||
|
||||
/* every omap rproc is assigned a mailbox instance for messaging */
|
||||
oproc->mbox = omap_mbox_get(pdata->mbox_name, &oproc->nb);
|
||||
if (IS_ERR(oproc->mbox)) {
|
||||
ret = PTR_ERR(oproc->mbox);
|
||||
dev_err(dev, "omap_mbox_get failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ping the remote processor. this is only for sanity-sake;
|
||||
* there is no functional effect whatsoever.
|
||||
*
|
||||
* Note that the reply will _not_ arrive immediately: this message
|
||||
* will wait in the mailbox fifo until the remote processor is booted.
|
||||
*/
|
||||
ret = omap_mbox_msg_send(oproc->mbox, RP_MBOX_ECHO_REQUEST);
|
||||
if (ret) {
|
||||
dev_err(dev, "omap_mbox_get failed: %d\n", ret);
|
||||
goto put_mbox;
|
||||
}
|
||||
|
||||
ret = pdata->device_enable(pdev);
|
||||
if (ret) {
|
||||
dev_err(dev, "omap_device_enable failed: %d\n", ret);
|
||||
goto put_mbox;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
put_mbox:
|
||||
omap_mbox_put(oproc->mbox, &oproc->nb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* power off the remote processor */
|
||||
static int omap_rproc_stop(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = rproc->dev.parent;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
|
||||
struct omap_rproc *oproc = rproc->priv;
|
||||
int ret;
|
||||
|
||||
ret = pdata->device_shutdown(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
omap_mbox_put(oproc->mbox, &oproc->nb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rproc_ops omap_rproc_ops = {
|
||||
.start = omap_rproc_start,
|
||||
.stop = omap_rproc_stop,
|
||||
.kick = omap_rproc_kick,
|
||||
};
|
||||
|
||||
static int omap_rproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap_rproc_pdata *pdata = pdev->dev.platform_data;
|
||||
struct omap_rproc *oproc;
|
||||
struct rproc *rproc;
|
||||
int ret;
|
||||
|
||||
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "dma_set_coherent_mask: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rproc = rproc_alloc(&pdev->dev, pdata->name, &omap_rproc_ops,
|
||||
pdata->firmware, sizeof(*oproc));
|
||||
if (!rproc)
|
||||
return -ENOMEM;
|
||||
|
||||
oproc = rproc->priv;
|
||||
oproc->rproc = rproc;
|
||||
|
||||
platform_set_drvdata(pdev, rproc);
|
||||
|
||||
ret = rproc_add(rproc);
|
||||
if (ret)
|
||||
goto free_rproc;
|
||||
|
||||
return 0;
|
||||
|
||||
free_rproc:
|
||||
rproc_put(rproc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_rproc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rproc *rproc = platform_get_drvdata(pdev);
|
||||
|
||||
rproc_del(rproc);
|
||||
rproc_put(rproc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap_rproc_driver = {
|
||||
.probe = omap_rproc_probe,
|
||||
.remove = omap_rproc_remove,
|
||||
.driver = {
|
||||
.name = "omap-rproc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(omap_rproc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("OMAP Remote Processor control driver");
|
69
drivers/remoteproc/omap_remoteproc.h
Normal file
69
drivers/remoteproc/omap_remoteproc.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Remote processor messaging
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name Texas Instruments nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _OMAP_RPMSG_H
|
||||
#define _OMAP_RPMSG_H
|
||||
|
||||
/*
|
||||
* enum - Predefined Mailbox Messages
|
||||
*
|
||||
* @RP_MBOX_READY: informs the M3's that we're up and running. this is
|
||||
* part of the init sequence sent that the M3 expects to see immediately
|
||||
* after it is booted.
|
||||
*
|
||||
* @RP_MBOX_PENDING_MSG: informs the receiver that there is an inbound
|
||||
* message waiting in its own receive-side vring. please note that currently
|
||||
* this message is optional: alternatively, one can explicitly send the index
|
||||
* of the triggered virtqueue itself. the preferred approach will be decided
|
||||
* as we progress and experiment with those two different approaches.
|
||||
*
|
||||
* @RP_MBOX_CRASH: this message is sent if BIOS crashes
|
||||
*
|
||||
* @RP_MBOX_ECHO_REQUEST: a mailbox-level "ping" message.
|
||||
*
|
||||
* @RP_MBOX_ECHO_REPLY: a mailbox-level reply to a "ping"
|
||||
*
|
||||
* @RP_MBOX_ABORT_REQUEST: a "please crash" request, used for testing the
|
||||
* recovery mechanism (to some extent).
|
||||
*/
|
||||
enum omap_rp_mbox_messages {
|
||||
RP_MBOX_READY = 0xFFFFFF00,
|
||||
RP_MBOX_PENDING_MSG = 0xFFFFFF01,
|
||||
RP_MBOX_CRASH = 0xFFFFFF02,
|
||||
RP_MBOX_ECHO_REQUEST = 0xFFFFFF03,
|
||||
RP_MBOX_ECHO_REPLY = 0xFFFFFF04,
|
||||
RP_MBOX_ABORT_REQUEST = 0xFFFFFF05,
|
||||
};
|
||||
|
||||
#endif /* _OMAP_RPMSG_H */
|
1430
drivers/remoteproc/remoteproc_core.c
Normal file
1430
drivers/remoteproc/remoteproc_core.c
Normal file
File diff suppressed because it is too large
Load diff
252
drivers/remoteproc/remoteproc_debugfs.c
Normal file
252
drivers/remoteproc/remoteproc_debugfs.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Remote Processor Framework
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Ohad Ben-Cohen <ohad@wizery.com>
|
||||
* Mark Grosen <mgrosen@ti.com>
|
||||
* Brian Swetland <swetland@google.com>
|
||||
* Fernando Guzman Lugo <fernando.lugo@ti.com>
|
||||
* Suman Anna <s-anna@ti.com>
|
||||
* Robert Tivy <rtivy@ti.com>
|
||||
* Armando Uribe De Leon <x0095078@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
/* remoteproc debugfs parent dir */
|
||||
static struct dentry *rproc_dbg;
|
||||
|
||||
/*
|
||||
* Some remote processors may support dumping trace logs into a shared
|
||||
* memory buffer. We expose this trace buffer using debugfs, so users
|
||||
* can easily tell what's going on remotely.
|
||||
*
|
||||
* We will most probably improve the rproc tracing facilities later on,
|
||||
* but this kind of lightweight and simple mechanism is always good to have,
|
||||
* as it provides very early tracing with little to no dependencies at all.
|
||||
*/
|
||||
static ssize_t rproc_trace_read(struct file *filp, char __user *userbuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc_mem_entry *trace = filp->private_data;
|
||||
int len = strnlen(trace->va, trace->len);
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, trace->va, len);
|
||||
}
|
||||
|
||||
static const struct file_operations trace_rproc_ops = {
|
||||
.read = rproc_trace_read,
|
||||
.open = simple_open,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/*
|
||||
* A state-to-string lookup table, for exposing a human readable state
|
||||
* via debugfs. Always keep in sync with enum rproc_state
|
||||
*/
|
||||
static const char * const rproc_state_string[] = {
|
||||
"offline",
|
||||
"suspended",
|
||||
"running",
|
||||
"crashed",
|
||||
"invalid",
|
||||
};
|
||||
|
||||
/* expose the state of the remote processor via debugfs */
|
||||
static ssize_t rproc_state_read(struct file *filp, char __user *userbuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
unsigned int state;
|
||||
char buf[30];
|
||||
int i;
|
||||
|
||||
state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state;
|
||||
|
||||
i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state],
|
||||
rproc->state);
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
|
||||
}
|
||||
|
||||
static const struct file_operations rproc_state_ops = {
|
||||
.read = rproc_state_read,
|
||||
.open = simple_open,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/* expose the name of the remote processor via debugfs */
|
||||
static ssize_t rproc_name_read(struct file *filp, char __user *userbuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
/* need room for the name, a newline and a terminating null */
|
||||
char buf[100];
|
||||
int i;
|
||||
|
||||
i = scnprintf(buf, sizeof(buf), "%.98s\n", rproc->name);
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, i);
|
||||
}
|
||||
|
||||
static const struct file_operations rproc_name_ops = {
|
||||
.read = rproc_name_read,
|
||||
.open = simple_open,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
/* expose recovery flag via debugfs */
|
||||
static ssize_t rproc_recovery_read(struct file *filp, char __user *userbuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
char *buf = rproc->recovery_disabled ? "disabled\n" : "enabled\n";
|
||||
|
||||
return simple_read_from_buffer(userbuf, count, ppos, buf, strlen(buf));
|
||||
}
|
||||
|
||||
/*
|
||||
* By writing to the 'recovery' debugfs entry, we control the behavior of the
|
||||
* recovery mechanism dynamically. The default value of this entry is "enabled".
|
||||
*
|
||||
* The 'recovery' debugfs entry supports these commands:
|
||||
*
|
||||
* enabled: When enabled, the remote processor will be automatically
|
||||
* recovered whenever it crashes. Moreover, if the remote
|
||||
* processor crashes while recovery is disabled, it will
|
||||
* be automatically recovered too as soon as recovery is enabled.
|
||||
*
|
||||
* disabled: When disabled, a remote processor will remain in a crashed
|
||||
* state if it crashes. This is useful for debugging purposes;
|
||||
* without it, debugging a crash is substantially harder.
|
||||
*
|
||||
* recover: This function will trigger an immediate recovery if the
|
||||
* remote processor is in a crashed state, without changing
|
||||
* or checking the recovery state (enabled/disabled).
|
||||
* This is useful during debugging sessions, when one expects
|
||||
* additional crashes to happen after enabling recovery. In this
|
||||
* case, enabling recovery will make it hard to debug subsequent
|
||||
* crashes, so it's recommended to keep recovery disabled, and
|
||||
* instead use the "recover" command as needed.
|
||||
*/
|
||||
static ssize_t
|
||||
rproc_recovery_write(struct file *filp, const char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rproc *rproc = filp->private_data;
|
||||
char buf[10];
|
||||
int ret;
|
||||
|
||||
if (count > sizeof(buf))
|
||||
return count;
|
||||
|
||||
ret = copy_from_user(buf, user_buf, count);
|
||||
if (ret)
|
||||
return -EFAULT;
|
||||
|
||||
/* remove end of line */
|
||||
if (buf[count - 1] == '\n')
|
||||
buf[count - 1] = '\0';
|
||||
|
||||
if (!strncmp(buf, "enabled", count)) {
|
||||
rproc->recovery_disabled = false;
|
||||
/* if rproc has crashed, trigger recovery */
|
||||
if (rproc->state == RPROC_CRASHED)
|
||||
rproc_trigger_recovery(rproc);
|
||||
} else if (!strncmp(buf, "disabled", count)) {
|
||||
rproc->recovery_disabled = true;
|
||||
} else if (!strncmp(buf, "recover", count)) {
|
||||
/* if rproc has crashed, trigger recovery */
|
||||
if (rproc->state == RPROC_CRASHED)
|
||||
rproc_trigger_recovery(rproc);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations rproc_recovery_ops = {
|
||||
.read = rproc_recovery_read,
|
||||
.write = rproc_recovery_write,
|
||||
.open = simple_open,
|
||||
.llseek = generic_file_llseek,
|
||||
};
|
||||
|
||||
void rproc_remove_trace_file(struct dentry *tfile)
|
||||
{
|
||||
debugfs_remove(tfile);
|
||||
}
|
||||
|
||||
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
|
||||
struct rproc_mem_entry *trace)
|
||||
{
|
||||
struct dentry *tfile;
|
||||
|
||||
tfile = debugfs_create_file(name, 0400, rproc->dbg_dir,
|
||||
trace, &trace_rproc_ops);
|
||||
if (!tfile) {
|
||||
dev_err(&rproc->dev, "failed to create debugfs trace entry\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tfile;
|
||||
}
|
||||
|
||||
void rproc_delete_debug_dir(struct rproc *rproc)
|
||||
{
|
||||
if (!rproc->dbg_dir)
|
||||
return;
|
||||
|
||||
debugfs_remove_recursive(rproc->dbg_dir);
|
||||
}
|
||||
|
||||
void rproc_create_debug_dir(struct rproc *rproc)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
|
||||
if (!rproc_dbg)
|
||||
return;
|
||||
|
||||
rproc->dbg_dir = debugfs_create_dir(dev_name(dev), rproc_dbg);
|
||||
if (!rproc->dbg_dir)
|
||||
return;
|
||||
|
||||
debugfs_create_file("name", 0400, rproc->dbg_dir,
|
||||
rproc, &rproc_name_ops);
|
||||
debugfs_create_file("state", 0400, rproc->dbg_dir,
|
||||
rproc, &rproc_state_ops);
|
||||
debugfs_create_file("recovery", 0400, rproc->dbg_dir,
|
||||
rproc, &rproc_recovery_ops);
|
||||
}
|
||||
|
||||
void __init rproc_init_debugfs(void)
|
||||
{
|
||||
if (debugfs_initialized()) {
|
||||
rproc_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL);
|
||||
if (!rproc_dbg)
|
||||
pr_err("can't create debugfs dir\n");
|
||||
}
|
||||
}
|
||||
|
||||
void __exit rproc_exit_debugfs(void)
|
||||
{
|
||||
debugfs_remove(rproc_dbg);
|
||||
}
|
337
drivers/remoteproc/remoteproc_elf_loader.c
Normal file
337
drivers/remoteproc/remoteproc_elf_loader.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Remote Processor Framework Elf loader
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Ohad Ben-Cohen <ohad@wizery.com>
|
||||
* Brian Swetland <swetland@google.com>
|
||||
* Mark Grosen <mgrosen@ti.com>
|
||||
* Fernando Guzman Lugo <fernando.lugo@ti.com>
|
||||
* Suman Anna <s-anna@ti.com>
|
||||
* Robert Tivy <rtivy@ti.com>
|
||||
* Armando Uribe De Leon <x0095078@ti.com>
|
||||
* Sjur Brændeland <sjur.brandeland@stericsson.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that 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.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/elf.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
/**
|
||||
* rproc_elf_sanity_check() - Sanity Check ELF firmware image
|
||||
* @rproc: the remote processor handle
|
||||
* @fw: the ELF firmware image
|
||||
*
|
||||
* Make sure this fw image is sane.
|
||||
*/
|
||||
static int
|
||||
rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
const char *name = rproc->firmware;
|
||||
struct device *dev = &rproc->dev;
|
||||
struct elf32_hdr *ehdr;
|
||||
char class;
|
||||
|
||||
if (!fw) {
|
||||
dev_err(dev, "failed to load %s\n", name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fw->size < sizeof(struct elf32_hdr)) {
|
||||
dev_err(dev, "Image is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ehdr = (struct elf32_hdr *)fw->data;
|
||||
|
||||
/* We only support ELF32 at this point */
|
||||
class = ehdr->e_ident[EI_CLASS];
|
||||
if (class != ELFCLASS32) {
|
||||
dev_err(dev, "Unsupported class: %d\n", class);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* We assume the firmware has the same endianness as the host */
|
||||
# ifdef __LITTLE_ENDIAN
|
||||
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
|
||||
# else /* BIG ENDIAN */
|
||||
if (ehdr->e_ident[EI_DATA] != ELFDATA2MSB) {
|
||||
# endif
|
||||
dev_err(dev, "Unsupported firmware endianness\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fw->size < ehdr->e_shoff + sizeof(struct elf32_shdr)) {
|
||||
dev_err(dev, "Image is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
|
||||
dev_err(dev, "Image is corrupted (bad magic)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ehdr->e_phnum == 0) {
|
||||
dev_err(dev, "No loadable segments\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ehdr->e_phoff > fw->size) {
|
||||
dev_err(dev, "Firmware size is too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_elf_get_boot_addr() - Get rproc's boot address.
|
||||
* @rproc: the remote processor handle
|
||||
* @fw: the ELF firmware image
|
||||
*
|
||||
* This function returns the entry point address of the ELF
|
||||
* image.
|
||||
*
|
||||
* Note that the boot address is not a configurable property of all remote
|
||||
* processors. Some will always boot at a specific hard-coded address.
|
||||
*/
|
||||
static
|
||||
u32 rproc_elf_get_boot_addr(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data;
|
||||
|
||||
return ehdr->e_entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_elf_load_segments() - load firmware segments to memory
|
||||
* @rproc: remote processor which will be booted using these fw segments
|
||||
* @fw: the ELF firmware image
|
||||
*
|
||||
* This function loads the firmware segments to memory, where the remote
|
||||
* processor expects them.
|
||||
*
|
||||
* Some remote processors will expect their code and data to be placed
|
||||
* in specific device addresses, and can't have them dynamically assigned.
|
||||
*
|
||||
* We currently support only those kind of remote processors, and expect
|
||||
* the program header's paddr member to contain those addresses. We then go
|
||||
* through the physically contiguous "carveout" memory regions which we
|
||||
* allocated (and mapped) earlier on behalf of the remote processor,
|
||||
* and "translate" device address to kernel addresses, so we can copy the
|
||||
* segments where they are expected.
|
||||
*
|
||||
* Currently we only support remote processors that required carveout
|
||||
* allocations and got them mapped onto their iommus. Some processors
|
||||
* might be different: they might not have iommus, and would prefer to
|
||||
* directly allocate memory for every segment/resource. This is not yet
|
||||
* supported, though.
|
||||
*/
|
||||
static int
|
||||
rproc_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct device *dev = &rproc->dev;
|
||||
struct elf32_hdr *ehdr;
|
||||
struct elf32_phdr *phdr;
|
||||
int i, ret = 0;
|
||||
const u8 *elf_data = fw->data;
|
||||
|
||||
ehdr = (struct elf32_hdr *)elf_data;
|
||||
phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
|
||||
|
||||
/* go through the available ELF segments */
|
||||
for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
|
||||
u32 da = phdr->p_paddr;
|
||||
u32 memsz = phdr->p_memsz;
|
||||
u32 filesz = phdr->p_filesz;
|
||||
u32 offset = phdr->p_offset;
|
||||
void *ptr;
|
||||
|
||||
if (phdr->p_type != PT_LOAD)
|
||||
continue;
|
||||
|
||||
dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
|
||||
phdr->p_type, da, memsz, filesz);
|
||||
|
||||
if (filesz > memsz) {
|
||||
dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
|
||||
filesz, memsz);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset + filesz > fw->size) {
|
||||
dev_err(dev, "truncated fw: need 0x%x avail 0x%zx\n",
|
||||
offset + filesz, fw->size);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* grab the kernel address for this device address */
|
||||
ptr = rproc_da_to_va(rproc, da, memsz);
|
||||
if (!ptr) {
|
||||
dev_err(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* put the segment where the remote processor expects it */
|
||||
if (phdr->p_filesz)
|
||||
memcpy(ptr, elf_data + phdr->p_offset, filesz);
|
||||
|
||||
/*
|
||||
* Zero out remaining memory for this segment.
|
||||
*
|
||||
* This isn't strictly required since dma_alloc_coherent already
|
||||
* did this for us. albeit harmless, we may consider removing
|
||||
* this.
|
||||
*/
|
||||
if (memsz > filesz)
|
||||
memset(ptr + filesz, 0, memsz - filesz);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct elf32_shdr *
|
||||
find_table(struct device *dev, struct elf32_hdr *ehdr, size_t fw_size)
|
||||
{
|
||||
struct elf32_shdr *shdr;
|
||||
int i;
|
||||
const char *name_table;
|
||||
struct resource_table *table = NULL;
|
||||
const u8 *elf_data = (void *)ehdr;
|
||||
|
||||
/* look for the resource table and handle it */
|
||||
shdr = (struct elf32_shdr *)(elf_data + ehdr->e_shoff);
|
||||
name_table = elf_data + shdr[ehdr->e_shstrndx].sh_offset;
|
||||
|
||||
for (i = 0; i < ehdr->e_shnum; i++, shdr++) {
|
||||
u32 size = shdr->sh_size;
|
||||
u32 offset = shdr->sh_offset;
|
||||
|
||||
if (strcmp(name_table + shdr->sh_name, ".resource_table"))
|
||||
continue;
|
||||
|
||||
table = (struct resource_table *)(elf_data + offset);
|
||||
|
||||
/* make sure we have the entire table */
|
||||
if (offset + size > fw_size || offset + size < size) {
|
||||
dev_err(dev, "resource table truncated\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure table has at least the header */
|
||||
if (sizeof(struct resource_table) > size) {
|
||||
dev_err(dev, "header-less resource table\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we don't support any version beyond the first */
|
||||
if (table->ver != 1) {
|
||||
dev_err(dev, "unsupported fw ver: %d\n", table->ver);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure reserved bytes are zeroes */
|
||||
if (table->reserved[0] || table->reserved[1]) {
|
||||
dev_err(dev, "non zero reserved bytes\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure the offsets array isn't truncated */
|
||||
if (table->num * sizeof(table->offset[0]) +
|
||||
sizeof(struct resource_table) > size) {
|
||||
dev_err(dev, "resource table incomplete\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return shdr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_elf_find_rsc_table() - find the resource table
|
||||
* @rproc: the rproc handle
|
||||
* @fw: the ELF firmware image
|
||||
* @tablesz: place holder for providing back the table size
|
||||
*
|
||||
* This function finds the resource table inside the remote processor's
|
||||
* firmware. It is used both upon the registration of @rproc (in order
|
||||
* to look for and register the supported virito devices), and when the
|
||||
* @rproc is booted.
|
||||
*
|
||||
* Returns the pointer to the resource table if it is found, and write its
|
||||
* size into @tablesz. If a valid table isn't found, NULL is returned
|
||||
* (and @tablesz isn't set).
|
||||
*/
|
||||
static struct resource_table *
|
||||
rproc_elf_find_rsc_table(struct rproc *rproc, const struct firmware *fw,
|
||||
int *tablesz)
|
||||
{
|
||||
struct elf32_hdr *ehdr;
|
||||
struct elf32_shdr *shdr;
|
||||
struct device *dev = &rproc->dev;
|
||||
struct resource_table *table = NULL;
|
||||
const u8 *elf_data = fw->data;
|
||||
|
||||
ehdr = (struct elf32_hdr *)elf_data;
|
||||
|
||||
shdr = find_table(dev, ehdr, fw->size);
|
||||
if (!shdr)
|
||||
return NULL;
|
||||
|
||||
table = (struct resource_table *)(elf_data + shdr->sh_offset);
|
||||
*tablesz = shdr->sh_size;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_elf_find_loaded_rsc_table() - find the loaded resource table
|
||||
* @rproc: the rproc handle
|
||||
* @fw: the ELF firmware image
|
||||
*
|
||||
* This function finds the location of the loaded resource table. Don't
|
||||
* call this function if the table wasn't loaded yet - it's a bug if you do.
|
||||
*
|
||||
* Returns the pointer to the resource table if it is found or NULL otherwise.
|
||||
* If the table wasn't loaded yet the result is unspecified.
|
||||
*/
|
||||
static struct resource_table *
|
||||
rproc_elf_find_loaded_rsc_table(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct elf32_hdr *ehdr = (struct elf32_hdr *)fw->data;
|
||||
struct elf32_shdr *shdr;
|
||||
|
||||
shdr = find_table(&rproc->dev, ehdr, fw->size);
|
||||
if (!shdr)
|
||||
return NULL;
|
||||
|
||||
return rproc_da_to_va(rproc, shdr->sh_addr, shdr->sh_size);
|
||||
}
|
||||
|
||||
const struct rproc_fw_ops rproc_elf_fw_ops = {
|
||||
.load = rproc_elf_load_segments,
|
||||
.find_rsc_table = rproc_elf_find_rsc_table,
|
||||
.find_loaded_rsc_table = rproc_elf_find_loaded_rsc_table,
|
||||
.sanity_check = rproc_elf_sanity_check,
|
||||
.get_boot_addr = rproc_elf_get_boot_addr
|
||||
};
|
120
drivers/remoteproc/remoteproc_internal.h
Normal file
120
drivers/remoteproc/remoteproc_internal.h
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Remote processor framework
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Ohad Ben-Cohen <ohad@wizery.com>
|
||||
* Brian Swetland <swetland@google.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that 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 REMOTEPROC_INTERNAL_H
|
||||
#define REMOTEPROC_INTERNAL_H
|
||||
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/firmware.h>
|
||||
|
||||
struct rproc;
|
||||
|
||||
/**
|
||||
* struct rproc_fw_ops - firmware format specific operations.
|
||||
* @find_rsc_table: find the resource table inside the firmware image
|
||||
* @find_loaded_rsc_table: find the loaded resouce table
|
||||
* @load: load firmeware to memory, where the remote processor
|
||||
* expects to find it
|
||||
* @sanity_check: sanity check the fw image
|
||||
* @get_boot_addr: get boot address to entry point specified in firmware
|
||||
*/
|
||||
struct rproc_fw_ops {
|
||||
struct resource_table *(*find_rsc_table) (struct rproc *rproc,
|
||||
const struct firmware *fw,
|
||||
int *tablesz);
|
||||
struct resource_table *(*find_loaded_rsc_table)(struct rproc *rproc,
|
||||
const struct firmware *fw);
|
||||
int (*load)(struct rproc *rproc, const struct firmware *fw);
|
||||
int (*sanity_check)(struct rproc *rproc, const struct firmware *fw);
|
||||
u32 (*get_boot_addr)(struct rproc *rproc, const struct firmware *fw);
|
||||
};
|
||||
|
||||
/* from remoteproc_core.c */
|
||||
void rproc_release(struct kref *kref);
|
||||
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id);
|
||||
|
||||
/* from remoteproc_virtio.c */
|
||||
int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id);
|
||||
void rproc_remove_virtio_dev(struct rproc_vdev *rvdev);
|
||||
|
||||
/* from remoteproc_debugfs.c */
|
||||
void rproc_remove_trace_file(struct dentry *tfile);
|
||||
struct dentry *rproc_create_trace_file(const char *name, struct rproc *rproc,
|
||||
struct rproc_mem_entry *trace);
|
||||
void rproc_delete_debug_dir(struct rproc *rproc);
|
||||
void rproc_create_debug_dir(struct rproc *rproc);
|
||||
void rproc_init_debugfs(void);
|
||||
void rproc_exit_debugfs(void);
|
||||
|
||||
void rproc_free_vring(struct rproc_vring *rvring);
|
||||
int rproc_alloc_vring(struct rproc_vdev *rvdev, int i);
|
||||
|
||||
void *rproc_da_to_va(struct rproc *rproc, u64 da, int len);
|
||||
int rproc_trigger_recovery(struct rproc *rproc);
|
||||
|
||||
static inline
|
||||
int rproc_fw_sanity_check(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
if (rproc->fw_ops->sanity_check)
|
||||
return rproc->fw_ops->sanity_check(rproc, fw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
u32 rproc_get_boot_addr(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
if (rproc->fw_ops->get_boot_addr)
|
||||
return rproc->fw_ops->get_boot_addr(rproc, fw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline
|
||||
int rproc_load_segments(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
if (rproc->fw_ops->load)
|
||||
return rproc->fw_ops->load(rproc, fw);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline
|
||||
struct resource_table *rproc_find_rsc_table(struct rproc *rproc,
|
||||
const struct firmware *fw, int *tablesz)
|
||||
{
|
||||
if (rproc->fw_ops->find_rsc_table)
|
||||
return rproc->fw_ops->find_rsc_table(rproc, fw, tablesz);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline
|
||||
struct resource_table *rproc_find_loaded_rsc_table(struct rproc *rproc,
|
||||
const struct firmware *fw)
|
||||
{
|
||||
if (rproc->fw_ops->find_loaded_rsc_table)
|
||||
return rproc->fw_ops->find_loaded_rsc_table(rproc, fw);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
extern const struct rproc_fw_ops rproc_elf_fw_ops;
|
||||
|
||||
#endif /* REMOTEPROC_INTERNAL_H */
|
358
drivers/remoteproc/remoteproc_virtio.c
Normal file
358
drivers/remoteproc/remoteproc_virtio.c
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* Remote processor messaging transport (OMAP platform-specific bits)
|
||||
*
|
||||
* Copyright (C) 2011 Texas Instruments, Inc.
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Ohad Ben-Cohen <ohad@wizery.com>
|
||||
* Brian Swetland <swetland@google.com>
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* This program is distributed in the hope that 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.
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <linux/virtio_config.h>
|
||||
#include <linux/virtio_ids.h>
|
||||
#include <linux/virtio_ring.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
/* kick the remote processor, and let it know which virtqueue to poke at */
|
||||
static bool rproc_virtio_notify(struct virtqueue *vq)
|
||||
{
|
||||
struct rproc_vring *rvring = vq->priv;
|
||||
struct rproc *rproc = rvring->rvdev->rproc;
|
||||
int notifyid = rvring->notifyid;
|
||||
|
||||
dev_dbg(&rproc->dev, "kicking vq index: %d\n", notifyid);
|
||||
|
||||
rproc->ops->kick(rproc, notifyid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_vq_interrupt() - tell remoteproc that a virtqueue is interrupted
|
||||
* @rproc: handle to the remote processor
|
||||
* @notifyid: index of the signalled virtqueue (unique per this @rproc)
|
||||
*
|
||||
* This function should be called by the platform-specific rproc driver,
|
||||
* when the remote processor signals that a specific virtqueue has pending
|
||||
* messages available.
|
||||
*
|
||||
* Returns IRQ_NONE if no message was found in the @notifyid virtqueue,
|
||||
* and otherwise returns IRQ_HANDLED.
|
||||
*/
|
||||
irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int notifyid)
|
||||
{
|
||||
struct rproc_vring *rvring;
|
||||
|
||||
dev_dbg(&rproc->dev, "vq index %d is interrupted\n", notifyid);
|
||||
|
||||
rvring = idr_find(&rproc->notifyids, notifyid);
|
||||
if (!rvring || !rvring->vq)
|
||||
return IRQ_NONE;
|
||||
|
||||
return vring_interrupt(0, rvring->vq);
|
||||
}
|
||||
EXPORT_SYMBOL(rproc_vq_interrupt);
|
||||
|
||||
static struct virtqueue *rp_find_vq(struct virtio_device *vdev,
|
||||
unsigned id,
|
||||
void (*callback)(struct virtqueue *vq),
|
||||
const char *name)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
struct device *dev = &rproc->dev;
|
||||
struct rproc_vring *rvring;
|
||||
struct virtqueue *vq;
|
||||
void *addr;
|
||||
int len, size, ret;
|
||||
|
||||
/* we're temporarily limited to two virtqueues per rvdev */
|
||||
if (id >= ARRAY_SIZE(rvdev->vring))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
if (!name)
|
||||
return NULL;
|
||||
|
||||
ret = rproc_alloc_vring(rvdev, id);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
rvring = &rvdev->vring[id];
|
||||
addr = rvring->va;
|
||||
len = rvring->len;
|
||||
|
||||
/* zero vring */
|
||||
size = vring_size(len, rvring->align);
|
||||
memset(addr, 0, size);
|
||||
|
||||
dev_dbg(dev, "vring%d: va %p qsz %d notifyid %d\n",
|
||||
id, addr, len, rvring->notifyid);
|
||||
|
||||
/*
|
||||
* Create the new vq, and tell virtio we're not interested in
|
||||
* the 'weak' smp barriers, since we're talking with a real device.
|
||||
*/
|
||||
vq = vring_new_virtqueue(id, len, rvring->align, vdev, false, addr,
|
||||
rproc_virtio_notify, callback, name);
|
||||
if (!vq) {
|
||||
dev_err(dev, "vring_new_virtqueue %s failed\n", name);
|
||||
rproc_free_vring(rvring);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
rvring->vq = vq;
|
||||
vq->priv = rvring;
|
||||
|
||||
return vq;
|
||||
}
|
||||
|
||||
static void __rproc_virtio_del_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
struct virtqueue *vq, *n;
|
||||
struct rproc_vring *rvring;
|
||||
|
||||
list_for_each_entry_safe(vq, n, &vdev->vqs, list) {
|
||||
rvring = vq->priv;
|
||||
rvring->vq = NULL;
|
||||
vring_del_virtqueue(vq);
|
||||
rproc_free_vring(rvring);
|
||||
}
|
||||
}
|
||||
|
||||
static void rproc_virtio_del_vqs(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
|
||||
/* power down the remote processor before deleting vqs */
|
||||
rproc_shutdown(rproc);
|
||||
|
||||
__rproc_virtio_del_vqs(vdev);
|
||||
}
|
||||
|
||||
static int rproc_virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
|
||||
struct virtqueue *vqs[],
|
||||
vq_callback_t *callbacks[],
|
||||
const char *names[])
|
||||
{
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < nvqs; ++i) {
|
||||
vqs[i] = rp_find_vq(vdev, i, callbacks[i], names[i]);
|
||||
if (IS_ERR(vqs[i])) {
|
||||
ret = PTR_ERR(vqs[i]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* now that the vqs are all set, boot the remote processor */
|
||||
ret = rproc_boot(rproc);
|
||||
if (ret) {
|
||||
dev_err(&rproc->dev, "rproc_boot() failed %d\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
__rproc_virtio_del_vqs(vdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u8 rproc_virtio_get_status(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
|
||||
return rsc->status;
|
||||
}
|
||||
|
||||
static void rproc_virtio_set_status(struct virtio_device *vdev, u8 status)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
|
||||
rsc->status = status;
|
||||
dev_dbg(&vdev->dev, "status: %d\n", status);
|
||||
}
|
||||
|
||||
static void rproc_virtio_reset(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
|
||||
rsc->status = 0;
|
||||
dev_dbg(&vdev->dev, "reset !\n");
|
||||
}
|
||||
|
||||
/* provide the vdev features as retrieved from the firmware */
|
||||
static u32 rproc_virtio_get_features(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
|
||||
return rsc->dfeatures;
|
||||
}
|
||||
|
||||
static void rproc_virtio_finalize_features(struct virtio_device *vdev)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
|
||||
/* Give virtio_ring a chance to accept features */
|
||||
vring_transport_features(vdev);
|
||||
|
||||
/*
|
||||
* Remember the finalized features of our vdev, and provide it
|
||||
* to the remote processor once it is powered on.
|
||||
*/
|
||||
rsc->gfeatures = vdev->features[0];
|
||||
}
|
||||
|
||||
static void rproc_virtio_get(struct virtio_device *vdev, unsigned offset,
|
||||
void *buf, unsigned len)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
void *cfg;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
cfg = &rsc->vring[rsc->num_of_vrings];
|
||||
|
||||
if (offset + len > rsc->config_len || offset + len < len) {
|
||||
dev_err(&vdev->dev, "rproc_virtio_get: access out of bounds\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(buf, cfg + offset, len);
|
||||
}
|
||||
|
||||
static void rproc_virtio_set(struct virtio_device *vdev, unsigned offset,
|
||||
const void *buf, unsigned len)
|
||||
{
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct fw_rsc_vdev *rsc;
|
||||
void *cfg;
|
||||
|
||||
rsc = (void *)rvdev->rproc->table_ptr + rvdev->rsc_offset;
|
||||
cfg = &rsc->vring[rsc->num_of_vrings];
|
||||
|
||||
if (offset + len > rsc->config_len || offset + len < len) {
|
||||
dev_err(&vdev->dev, "rproc_virtio_set: access out of bounds\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(cfg + offset, buf, len);
|
||||
}
|
||||
|
||||
static const struct virtio_config_ops rproc_virtio_config_ops = {
|
||||
.get_features = rproc_virtio_get_features,
|
||||
.finalize_features = rproc_virtio_finalize_features,
|
||||
.find_vqs = rproc_virtio_find_vqs,
|
||||
.del_vqs = rproc_virtio_del_vqs,
|
||||
.reset = rproc_virtio_reset,
|
||||
.set_status = rproc_virtio_set_status,
|
||||
.get_status = rproc_virtio_get_status,
|
||||
.get = rproc_virtio_get,
|
||||
.set = rproc_virtio_set,
|
||||
};
|
||||
|
||||
/*
|
||||
* This function is called whenever vdev is released, and is responsible
|
||||
* to decrement the remote processor's refcount which was taken when vdev was
|
||||
* added.
|
||||
*
|
||||
* Never call this function directly; it will be called by the driver
|
||||
* core when needed.
|
||||
*/
|
||||
static void rproc_vdev_release(struct device *dev)
|
||||
{
|
||||
struct virtio_device *vdev = dev_to_virtio(dev);
|
||||
struct rproc_vdev *rvdev = vdev_to_rvdev(vdev);
|
||||
struct rproc *rproc = vdev_to_rproc(vdev);
|
||||
|
||||
list_del(&rvdev->node);
|
||||
kfree(rvdev);
|
||||
|
||||
put_device(&rproc->dev);
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_add_virtio_dev() - register an rproc-induced virtio device
|
||||
* @rvdev: the remote vdev
|
||||
*
|
||||
* This function registers a virtio device. This vdev's partent is
|
||||
* the rproc device.
|
||||
*
|
||||
* Returns 0 on success or an appropriate error value otherwise.
|
||||
*/
|
||||
int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id)
|
||||
{
|
||||
struct rproc *rproc = rvdev->rproc;
|
||||
struct device *dev = &rproc->dev;
|
||||
struct virtio_device *vdev = &rvdev->vdev;
|
||||
int ret;
|
||||
|
||||
vdev->id.device = id,
|
||||
vdev->config = &rproc_virtio_config_ops,
|
||||
vdev->dev.parent = dev;
|
||||
vdev->dev.release = rproc_vdev_release;
|
||||
|
||||
/*
|
||||
* We're indirectly making a non-temporary copy of the rproc pointer
|
||||
* here, because drivers probed with this vdev will indirectly
|
||||
* access the wrapping rproc.
|
||||
*
|
||||
* Therefore we must increment the rproc refcount here, and decrement
|
||||
* it _only_ when the vdev is released.
|
||||
*/
|
||||
get_device(&rproc->dev);
|
||||
|
||||
ret = register_virtio_device(vdev);
|
||||
if (ret) {
|
||||
put_device(&rproc->dev);
|
||||
dev_err(dev, "failed to register vdev: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_info(dev, "registered %s (type %d)\n", dev_name(&vdev->dev), id);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* rproc_remove_virtio_dev() - remove an rproc-induced virtio device
|
||||
* @rvdev: the remote vdev
|
||||
*
|
||||
* This function unregisters an existing virtio device.
|
||||
*/
|
||||
void rproc_remove_virtio_dev(struct rproc_vdev *rvdev)
|
||||
{
|
||||
unregister_virtio_device(&rvdev->vdev);
|
||||
}
|
342
drivers/remoteproc/ste_modem_rproc.c
Normal file
342
drivers/remoteproc/ste_modem_rproc.c
Normal file
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* Copyright (C) ST-Ericsson AB 2012
|
||||
* Author: Sjur Brændeland <sjur.brandeland@stericsson.com>
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/remoteproc.h>
|
||||
#include <linux/ste_modem_shm.h>
|
||||
#include "remoteproc_internal.h"
|
||||
|
||||
#define SPROC_FW_SIZE (50 * 4096)
|
||||
#define SPROC_MAX_TOC_ENTRIES 32
|
||||
#define SPROC_MAX_NOTIFY_ID 14
|
||||
#define SPROC_RESOURCE_NAME "rsc-table"
|
||||
#define SPROC_MODEM_NAME "ste-modem"
|
||||
#define SPROC_MODEM_FIRMWARE SPROC_MODEM_NAME "-fw.bin"
|
||||
|
||||
#define sproc_dbg(sproc, fmt, ...) \
|
||||
dev_dbg(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
|
||||
#define sproc_err(sproc, fmt, ...) \
|
||||
dev_err(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__)
|
||||
|
||||
/* STE-modem control structure */
|
||||
struct sproc {
|
||||
struct rproc *rproc;
|
||||
struct ste_modem_device *mdev;
|
||||
int error;
|
||||
void *fw_addr;
|
||||
size_t fw_size;
|
||||
dma_addr_t fw_dma_addr;
|
||||
};
|
||||
|
||||
/* STE-Modem firmware entry */
|
||||
struct ste_toc_entry {
|
||||
__le32 start;
|
||||
__le32 size;
|
||||
__le32 flags;
|
||||
__le32 entry_point;
|
||||
__le32 load_addr;
|
||||
char name[12];
|
||||
};
|
||||
|
||||
/*
|
||||
* The Table Of Content is located at the start of the firmware image and
|
||||
* at offset zero in the shared memory region. The resource table typically
|
||||
* contains the initial boot image (boot strap) and other information elements
|
||||
* such as remoteproc resource table. Each entry is identified by a unique
|
||||
* name.
|
||||
*/
|
||||
struct ste_toc {
|
||||
struct ste_toc_entry table[SPROC_MAX_TOC_ENTRIES];
|
||||
};
|
||||
|
||||
/* Loads the firmware to shared memory. */
|
||||
static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
|
||||
memcpy(sproc->fw_addr, fw->data, fw->size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the entry for resource table in the Table of Content */
|
||||
static const struct ste_toc_entry *sproc_find_rsc_entry(const void *data)
|
||||
{
|
||||
int i;
|
||||
const struct ste_toc *toc;
|
||||
toc = data;
|
||||
|
||||
/* Search the table for the resource table */
|
||||
for (i = 0; i < SPROC_MAX_TOC_ENTRIES &&
|
||||
toc->table[i].start != 0xffffffff; i++) {
|
||||
if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME,
|
||||
sizeof(toc->table[i].name)))
|
||||
return &toc->table[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find the resource table inside the remote processor's firmware. */
|
||||
static struct resource_table *
|
||||
sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw,
|
||||
int *tablesz)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
struct resource_table *table;
|
||||
const struct ste_toc_entry *entry;
|
||||
|
||||
if (!fw)
|
||||
return NULL;
|
||||
|
||||
entry = sproc_find_rsc_entry(fw->data);
|
||||
if (!entry) {
|
||||
sproc_err(sproc, "resource table not found in fw\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table = (void *)(fw->data + entry->start);
|
||||
|
||||
/* sanity check size and offset of resource table */
|
||||
if (entry->start > SPROC_FW_SIZE ||
|
||||
entry->size > SPROC_FW_SIZE ||
|
||||
fw->size > SPROC_FW_SIZE ||
|
||||
entry->start + entry->size > fw->size ||
|
||||
sizeof(struct resource_table) > entry->size) {
|
||||
sproc_err(sproc, "bad size of fw or resource table\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* we don't support any version beyond the first */
|
||||
if (table->ver != 1) {
|
||||
sproc_err(sproc, "unsupported fw ver: %d\n", table->ver);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure reserved bytes are zeroes */
|
||||
if (table->reserved[0] || table->reserved[1]) {
|
||||
sproc_err(sproc, "non zero reserved bytes\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* make sure the offsets array isn't truncated */
|
||||
if (table->num > SPROC_MAX_TOC_ENTRIES ||
|
||||
table->num * sizeof(table->offset[0]) +
|
||||
sizeof(struct resource_table) > entry->size) {
|
||||
sproc_err(sproc, "resource table incomplete\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If the fw size has grown, release the previous fw allocation */
|
||||
if (SPROC_FW_SIZE < fw->size) {
|
||||
sproc_err(sproc, "Insufficient space for fw (%d < %zd)\n",
|
||||
SPROC_FW_SIZE, fw->size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sproc->fw_size = fw->size;
|
||||
*tablesz = entry->size;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/* Find the resource table inside the remote processor's firmware. */
|
||||
static struct resource_table *
|
||||
sproc_find_loaded_rsc_table(struct rproc *rproc, const struct firmware *fw)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
const struct ste_toc_entry *entry;
|
||||
|
||||
if (!fw || !sproc->fw_addr)
|
||||
return NULL;
|
||||
|
||||
entry = sproc_find_rsc_entry(sproc->fw_addr);
|
||||
if (!entry) {
|
||||
sproc_err(sproc, "resource table not found in fw\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sproc->fw_addr + entry->start;
|
||||
}
|
||||
|
||||
/* STE modem firmware handler operations */
|
||||
static const struct rproc_fw_ops sproc_fw_ops = {
|
||||
.load = sproc_load_segments,
|
||||
.find_rsc_table = sproc_find_rsc_table,
|
||||
.find_loaded_rsc_table = sproc_find_loaded_rsc_table,
|
||||
};
|
||||
|
||||
/* Kick the modem with specified notification id */
|
||||
static void sproc_kick(struct rproc *rproc, int vqid)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
|
||||
sproc_dbg(sproc, "kick vqid:%d\n", vqid);
|
||||
|
||||
/*
|
||||
* We need different notification IDs for RX and TX so add
|
||||
* an offset on TX notification IDs.
|
||||
*/
|
||||
sproc->mdev->ops.kick(sproc->mdev, vqid + SPROC_MAX_NOTIFY_ID);
|
||||
}
|
||||
|
||||
/* Received a kick from a modem, kick the virtqueue */
|
||||
static void sproc_kick_callback(struct ste_modem_device *mdev, int vqid)
|
||||
{
|
||||
struct sproc *sproc = mdev->drv_data;
|
||||
|
||||
if (rproc_vq_interrupt(sproc->rproc, vqid) == IRQ_NONE)
|
||||
sproc_dbg(sproc, "no message was found in vqid %d\n", vqid);
|
||||
}
|
||||
|
||||
static struct ste_modem_dev_cb sproc_dev_cb = {
|
||||
.kick = sproc_kick_callback,
|
||||
};
|
||||
|
||||
/* Start the STE modem */
|
||||
static int sproc_start(struct rproc *rproc)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
int i, err;
|
||||
|
||||
sproc_dbg(sproc, "start ste-modem\n");
|
||||
|
||||
/* Sanity test the max_notifyid */
|
||||
if (rproc->max_notifyid > SPROC_MAX_NOTIFY_ID) {
|
||||
sproc_err(sproc, "Notification IDs too high:%d\n",
|
||||
rproc->max_notifyid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Subscribe to notifications */
|
||||
for (i = 0; i <= rproc->max_notifyid; i++) {
|
||||
err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i);
|
||||
if (err) {
|
||||
sproc_err(sproc,
|
||||
"subscription of kicks failed:%d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Request modem start-up*/
|
||||
return sproc->mdev->ops.power(sproc->mdev, true);
|
||||
}
|
||||
|
||||
/* Stop the STE modem */
|
||||
static int sproc_stop(struct rproc *rproc)
|
||||
{
|
||||
struct sproc *sproc = rproc->priv;
|
||||
sproc_dbg(sproc, "stop ste-modem\n");
|
||||
|
||||
return sproc->mdev->ops.power(sproc->mdev, false);
|
||||
}
|
||||
|
||||
static struct rproc_ops sproc_ops = {
|
||||
.start = sproc_start,
|
||||
.stop = sproc_stop,
|
||||
.kick = sproc_kick,
|
||||
};
|
||||
|
||||
/* STE modem device is unregistered */
|
||||
static int sproc_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ste_modem_device *mdev =
|
||||
container_of(pdev, struct ste_modem_device, pdev);
|
||||
struct sproc *sproc = mdev->drv_data;
|
||||
|
||||
sproc_dbg(sproc, "remove ste-modem\n");
|
||||
|
||||
/* Reset device callback functions */
|
||||
sproc->mdev->ops.setup(sproc->mdev, NULL);
|
||||
|
||||
/* Unregister as remoteproc device */
|
||||
rproc_del(sproc->rproc);
|
||||
dma_free_coherent(sproc->rproc->dev.parent, SPROC_FW_SIZE,
|
||||
sproc->fw_addr, sproc->fw_dma_addr);
|
||||
rproc_put(sproc->rproc);
|
||||
|
||||
mdev->drv_data = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle probe of a modem device */
|
||||
static int sproc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ste_modem_device *mdev =
|
||||
container_of(pdev, struct ste_modem_device, pdev);
|
||||
struct sproc *sproc;
|
||||
struct rproc *rproc;
|
||||
int err;
|
||||
|
||||
dev_dbg(&mdev->pdev.dev, "probe ste-modem\n");
|
||||
|
||||
if (!mdev->ops.setup || !mdev->ops.kick || !mdev->ops.kick_subscribe ||
|
||||
!mdev->ops.power) {
|
||||
dev_err(&mdev->pdev.dev, "invalid mdev ops\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rproc = rproc_alloc(&mdev->pdev.dev, mdev->pdev.name, &sproc_ops,
|
||||
SPROC_MODEM_FIRMWARE, sizeof(*sproc));
|
||||
if (!rproc)
|
||||
return -ENOMEM;
|
||||
|
||||
sproc = rproc->priv;
|
||||
sproc->mdev = mdev;
|
||||
sproc->rproc = rproc;
|
||||
mdev->drv_data = sproc;
|
||||
|
||||
/* Provide callback functions to modem device */
|
||||
sproc->mdev->ops.setup(sproc->mdev, &sproc_dev_cb);
|
||||
|
||||
/* Set the STE-modem specific firmware handler */
|
||||
rproc->fw_ops = &sproc_fw_ops;
|
||||
|
||||
/*
|
||||
* STE-modem requires the firmware to be located
|
||||
* at the start of the shared memory region. So we need to
|
||||
* reserve space for firmware at the start.
|
||||
*/
|
||||
sproc->fw_addr = dma_alloc_coherent(rproc->dev.parent, SPROC_FW_SIZE,
|
||||
&sproc->fw_dma_addr,
|
||||
GFP_KERNEL);
|
||||
if (!sproc->fw_addr) {
|
||||
sproc_err(sproc, "Cannot allocate memory for fw\n");
|
||||
err = -ENOMEM;
|
||||
goto free_rproc;
|
||||
}
|
||||
|
||||
/* Register as a remoteproc device */
|
||||
err = rproc_add(rproc);
|
||||
if (err)
|
||||
goto free_mem;
|
||||
|
||||
return 0;
|
||||
|
||||
free_mem:
|
||||
dma_free_coherent(rproc->dev.parent, SPROC_FW_SIZE,
|
||||
sproc->fw_addr, sproc->fw_dma_addr);
|
||||
free_rproc:
|
||||
/* Reset device data upon error */
|
||||
mdev->drv_data = NULL;
|
||||
rproc_put(rproc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct platform_driver sproc_driver = {
|
||||
.driver = {
|
||||
.name = SPROC_MODEM_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sproc_probe,
|
||||
.remove = sproc_drv_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(sproc_driver);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("STE Modem driver using the Remote Processor Framework");
|
Loading…
Add table
Add a link
Reference in a new issue