2006-12-08 18:14:50

by Arnd Bergmann

[permalink] [raw]
Subject: [PATCH] libata: fix oops with sparsemem

libata incorrectly passes NULL arguments to sg_set_buf, which
crashes on powerpc64 when looking for the corresponding mem_section.

This introduces a new ata_exec_nodma() wrapper that takes no buffer
arguments and does not call sg_set_buf either. In order to make it
easier to detect this sort of problem, it also adds a WARN_ON(!buf)
to sg_set_buf() so we get a log message even platforms without
sparsemem.

Signed-off-by: Arnd Bergmann <[email protected]>

Index: linux-2.6/drivers/ata/libata-core.c
===================================================================
--- linux-2.6.orig/drivers/ata/libata-core.c
+++ linux-2.6/drivers/ata/libata-core.c
@@ -1332,7 +1332,7 @@ unsigned ata_exec_internal_sg(struct ata
}

/**
- * ata_exec_internal_sg - execute libata internal command
+ * ata_exec_internal - execute libata internal command
* @dev: Device to which the command is sent
* @tf: Taskfile registers for the command and the result
* @cdb: CDB for packet command
@@ -1361,6 +1361,25 @@ unsigned ata_exec_internal(struct ata_de
}

/**
+ * ata_exec_nodma - execute libata internal command
+ * @dev: Device to which the command is sent
+ * @tf: Taskfile registers for the command and the result
+ *
+ * Wrapper around ata_exec_internal_sg() which takes no
+ * data buffer.
+ *
+ * LOCKING:
+ * None. Should be called with kernel context, might sleep.
+ *
+ * RETURNS:
+ * Zero on success, AC_ERR_* mask on failure
+ */
+static unsigned ata_exec_nodma(struct ata_device *dev, struct ata_taskfile *tf)
+{
+ return ata_exec_internal_sg(dev, tf, NULL, DMA_NONE, NULL, 0);
+}
+
+/**
* ata_do_simple_cmd - execute simple internal command
* @dev: Device to which the command is sent
* @cmd: Opcode to execute
@@ -1384,7 +1403,7 @@ unsigned int ata_do_simple_cmd(struct at
tf.flags |= ATA_TFLAG_DEVICE;
tf.protocol = ATA_PROT_NODATA;

- return ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0);
+ return ata_exec_nodma(dev, &tf);
}

/**
@@ -3475,7 +3494,7 @@ static unsigned int ata_dev_set_xfermode
tf.protocol = ATA_PROT_NODATA;
tf.nsect = dev->xfer_mode;

- err_mask = ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0);
+ err_mask = ata_exec_nodma(dev, &tf);

DPRINTK("EXIT, err_mask=%x\n", err_mask);
return err_mask;
@@ -3513,7 +3532,7 @@ static unsigned int ata_dev_init_params(
tf.nsect = sectors;
tf.device |= (heads - 1) & 0x0f; /* max head = num. of heads - 1 */

- err_mask = ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0);
+ err_mask = ata_exec_nodma(dev, &tf);

