Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c354b6fd authored by Kyle Yan's avatar Kyle Yan Committed by Gerrit - the friendly Code Review server
Browse files

Merge "spmi: pmic_arb: block access of invalid read and writes" into msm-4.8

parents 6f684de1 ea64f7f3
Loading
Loading
Loading
Loading
+24 −1
Original line number Original line Diff line number Diff line
@@ -42,7 +42,7 @@ Required properties:
    cell 4: interrupt flags indicating level-sense information, as defined in
    cell 4: interrupt flags indicating level-sense information, as defined in
            dt-bindings/interrupt-controller/irq.h
            dt-bindings/interrupt-controller/irq.h


Example:
Example V1 PMIC-Arbiter:


	spmi {
	spmi {
		compatible = "qcom,spmi-pmic-arb";
		compatible = "qcom,spmi-pmic-arb";
@@ -63,3 +63,26 @@ Example:
		interrupt-controller;
		interrupt-controller;
		#interrupt-cells = <4>;
		#interrupt-cells = <4>;
	};
	};

Example V2 PMIC-Arbiter:

	spmi_bus: qcom,spmi@200f000 {
		compatible = "qcom,spmi-pmic-arb";
		reg-names = "core", "chnls", "obsrvr", "intr", "cnfg";
		reg = <0x200f000 0xc00>,
			<0x2400000 0x400000>,
			<0x2c00000 0x400000>,
			<0x3800000 0x200000>,
			<0x200a000 0x2100>;

		interrupt-names = "periph_irq";
		interrupts = <0 190 0>;

		qcom,ee = <0>;

		#address-cells = <2>;
		#size-cells = <0>;

		interrupt-controller;
		#interrupt-cells = <4>;
	};
+83 −2
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
 * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * it under the terms of the GNU General Public License version 2 and
@@ -111,6 +111,7 @@ struct pmic_arb_ver_ops;
 * @ee:			the current Execution Environment
 * @ee:			the current Execution Environment
 * @min_apid:		minimum APID (used for bounding IRQ search)
 * @min_apid:		minimum APID (used for bounding IRQ search)
 * @max_apid:		maximum APID
 * @max_apid:		maximum APID
 * @max_periph:		maximum number of PMIC peripherals supported by HW.
 * @mapping_table:	in-memory copy of PPID -> APID mapping table.
 * @mapping_table:	in-memory copy of PPID -> APID mapping table.
 * @domain:		irq domain object for PMIC IRQ domain
 * @domain:		irq domain object for PMIC IRQ domain
 * @spmic:		SPMI controller object
 * @spmic:		SPMI controller object
@@ -132,6 +133,7 @@ struct spmi_pmic_arb_dev {
	u8			ee;
	u8			ee;
	u16			min_apid;
	u16			min_apid;
	u16			max_apid;
	u16			max_apid;
	u16			max_periph;
	u32			*mapping_table;
	u32			*mapping_table;
	DECLARE_BITMAP(mapping_table_valid, PMIC_ARB_MAX_PERIPHS);
	DECLARE_BITMAP(mapping_table_valid, PMIC_ARB_MAX_PERIPHS);
	struct irq_domain	*domain;
	struct irq_domain	*domain;
@@ -140,11 +142,13 @@ struct spmi_pmic_arb_dev {
	const struct pmic_arb_ver_ops *ver_ops;
	const struct pmic_arb_ver_ops *ver_ops;
	u16			*ppid_to_chan;
	u16			*ppid_to_chan;
	u16			last_channel;
	u16			last_channel;
	u8			*chan_to_owner;
};
};


/**
/**
 * pmic_arb_ver: version dependent functionality.
 * pmic_arb_ver: version dependent functionality.
 *
 *
 * @mode:	access rights to specified pmic peripheral.
 * @non_data_cmd:	on v1 issues an spmi non-data command.
 * @non_data_cmd:	on v1 issues an spmi non-data command.
 *			on v2 no HW support, returns -EOPNOTSUPP.
 *			on v2 no HW support, returns -EOPNOTSUPP.
 * @offset:		on v1 offset of per-ee channel.
 * @offset:		on v1 offset of per-ee channel.
@@ -160,6 +164,8 @@ struct spmi_pmic_arb_dev {
 *			on v2 offset of SPMI_PIC_IRQ_CLEARn.
 *			on v2 offset of SPMI_PIC_IRQ_CLEARn.
 */
 */
