Skip to contentSkip to conversation

PNP: Switch from __check_region() to __request_region()

PATCH In review Jakub Sitnicki 2 participants 8 messages Dec 8, 2014 – Feb 19, 2015

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 PNP: Switch from __check_region() to __request_region()

PNP core is the last user of the __check_region() which has been deprecated for almost 12 years (since v2.5.54). Replace it with a combo of __request_region() followed by __release_region().

pnp_check_port() and pnp_check_mem() remain racy after this change.

---

There was a previous attempt at making this change 3 years ago but the author has never addressed the review comments:

https://lkml.org/lkml/2011/8/12/216

The end goal here is to get rid of __check_region() which lands in every kernel image because of the PNP core.

It has been previously pointed out that replacing __check_region() with request_region() obscures the fact that pnp_check_port() is racy:

https://lkml.org/lkml/2011/8/11/466

Because of that I've also considered just moving __check_region() to PNP core. However, that would require making free_resource() an exported symbol in kernel/resource.c.

On the other hand, a switch to request/release_region() makes pnp_check_port() and pnp_check_mem() follow the same pattern as found in pnp_check_irq() and pnp_check_dma():

	if (!dev->active) {
		if (request_<resource type>(...))
			return 0;
		free_<resource type>(...);
	}

Admittedly, I was not able to exercise the touched code paths on a commodity x86_64 laptop or under QEMU / VirtualBox (which lack ISA PnP support, AFAIK).

To my understanding, the correct way to test pnp_check_port() or pnp_check_mem() would be by issuing either:

  $ echo fill >/sys/bus/pnp/devices/XX:YY/resources
or
  $ echo auto >/sys/bus/pnp/devices/XX:YY/resources

... but only if the device is not attached or active, which is not the case for ACPI PnP devices on my machines. If anyone can provide hints on steps to test this, I will be glad to do so.

 drivers/pnp/resource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
drivers/pnp/resource.c +4 -2
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));
 	}
 
 	/* check if the resource is reserved */
@@ -241,8 +242,9 @@ int pnp_check_mem(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (check_mem_region(*addr, length(addr, end)))
+		if (!request_mem_region(*addr, length(addr, end), "pnp"))
 			return 0;
+		release_mem_region(*addr, length(addr, end));
 	}
 
 	/* check if the resource is reserved */
Signature · 1 line
1.9.3
#1 lore raw
Quoted 59 lines · Jakub Sitnicki

PNP core is the last user of the __check_region() which has been deprecated for almost 12 years (since v2.5.54). Replace it with a combo of __request_region() followed by __release_region().

pnp_check_port() and pnp_check_mem() remain racy after this change.

---

There was a previous attempt at making this change 3 years ago but the author has never addressed the review comments:

https://lkml.org/lkml/2011/8/12/216

The end goal here is to get rid of __check_region() which lands in every kernel image because of the PNP core.

It has been previously pointed out that replacing __check_region() with request_region() obscures the fact that pnp_check_port() is racy:

https://lkml.org/lkml/2011/8/11/466

Because of that I've also considered just moving __check_region() to PNP core. However, that would require making free_resource() an exported symbol in kernel/resource.c.

On the other hand, a switch to request/release_region() makes pnp_check_port() and pnp_check_mem() follow the same pattern as found in pnp_check_irq() and pnp_check_dma():

	if (!dev->active) {
		if (request_<resource type>(...))
			return 0;
		free_<resource type>(...);
	}

Admittedly, I was not able to exercise the touched code paths on a commodity x86_64 laptop or under QEMU / VirtualBox (which lack ISA PnP support, AFAIK).

To my understanding, the correct way to test pnp_check_port() or pnp_check_mem() would be by issuing either:

  $ echo fill >/sys/bus/pnp/devices/XX:YY/resources
or
  $ echo auto >/sys/bus/pnp/devices/XX:YY/resources

... but only if the device is not attached or active, which is not the case for ACPI PnP devices on my machines. If anyone can provide hints on steps to test this, I will be glad to do so.

 drivers/pnp/resource.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
drivers/pnp/resource.c +0 -0
index 782e822..f980ff7 100644

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 7 lines
drivers/pnp/resource.c +2 -1
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Quoted 15 lines
drivers/pnp/resource.c +2 -1
 	}
 
 	/* check if the resource is reserved */
@@ -241,8 +242,9 @@ int pnp_check_mem(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (check_mem_region(*addr, length(addr, end)))
+		if (!request_mem_region(*addr, length(addr, end), "pnp"))
 			return 0;
+		release_mem_region(*addr, length(addr, end));
 	}
 
 	/* check if the resource is reserved */
#2 lore raw
Quoted 17 lines · Rafael J. Wysocki

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 12 lines · Jakub Sitnicki
drivers/pnp/resource.c +2 -1
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Thanks for taking a look at this. I think we're good here. If you please bear with me for a moment:

release_resource() removes an element from the list of resource parent's children (and makes it an orphan):

	p = &old->parent->child;
	for (;;) {
		tmp = *p;
		if (!tmp)
			break;
		if (tmp == old) {
			*p = tmp->sibling;
			old->parent = NULL;
			return 0;
		}
		p = &tmp->sibling;
	}

release_region() does the same but with additional checks, and also frees the resource:

	p = &parent->child;
        /* ... */
	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
                        /* ... */
			*p = res->sibling;
                        /* ... */
			free_resource(res);
			return;
		}
		p = &res->sibling;
	}

When making the change I've based on other code in the kernel which also make use of request_region().

To quote one example, drivers/net/ethernet/8390/ne2k-pci.c cleans up its I/O port region when initialization fails like so:

static int ne2k_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)

{
        /* ... */

	if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
		dev_err(&pdev->dev, "I/O resource 0x%x @ 0x%lx busy\n",
			NE_IO_EXTENT, ioaddr);
		return -EBUSY;
	}

        /* ... */

	dev = alloc_ei_netdev();
	if (!dev) {
		dev_err(&pdev->dev, "cannot allocate ethernet device\n");
		goto err_out_free_res;
	}

        /* ... */

err_out_free_res:

	release_region (ioaddr, NE_IO_EXTENT);
	return -ENODEV;
}
Quoted 15 lines
Quoted 15 lines
drivers/pnp/resource.c +2 -1
 	}
 
 	/* check if the resource is reserved */
@@ -241,8 +242,9 @@ int pnp_check_mem(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (check_mem_region(*addr, length(addr, end)))
+		if (!request_mem_region(*addr, length(addr, end), "pnp"))
 			return 0;
+		release_mem_region(*addr, length(addr, end));
 	}
 
 	/* check if the resource is reserved */
#3 lore raw
Quoted 86 lines · Jakub Sitnicki
Quoted 17 lines · Rafael J. Wysocki

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 12 lines · Jakub Sitnicki
drivers/pnp/resource.c +2 -1
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Thanks for taking a look at this. I think we're good here. If you please bear with me for a moment:

release_resource() removes an element from the list of resource parent's children (and makes it an orphan):

	p = &old->parent->child;
	for (;;) {
		tmp = *p;
		if (!tmp)
			break;
		if (tmp == old) {
			*p = tmp->sibling;
			old->parent = NULL;
			return 0;
		}
		p = &tmp->sibling;
	}

release_region() does the same but with additional checks, and also frees the resource:

	p = &parent->child;
        /* ... */
	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
                        /* ... */
			*p = res->sibling;
                        /* ... */
			free_resource(res);
			return;
		}
		p = &res->sibling;
	}

When making the change I've based on other code in the kernel which also make use of request_region().

To quote one example, drivers/net/ethernet/8390/ne2k-pci.c cleans up its I/O port region when initialization fails like so:

static int ne2k_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)

{
        /* ... */

	if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
		dev_err(&pdev->dev, "I/O resource 0x%x @ 0x%lx busy\n",
			NE_IO_EXTENT, ioaddr);
		return -EBUSY;
	}

        /* ... */

	dev = alloc_ei_netdev();
	if (!dev) {
		dev_err(&pdev->dev, "cannot allocate ethernet device\n");
		goto err_out_free_res;
	}

        /* ... */
Quoted 4 lines

err_out_free_res:

	release_region (ioaddr, NE_IO_EXTENT);
	return -ENODEV;
}

Just wondering, do you have any further thoughts on this?

Thanks,
Jakub

#4 lore raw
Quoted 85 lines · Jakub Sitnicki
Quoted 84 lines · Jakub Sitnicki
Quoted 17 lines · Rafael J. Wysocki

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 12 lines · Jakub Sitnicki
drivers/pnp/resource.c +2 -1
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Thanks for taking a look at this. I think we're good here. If you please bear with me for a moment:

release_resource() removes an element from the list of resource parent's children (and makes it an orphan):

	p = &old->parent->child;
	for (;;) {
		tmp = *p;
		if (!tmp)
			break;
		if (tmp == old) {
			*p = tmp->sibling;
			old->parent = NULL;
			return 0;
		}
		p = &tmp->sibling;
	}

release_region() does the same but with additional checks, and also frees the resource:

	p = &parent->child;
        /* ... */
	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
                        /* ... */
			*p = res->sibling;
                        /* ... */
			free_resource(res);
			return;
		}
		p = &res->sibling;
	}

When making the change I've based on other code in the kernel which also make use of request_region().

To quote one example, drivers/net/ethernet/8390/ne2k-pci.c cleans up its I/O port region when initialization fails like so:

static int ne2k_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)

{
        /* ... */

	if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
		dev_err(&pdev->dev, "I/O resource 0x%x @ 0x%lx busy\n",
			NE_IO_EXTENT, ioaddr);
		return -EBUSY;
	}

        /* ... */

	dev = alloc_ei_netdev();
	if (!dev) {
		dev_err(&pdev->dev, "cannot allocate ethernet device\n");
		goto err_out_free_res;
	}
Quoted 8 lines
Quoted 6 lines
        /* ... */

err_out_free_res:

	release_region (ioaddr, NE_IO_EXTENT);
	return -ENODEV;
}

Just wondering, do you have any further thoughts on this?

I'll queue it up for 3.20 later in January.

Signature · 2 lines
I speak only for myself.
Rafael J. Wysocki, Intel Open Source Technology Center.
#5 lore raw
Quoted 88 lines · Rafael J. Wysocki
Quoted 87 lines · Jakub Sitnicki
Quoted 86 lines · Jakub Sitnicki
Quoted 17 lines · Rafael J. Wysocki

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 12 lines · Jakub Sitnicki
drivers/pnp/resource.c +2 -1
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Thanks for taking a look at this. I think we're good here. If you please bear with me for a moment:

release_resource() removes an element from the list of resource parent's children (and makes it an orphan):

	p = &old->parent->child;
	for (;;) {
		tmp = *p;
		if (!tmp)
			break;
		if (tmp == old) {
			*p = tmp->sibling;
			old->parent = NULL;
			return 0;
		}
		p = &tmp->sibling;
	}

release_region() does the same but with additional checks, and also frees the resource:

	p = &parent->child;
        /* ... */
	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
                        /* ... */
			*p = res->sibling;
                        /* ... */
			free_resource(res);
			return;
		}
		p = &res->sibling;
	}

When making the change I've based on other code in the kernel which also make use of request_region().

To quote one example, drivers/net/ethernet/8390/ne2k-pci.c cleans up its I/O port region when initialization fails like so:

static int ne2k_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)

{
        /* ... */

	if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
		dev_err(&pdev->dev, "I/O resource 0x%x @ 0x%lx busy\n",
			NE_IO_EXTENT, ioaddr);
		return -EBUSY;
	}

        /* ... */

	dev = alloc_ei_netdev();
	if (!dev) {
		dev_err(&pdev->dev, "cannot allocate ethernet device\n");
		goto err_out_free_res;
	}

        /* ... */
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

err_out_free_res:

	release_region (ioaddr, NE_IO_EXTENT);
	return -ENODEV;
}

Just wondering, do you have any further thoughts on this?

I'll queue it up for 3.20 later in January.

Done now.

Signature · 2 lines
I speak only for myself.
Rafael J. Wysocki, Intel Open Source Technology Center.
#6 lore raw
Quoted 94 lines · Rafael J. Wysocki
Quoted 93 lines · Rafael J. Wysocki
Quoted 92 lines · Jakub Sitnicki
Quoted 90 lines · Jakub Sitnicki
Quoted 17 lines · Rafael J. Wysocki

drivers/pnp/resource.c int pnp_check_port(struct pnp_dev *dev, struct resource *res) · line 184

Quoted 12 lines · Jakub Sitnicki
drivers/pnp/resource.c +2 -1
index 782e822..f980ff7 100644
@@ -179,8 +179,9 @@ int pnp_check_port(struct pnp_dev *dev, struct resource *res)
 	/* check if the resource is already in use, skip if the
 	 * device is active because it itself may be in use */
 	if (!dev->active) {
-		if (__check_region(&ioport_resource, *port, length(port, end)))
+		if (!request_region(*port, length(port, end), "pnp"))
 			return 0;
+		release_region(*port, length(port, end));

Shouldn't we also release the resource returned by request_region() if it is not NULL?

Thanks for taking a look at this. I think we're good here. If you please bear with me for a moment:

release_resource() removes an element from the list of resource parent's children (and makes it an orphan):

	p = &old->parent->child;
	for (;;) {
		tmp = *p;
		if (!tmp)
			break;
		if (tmp == old) {
			*p = tmp->sibling;
			old->parent = NULL;
			return 0;
		}
		p = &tmp->sibling;
	}

release_region() does the same but with additional checks, and also frees the resource:

	p = &parent->child;
        /* ... */
	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
                        /* ... */
			*p = res->sibling;
                        /* ... */
			free_resource(res);
			return;
		}
		p = &res->sibling;
	}

When making the change I've based on other code in the kernel which also make use of request_region().

To quote one example, drivers/net/ethernet/8390/ne2k-pci.c cleans up its I/O port region when initialization fails like so:

static int ne2k_pci_init_one(struct pci_dev *pdev,
const struct pci_device_id *ent)

{
        /* ... */

	if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
		dev_err(&pdev->dev, "I/O resource 0x%x @ 0x%lx busy\n",
			NE_IO_EXTENT, ioaddr);
		return -EBUSY;
	}

        /* ... */

	dev = alloc_ei_netdev();
	if (!dev) {
		dev_err(&pdev->dev, "cannot allocate ethernet device\n");
		goto err_out_free_res;
	}

        /* ... */

err_out_free_res:

	release_region (ioaddr, NE_IO_EXTENT);
	return -ENODEV;
}
Quoted 5 lines
Quoted 3 lines
Quoted 1 line

Just wondering, do you have any further thoughts on this?

I'll queue it up for 3.20 later in January.

Done now.

If you don't mind me asking, is this going to go through the linux-pm tree?

Thanks,
Jakub

#7 lore raw
Quoted 1 line · Jakub Sitnicki

On Fri, Jan 30, 2015 at 01:01 AM CET, Rafael J. Wysocki wrote:

[cut]

Quoted 6 lines
Quoted 3 lines
Quoted 1 line

I'll queue it up for 3.20 later in January.

Done now.

If you don't mind me asking, is this going to go through the linux-pm tree?

Yes, but unfortunately I skipped the pnp branch accidentally last time.

I'll add it to the next pull request.

Signature · 2 lines
I speak only for myself.
Rafael J. Wysocki, Intel Open Source Technology Center.