DPRINTK("EXIT, err_mask=%x\n", err_mask);
return err_mask;
Index: linux-2.6/include/linux/scatterlist.h
===================================================================
--- linux-2.6.orig/include/linux/scatterlist.h
+++ linux-2.6/include/linux/scatterlist.h
@@ -8,6 +8,8 @@
static inline void sg_set_buf(struct scatterlist *sg, const void *buf,
unsigned int buflen)
{
+ WARN_ON(!buf); /* virt_to_page(NULL) crashes with sparsemem */
+
sg->page = virt_to_page(buf);
sg->offset = offset_in_page(buf);
sg->length = buflen;


2006-12-11 14:03:07

by Tejun Heo

[permalink] [raw]
Subject: [PATCH] libata: don't initialize sg in ata_exec_internal() if DMA_NONE

Calling sg_init_one() with NULL buf causes oops on certain
configurations. Don't initialize sg in ata_exec_internal() if
DMA_NONE and make the function complain if @buf is NULL when dma_dir
isn't DMA_NONE. While at it, fix comment.

The problem is discovered and initial patch was submitted by Arnd
Bergmann.

Signed-off-by: Tejun Heo <[email protected]>
Cc: Arnd Bergmann <[email protected]>
---

Hello, Arnd Bergmann.

Thanks for spotting and fixing this but ata_exec_internal_nodma() is
almost identical to ata_do_simple_cmd() and ata_exec_internal() itself
needs fixing anyway. This patch just fixes ata_exec_internal(). I'll
follow up with conversion to ata_do_simple_cmd().

Thanks.

diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 011c0a8..70e02e9 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -1332,7 +1332,7 @@ unsigned ata_exec_internal_sg(struct ata_device *dev,
}

/**
- * ata_exec_internal_sg - execute libata internal command
+ * ata_exec_internal - execute libata internal command
* @dev: Device to which the command is sent
* @tf: Taskfile registers for the command and the result
* @cdb: CDB for packet command
@@ -1354,10 +1354,15 @@ unsigned ata_exec_internal(struct ata_device *dev,
int dma_dir, void *buf, unsigned int buflen)
{
struct scatterlist sg;
+ unsigned int n_elem = 0;

- sg_init_one(&sg, buf, buflen);
+ if (dma_dir != DMA_NONE) {
+ WARN_ON(!buf);
+ sg_init_one(&sg, buf, buflen);
+ n_elem++;
+ }

- return ata_exec_internal_sg(dev, tf, cdb, dma_dir, &sg, 1);
+ return ata_exec_internal_sg(dev, tf, cdb, dma_dir, &sg, n_elem);
}

/**

2006-12-11 14:23:46

by Tejun Heo

[permalink] [raw]
Subject: Re: [PATCH] libata: don't initialize sg in ata_exec_internal() if DMA_NONE

Tejun Heo wrote:
> I'll follow up with conversion to ata_do_simple_cmd().

The current situation is...

ata_exec_internal_sg() : no user except for ata_exec_internal() yet
ata_exec_internal() : one data transferring user. other are non-data
ata_do_simple_cmd() : three users

So, adding another exec_internal variant doesn't look so hot. It seems
we already have enough variants considering the small number of users.
I think it's best to leave it alone for now.

Thanks.

--
tejun

2006-12-11 14:34:06

by Jeff Garzik

[permalink] [raw]
Subject: Re: [PATCH] libata: don't initialize sg in ata_exec_internal() if DMA_NONE

Arnd Bergmann wrote:
> On Monday 11 December 2006 15:02, Tejun Heo wrote:
>> {
>> struct scatterlist sg;
>> + unsigned int n_elem = 0;
>>
>> - sg_init_one(&sg, buf, buflen);
>> + if (dma_dir != DMA_NONE) {
>> + WARN_ON(!buf);
>> + sg_init_one(&sg, buf, buflen);
>> + n_elem++;
>> + }
>>
> Ok, looks good as well. I still think we should have the WARN_ON()
> in sg_set_buf(), but I can send a separate patch for that to linux-mm.

Please CC me and linux-ide on all libata patches (certainly akpm as
well). Andrew picks up most of the libata changes automatically via git
from my libata-dev.git#ALL.

Jeff



2006-12-11 15:55:19

by Jeff Garzik

[permalink] [raw]
Subject: Re: [PATCH] libata: don't initialize sg in ata_exec_internal() if DMA_NONE

Tejun Heo wrote:
> Calling sg_init_one() with NULL buf causes oops on certain
> configurations. Don't initialize sg in ata_exec_internal() if
> DMA_NONE and make the function complain if @buf is NULL when dma_dir
> isn't DMA_NONE. While at it, fix comment.
>
> The problem is discovered and initial patch was submitted by Arnd
> Bergmann.
>
> Signed-off-by: Tejun Heo <[email protected]>
> Cc: Arnd Bergmann <[email protected]>
> ---
>
> Hello, Arnd Bergmann.
>
> Thanks for spotting and fixing this but ata_exec_internal_nodma() is
> almost identical to ata_do_simple_cmd() and ata_exec_internal() itself
> needs fixing anyway. This patch just fixes ata_exec_internal(). I'll
> follow up with conversion to ata_do_simple_cmd().
>
> Thanks.
>
> diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
> index 011c0a8..70e02e9 100644
> --- a/drivers/ata/libata-core.c
> +++ b/drivers/ata/libata-core.c
> @@ -1332,7 +1332,7 @@ unsigned ata_exec_internal_sg(struct ata_device *dev,
> }
>
> /**
> - * ata_exec_internal_sg - execute libata internal command
> + * ata_exec_internal - execute libata internal command
> * @dev: Device to which the command is sent
> * @tf: Taskfile registers for the command and the result
> * @cdb: CDB for packet command
> @@ -1354,10 +1354,15 @@ unsigned ata_exec_internal(struct ata_device *dev,
> int dma_dir, void *buf, unsigned int buflen)
> {
> struct scatterlist sg;
> + unsigned int n_elem = 0;
>
> - sg_init_one(&sg, buf, buflen);
> + if (dma_dir != DMA_NONE) {
> + WARN_ON(!buf);
> + sg_init_one(&sg, buf, buflen);
> + n_elem++;
> + }
>
> - return ata_exec_internal_sg(dev, tf, cdb, dma_dir, &sg, 1);
> + return ata_exec_internal_sg(dev, tf, cdb, dma_dir, &sg, n_elem);

ACK, if you conditionally replace "&sg" with NULL. That's the safer
choice, as it guarantees (via an oops) that the user will not be
touching sg, if dma_dir==DMA_NONE.

Jeff



2006-12-11 17:15:41

by Tejun Heo

[permalink] [raw]
Subject: [PATCH] libata: don't initialize sg in ata_exec_internal() if DMA_NONE (take #2)

Calling sg_init_one() with NULL buf causes oops on certain
configurations. Don't initialize sg in ata_exec_internal() if
DMA_NONE and make the function complain if @buf is NULL when dma_dir
isn't DMA_NONE. While at it, fix comment.

The problem is discovered and initial patch was submitted by Arnd
Bergmann.

Signed-off-by: Tejun Heo <[email protected]>
Cc: Arnd Bergmann <[email protected]>
---

Modified as suggested.

Thanks.

diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 011c0a8..0d51d13 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -1332,7 +1332,7 @@ unsigned ata_exec_internal_sg(struct ata_device *dev,
}

/**
- * ata_exec_internal_sg - execute libata internal command
+ * ata_exec_internal - execute libata internal command
* @dev: Device to which the command is sent
* @tf: Taskfile registers for the command and the result
* @cdb: CDB for packet command
@@ -1353,11 +1353,17 @@ unsigned ata_exec_internal(struct ata_device *dev,
struct ata_taskfile *tf, const u8 *cdb,
int dma_dir, void *buf, unsigned int buflen)
{
- struct scatterlist sg;
+ struct scatterlist *psg = NULL, sg;
+ unsigned int n_elem = 0;

- sg_init_one(&sg, buf, buflen);
+ if (dma_dir != DMA_NONE) {
+ WARN_ON(!buf);
+ sg_init_one(&sg, buf, buflen);
+ psg = &sg;
+ n_elem++;
+ }

- return ata_exec_internal_sg(dev, tf, cdb, dma_dir, &sg, 1);
+ return ata_exec_internal_sg(dev, tf, cdb, dma_dir, psg, n_elem);
}

/**