struct pmic_arb_ver_ops {
struct pmic_arb_ver_ops {
	int (*mode)(struct spmi_pmic_arb_dev *dev, u8 sid, u16 addr,
			mode_t *mode);
	/* spmi commands (read_cmd, write_cmd, cmd) functionality */
	/* spmi commands (read_cmd, write_cmd, cmd) functionality */
	int (*offset)(struct spmi_pmic_arb_dev *dev, u8 sid, u16 addr,
	int (*offset)(struct spmi_pmic_arb_dev *dev, u8 sid, u16 addr,
		      u32 *offset);
		      u32 *offset);
@@ -313,11 +319,23 @@ static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
	u32 cmd;
	u32 cmd;
	int rc;
	int rc;
	u32 offset;
	u32 offset;
	mode_t mode;


	rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, &offset);
	rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, &offset);
	if (rc)
	if (rc)
		return rc;
		return rc;


	rc = pmic_arb->ver_ops->mode(pmic_arb, sid, addr, &mode);
	if (rc)
		return rc;

	if (!(mode & 0400)) {
		dev_err(&pmic_arb->spmic->dev,
			"error: impermissible read from peripheral sid:%d addr:0x%x\n",
			sid, addr);
		return -ENODEV;
	}

	if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
	if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
		dev_err(&ctrl->dev,
		dev_err(&ctrl->dev,
			"pmic-arb supports 1..%d bytes per trans, but:%zu requested",
			"pmic-arb supports 1..%d bytes per trans, but:%zu requested",
@@ -364,11 +382,23 @@ static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
	u32 cmd;
	u32 cmd;
	int rc;
	int rc;
	u32 offset;
	u32 offset;
	mode_t mode;


	rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, &offset);
	rc = pmic_arb->ver_ops->offset(pmic_arb, sid, addr, &offset);
	if (rc)
	if (rc)
		return rc;
		return rc;


	rc = pmic_arb->ver_ops->mode(pmic_arb, sid, addr, &mode);
	if (rc)
		return rc;

	if (!(mode & 0200)) {
		dev_err(&pmic_arb->spmic->dev,
			"error: impermissible write to peripheral sid:%d addr:0x%x\n",
			sid, addr);
		return -ENODEV;
	}

	if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
	if (bc >= PMIC_ARB_MAX_TRANS_BYTES) {
		dev_err(&ctrl->dev,
		dev_err(&ctrl->dev,
			"pmic-arb supports 1..%d bytes per trans, but:%zu requested",
			"pmic-arb supports 1..%d bytes per trans, but:%zu requested",
@@ -727,6 +757,13 @@ static int qpnpint_irq_domain_map(struct irq_domain *d,
	return 0;
	return 0;
}
}


static int
pmic_arb_mode_v1(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr, mode_t *mode)
{
	*mode = 0600;
	return 0;
}

/* v1 offset per ee */
/* v1 offset per ee */
static int
static int
pmic_arb_offset_v1(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr, u32 *offset)
pmic_arb_offset_v1(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr, u32 *offset)
@@ -745,7 +782,11 @@ static u16 pmic_arb_find_chan(struct spmi_pmic_arb_dev *pa, u16 ppid)
	 * PMIC_ARB_REG_CHNL is a table in HW mapping channel to ppid.
	 * PMIC_ARB_REG_CHNL is a table in HW mapping channel to ppid.
	 * ppid_to_chan is an in-memory invert of that table.
	 * ppid_to_chan is an in-memory invert of that table.
	 */
	 */
	for (chan = pa->last_channel; ; chan++) {
	for (chan = pa->last_channel; chan < pa->max_periph; chan++) {
		regval = readl_relaxed(pa->cnfg +
				      SPMI_OWNERSHIP_TABLE_REG(chan));
		pa->chan_to_owner[chan] = SPMI_OWNERSHIP_PERIPH2OWNER(regval);

		offset = PMIC_ARB_REG_CHNL(chan);
		offset = PMIC_ARB_REG_CHNL(chan);
		if (offset >= pa->core_size)
		if (offset >= pa->core_size)
			break;
			break;
@@ -766,6 +807,26 @@ static u16 pmic_arb_find_chan(struct spmi_pmic_arb_dev *pa, u16 ppid)
	return chan;
	return chan;
}
}


static int
pmic_arb_mode_v2(struct spmi_pmic_arb_dev *pa, u8 sid, u16 addr, mode_t *mode)
{
	u16 ppid = (sid << 8) | (addr >> 8);
	u16 chan;
	u8 owner;

	chan = pa->ppid_to_chan[ppid];
	if (!(chan & PMIC_ARB_CHAN_VALID))
		return -ENODEV;

	*mode = 0;
	*mode |= 0400;

	chan &= ~PMIC_ARB_CHAN_VALID;
	owner = pa->chan_to_owner[chan];
	if (owner == pa->ee)
		*mode |= 0200;
	return 0;
}


/* v2 offset per ppid (chan) and per ee */
/* v2 offset per ppid (chan) and per ee */
static int
static int
@@ -836,6 +897,7 @@ static u32 pmic_arb_irq_clear_v2(u8 n)
}
}


static const struct pmic_arb_ver_ops pmic_arb_v1 = {
static const struct pmic_arb_ver_ops pmic_arb_v1 = {
	.mode			= pmic_arb_mode_v1,
	.non_data_cmd		= pmic_arb_non_data_cmd_v1,
	.non_data_cmd		= pmic_arb_non_data_cmd_v1,
	.offset			= pmic_arb_offset_v1,
	.offset			= pmic_arb_offset_v1,
	.fmt_cmd		= pmic_arb_fmt_cmd_v1,
	.fmt_cmd		= pmic_arb_fmt_cmd_v1,
@@ -846,6 +908,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v1 = {
};
};


static const struct pmic_arb_ver_ops pmic_arb_v2 = {
static const struct pmic_arb_ver_ops pmic_arb_v2 = {
	.mode			= pmic_arb_mode_v2,
	.non_data_cmd		= pmic_arb_non_data_cmd_v2,
	.non_data_cmd		= pmic_arb_non_data_cmd_v2,
	.offset			= pmic_arb_offset_v2,
	.offset			= pmic_arb_offset_v2,
	.fmt_cmd		= pmic_arb_fmt_cmd_v2,
	.fmt_cmd		= pmic_arb_fmt_cmd_v2,
@@ -879,6 +942,12 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)


	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
	pa->core_size = resource_size(res);
	pa->core_size = resource_size(res);
	if (pa->core_size <= 0x800) {
		dev_err(&pdev->dev, "core_size is smaller than 0x800. Failing Probe\n");
		err = -EINVAL;
		goto err_put_ctrl;
	}

	core = devm_ioremap_resource(&ctrl->dev, res);
	core = devm_ioremap_resource(&ctrl->dev, res);
	if (IS_ERR(core)) {
	if (IS_ERR(core)) {
		err = PTR_ERR(core);
		err = PTR_ERR(core);
@@ -899,6 +968,9 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
		pa->core = core;
		pa->core = core;
		pa->ver_ops = &pmic_arb_v2;
		pa->ver_ops = &pmic_arb_v2;


		/* the apid to ppid table starts at PMIC_ARB_REG_CHNL(0) */
		pa->max_periph =  (pa->core_size - PMIC_ARB_REG_CHNL(0)) / 4;

		res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
		res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						   "obsrvr");
						   "obsrvr");
		pa->rd_base = devm_ioremap_resource(&ctrl->dev, res);
		pa->rd_base = devm_ioremap_resource(&ctrl->dev, res);
@@ -923,6 +995,15 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
			err = -ENOMEM;
			err = -ENOMEM;
			goto err_put_ctrl;
			goto err_put_ctrl;
		}
		}

		pa->chan_to_owner = devm_kcalloc(&ctrl->dev,
						 pa->max_periph,
						 sizeof(*pa->chan_to_owner),
						 GFP_KERNEL);
		if (!pa->chan_to_owner) {
			err = -ENOMEM;
			goto err_put_ctrl;
		}
	}
	}


	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr");
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr");